diff --git a/CHANGELOG.md b/CHANGELOG.md index ad57f0f..8a15ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Change log +## v0.7.0 - 2024-08-23 + +### Added +- SPOT `FIAT` Endpoints: + - `GET /sapi/v1/fiat/orders` - Get Fiat Deposit/Withdraw History + - `GET /sapi/v1/fiat/payments` - Get Fiat Payments History +- Websocket Stream: + - `@miniTicker` - Individual Symbol Mini Ticker Stream + +### Updated +- Updated `SymbolInfo` and `SymbolFilter` types + +### Fixed +- Fixed issue with `stopCh` not stopping the WebSocket connection +- Fixed the `stop` method for `userDataStream` +- Fixed symbols method for `NewTicker24hrService` + ## v0.6.0 - 2024-06-19 ### Fixed diff --git a/client.go b/client.go index 452c8e6..d78994e 100644 --- a/client.go +++ b/client.go @@ -767,3 +767,11 @@ func (c *Client) NewPingUserStream() *PingUserStream { func (c *Client) NewCloseUserStream() *CloseUserStream { return &CloseUserStream{c: c} } + +func (c *Client) NewGetFiatDepositWithdrawHistoryService() *GetFiatDepositWithdrawHistoryService { + return &GetFiatDepositWithdrawHistoryService{c: c} +} + +func (c *Client) NewGetFiatPaymentHistoryService() *GetFiatPaymentHistoryService { + return &GetFiatPaymentHistoryService{c: c} +} diff --git a/consts.go b/consts.go index 1b0444a..f7fad1e 100644 --- a/consts.go +++ b/consts.go @@ -2,4 +2,4 @@ package binance_connector const Name = "binance-connector-go" -const Version = "0.6.0" +const Version = "0.7.0" diff --git a/examples/fiat/GetFiatDepositWithdrawHistory/GetFiatDepositWithdrawHistory.go b/examples/fiat/GetFiatDepositWithdrawHistory/GetFiatDepositWithdrawHistory.go new file mode 100644 index 0000000..5b0c1d4 --- /dev/null +++ b/examples/fiat/GetFiatDepositWithdrawHistory/GetFiatDepositWithdrawHistory.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "fmt" + + binance_connector "github.com/binance/binance-connector-go" +) + +func main() { + GetFiatDepositWithdrawHistory() +} + +func GetFiatDepositWithdrawHistory() { + apiKey := "your api key" + secretKey := "your secret key" + baseURL := "https://api.binance.com" + + client := binance_connector.NewClient(apiKey, secretKey, baseURL) + + // GetFiatDepositWithdrawHistoryService - /sapi/v1/fiat/orders + getFiatDepositWithdrawHistory, err := client.NewGetFiatDepositWithdrawHistoryService(). + TransactionType("0"). + Do(context.Background()) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(binance_connector.PrettyPrint(getFiatDepositWithdrawHistory)) +} diff --git a/examples/fiat/GetFiatPaymentHistory/GetFiatPaymentHistory.go b/examples/fiat/GetFiatPaymentHistory/GetFiatPaymentHistory.go new file mode 100644 index 0000000..5ede559 --- /dev/null +++ b/examples/fiat/GetFiatPaymentHistory/GetFiatPaymentHistory.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "fmt" + + binance_connector "github.com/binance/binance-connector-go" +) + +func main() { + GetFiatPaymentHistory() +} + +func GetFiatPaymentHistory() { + apiKey := "your api key" + secretKey := "your secret key" + baseURL := "https://api.binance.com" + + client := binance_connector.NewClient(apiKey, secretKey, baseURL) + + // GetFiatPaymentHistoryService - /sapi/v1/fiat/payments + getFiatPaymentHistory, err := client.NewGetFiatPaymentHistoryService(). + TransactionType("0"). + Do(context.Background()) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(binance_connector.PrettyPrint(getFiatPaymentHistory)) +} diff --git a/examples/websocket/AllMarketMiniTickers/AllMarketMiniTickers.go b/examples/websocket/AllMarketMiniTickers/AllMarketMiniTickers.go index ab3e171..8273a3e 100644 --- a/examples/websocket/AllMarketMiniTickers/AllMarketMiniTickers.go +++ b/examples/websocket/AllMarketMiniTickers/AllMarketMiniTickers.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "time" binance_connector "github.com/binance/binance-connector-go" ) @@ -18,10 +19,15 @@ func WsAllMarketMiniTickers() { errHandler := func(err error) { fmt.Println(err) } - doneCh, _, err := websocketStreamClient.WsAllMarketMiniTickersStatServe(wsAllMarketMiniTickersHandler, errHandler) + doneCh, stopCh, err := websocketStreamClient.WsAllMarketMiniTickersStatServe(wsAllMarketMiniTickersHandler, errHandler) if err != nil { fmt.Println(err) return } + // use stopCh to exit + go func() { + time.Sleep(10 * time.Second) + stopCh <- struct{}{} + }() <-doneCh } diff --git a/examples/websocket/AllMarketTickers/AllMarketTickers.go b/examples/websocket/AllMarketTickers/AllMarketTickers.go index 4fce408..eadd893 100644 --- a/examples/websocket/AllMarketTickers/AllMarketTickers.go +++ b/examples/websocket/AllMarketTickers/AllMarketTickers.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "time" binance_connector "github.com/binance/binance-connector-go" ) @@ -18,10 +19,15 @@ func WsAllMarketTickersExample() { errHandler := func(err error) { fmt.Println(err) } - doneCh, _, err := websocketStreamClient.WsAllMarketTickersStatServe(wsAllMarketTickersHandler, errHandler) + doneCh, stopCh, err := websocketStreamClient.WsAllMarketTickersStatServe(wsAllMarketTickersHandler, errHandler) if err != nil { fmt.Println(err) return } + // use stopCh to exit + go func() { + time.Sleep(10 * time.Second) + stopCh <- struct{}{} + }() <-doneCh } diff --git a/examples/websocket/CombinedDepth/CombinedDepth.go b/examples/websocket/CombinedDepth/CombinedDepth.go index d960a04..1edba9c 100644 --- a/examples/websocket/CombinedDepth/CombinedDepth.go +++ b/examples/websocket/CombinedDepth/CombinedDepth.go @@ -26,7 +26,7 @@ func WsCombinedDepthHandlerExample() { } // use stopCh to exit go func() { - time.Sleep(5 * time.Second) + time.Sleep(10 * time.Second) stopCh <- struct{}{} }() // remove this if you do not want to be blocked here diff --git a/examples/websocket/MarketMiniTickers/MarketMiniTickers.go b/examples/websocket/MarketMiniTickers/MarketMiniTickers.go new file mode 100644 index 0000000..afbf55d --- /dev/null +++ b/examples/websocket/MarketMiniTickers/MarketMiniTickers.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "time" + + binance_connector "github.com/binance/binance-connector-go" +) + +func main() { + WsMarketMiniTickers() +} + +func WsMarketMiniTickers() { + websocketStreamClient := binance_connector.NewWebsocketStreamClient(false) + wsMarketMiniTickersHandler := func(event binance_connector.WsMarketMiniTickerStatEvent) { + fmt.Println(binance_connector.PrettyPrint(event)) + } + errHandler := func(err error) { + fmt.Println(err) + } + doneCh, stopCh, err := websocketStreamClient.WsMarketMiniTickersStatServe("BNBBTC", wsMarketMiniTickersHandler, errHandler) + if err != nil { + fmt.Println(err) + return + } + // use stopCh to exit + go func() { + time.Sleep(10 * time.Second) + stopCh <- struct{}{} + }() + <-doneCh +} diff --git a/examples/websocket/aggtrades/aggtrades.go b/examples/websocket/aggtrades/aggtrades.go index 7602fc5..9d10557 100644 --- a/examples/websocket/aggtrades/aggtrades.go +++ b/examples/websocket/aggtrades/aggtrades.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "time" binance_connector "github.com/binance/binance-connector-go" ) @@ -20,10 +21,15 @@ func AggTradesExample() { errHandler := func(err error) { fmt.Println(err) } - doneCh, _, err := websocketStreamClient.WsAggTradeServe("BTCUSDT", wsAggTradeHandler, errHandler) + doneCh, stopCh, err := websocketStreamClient.WsAggTradeServe("BTCUSDT", wsAggTradeHandler, errHandler) if err != nil { fmt.Println(err) return } + // use stopCh to exit + go func() { + time.Sleep(10 * time.Second) + stopCh <- struct{}{} + }() <-doneCh } diff --git a/examples/websocket/bookticker/bookticker.go b/examples/websocket/bookticker/bookticker.go index 927b411..8d24df8 100644 --- a/examples/websocket/bookticker/bookticker.go +++ b/examples/websocket/bookticker/bookticker.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "time" binance_connector "github.com/binance/binance-connector-go" ) @@ -18,10 +19,15 @@ func WsBookTickerExample() { errHandler := func(err error) { fmt.Println(err) } - doneCh, _, err := websocketStreamClient.WsBookTickerServe("LTCBTC", wsBookTickerHandler, errHandler) + doneCh, stopCh, err := websocketStreamClient.WsBookTickerServe("LTCBTC", wsBookTickerHandler, errHandler) if err != nil { fmt.Println(err) return } + // use stopCh to exit + go func() { + time.Sleep(10 * time.Second) + stopCh <- struct{}{} + }() <-doneCh } diff --git a/examples/websocket/depth/depth.go b/examples/websocket/depth/depth.go index 69f3ce4..eb23560 100644 --- a/examples/websocket/depth/depth.go +++ b/examples/websocket/depth/depth.go @@ -26,7 +26,7 @@ func WsDepthHandlerExample() { } // use stopCh to exit go func() { - time.Sleep(5 * time.Second) + time.Sleep(10 * time.Second) stopCh <- struct{}{} }() // remove this if you do not want to be blocked here diff --git a/examples/websocket/kline/kline.go b/examples/websocket/kline/kline.go index 146a098..dfb8534 100644 --- a/examples/websocket/kline/kline.go +++ b/examples/websocket/kline/kline.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "time" binance_connector "github.com/binance/binance-connector-go" ) @@ -18,10 +19,15 @@ func WsKlineExample() { errHandler := func(err error) { fmt.Println(err) } - doneCh, _, err := websocketStreamClient.WsKlineServe("LTCBTC", "1m", wsKlineHandler, errHandler) + doneCh, stopCh, err := websocketStreamClient.WsKlineServe("LTCBTC", "1m", wsKlineHandler, errHandler) if err != nil { fmt.Println(err) return } + // use stopCh to exit + go func() { + time.Sleep(10 * time.Second) + stopCh <- struct{}{} + }() <-doneCh } diff --git a/examples/websocket/trades/trades.go b/examples/websocket/trades/trades.go index ebd9fd0..03ea197 100644 --- a/examples/websocket/trades/trades.go +++ b/examples/websocket/trades/trades.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "time" binance_connector "github.com/binance/binance-connector-go" ) @@ -18,10 +19,15 @@ func WsTradeExample() { errHandler := func(err error) { fmt.Println(err) } - doneCh, _, err := websocketStreamClient.WsTradeServe("LTCBTC", wsTradeHandler, errHandler) + doneCh, stopCh, err := websocketStreamClient.WsTradeServe("LTCBTC", wsTradeHandler, errHandler) if err != nil { fmt.Println(err) return } + // use stopCh to exit + go func() { + time.Sleep(10 * time.Second) + stopCh <- struct{}{} + }() <-doneCh } diff --git a/fiat.go b/fiat.go new file mode 100644 index 0000000..7ed9fd9 --- /dev/null +++ b/fiat.go @@ -0,0 +1,183 @@ +package binance_connector + +import ( + "context" + "encoding/json" + "net/http" +) + +const ( + fiatDepositWithdrawHistory = "/sapi/v1/fiat/orders" +) + +type GetFiatDepositWithdrawHistoryService struct { + c *Client + transactionType *string + beginTime *uint64 + endTime *uint64 + page *int + rows *int +} + +func (s *GetFiatDepositWithdrawHistoryService) TransactionType(transactionType string) *GetFiatDepositWithdrawHistoryService { + s.transactionType = &transactionType + return s +} + +func (s *GetFiatDepositWithdrawHistoryService) BeginTime(beginTime uint64) *GetFiatDepositWithdrawHistoryService { + s.beginTime = &beginTime + return s +} + +func (s *GetFiatDepositWithdrawHistoryService) EndTime(endTime uint64) *GetFiatDepositWithdrawHistoryService { + s.endTime = &endTime + return s +} + +func (s *GetFiatDepositWithdrawHistoryService) Page(page int) *GetFiatDepositWithdrawHistoryService { + s.page = &page + return s +} + +func (s *GetFiatDepositWithdrawHistoryService) Rows(rows int) *GetFiatDepositWithdrawHistoryService { + s.rows = &rows + return s +} + +func (s *GetFiatDepositWithdrawHistoryService) Do(ctx context.Context) (res *GetFiatDepositWithdrawHistoryResponse, err error) { + r := &request{ + method: http.MethodGet, + endpoint: fiatDepositWithdrawHistory, + secType: secTypeSigned, + } + r.setParam("transactionType", *s.transactionType) + if s.beginTime != nil { + r.setParam("beginTime", *s.beginTime) + } + if s.endTime != nil { + r.setParam("endTime", *s.endTime) + } + if s.page != nil { + r.setParam("page", *s.page) + } + if s.rows != nil { + r.setParam("rows", *s.rows) + } + data, err := s.c.callAPI(ctx, r) + if err != nil { + return nil, err + } + res = new(GetFiatDepositWithdrawHistoryResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +type GetFiatDepositWithdrawHistoryResponse struct { + Code string `json:"code"` + Message string `json:"message"` + Data []struct { + OrderNo string `json:"orderNo"` + FiatCurrency string `json:"fiatCurrency"` + IndicatedAmount string `json:"indicatedAmount"` + Amount string `json:"amount"` + TotalFee string `json:"totalFee"` + Method string `json:"method"` + Status string `json:"status"` + CreateTime uint64 `json:"createTime"` + UpdateTime uint64 `json:"updateTime"` + } + Total int64 `json:"total"` + Success bool `json:"success"` +} + +const ( + fiatPaymentHistory = "/sapi/v1/fiat/payments" +) + +type GetFiatPaymentHistoryService struct { + c *Client + transactionType *string + beginTime *uint64 + endTime *uint64 + page *int + rows *int +} + +func (s *GetFiatPaymentHistoryService) TransactionType(transactionType string) *GetFiatPaymentHistoryService { + s.transactionType = &transactionType + return s +} + +func (s *GetFiatPaymentHistoryService) BeginTime(beginTime uint64) *GetFiatPaymentHistoryService { + s.beginTime = &beginTime + return s +} + +func (s *GetFiatPaymentHistoryService) EndTime(endTime uint64) *GetFiatPaymentHistoryService { + s.endTime = &endTime + return s +} + +func (s *GetFiatPaymentHistoryService) Page(page int) *GetFiatPaymentHistoryService { + s.page = &page + return s +} + +func (s *GetFiatPaymentHistoryService) Rows(rows int) *GetFiatPaymentHistoryService { + s.rows = &rows + return s +} + +func (s *GetFiatPaymentHistoryService) Do(ctx context.Context) (res *GetFiatPaymentHistoryResponse, err error) { + r := &request{ + method: http.MethodGet, + endpoint: fiatPaymentHistory, + secType: secTypeSigned, + } + r.setParam("transactionType", *s.transactionType) + if s.beginTime != nil { + r.setParam("beginTime", *s.beginTime) + } + if s.endTime != nil { + r.setParam("endTime", *s.endTime) + } + if s.page != nil { + r.setParam("page", *s.page) + } + if s.rows != nil { + r.setParam("rows", *s.rows) + } + data, err := s.c.callAPI(ctx, r) + if err != nil { + return nil, err + } + res = new(GetFiatPaymentHistoryResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +type GetFiatPaymentHistoryResponse struct { + Code string `json:"code"` + Message string `json:"message"` + Data []struct { + OrderNo string `json:"orderNo"` + SourceAmount string `json:"sourceAmount"` + FiatCurrency string `json:"fiatCurrency"` + ObtainAmount string `json:"obtainAmount"` + CryptoCurrency string `json:"cryptoCurrency"` + TotalFee string `json:"totalFee"` + Price string `json:"price"` + Status string `json:"status"` + PaymentMethod string `json:"paymentMethod"` + CreateTime uint64 `json:"createTime"` + UpdateTime uint64 `json:"updateTime"` + } + Total int64 `json:"total"` + Success bool `json:"success"` +} diff --git a/fiat_test.go b/fiat_test.go new file mode 100644 index 0000000..2bd8516 --- /dev/null +++ b/fiat_test.go @@ -0,0 +1,117 @@ +package binance_connector + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type fiatTestSuite struct { + baseTestSuite +} + +func TestFiat(t *testing.T) { + suite.Run(t, new(fiatTestSuite)) +} + +func (s *fiatTestSuite) TestGetFiatDepositWithdrawHistory() { + data := []byte(`{ + "code": "000000", + "message": "success", + "data": [ + { + "orderNo":"7d76d611-0568-4f43-afb6-24cac7767365", + "fiatCurrency": "BRL", + "indicatedAmount": "10.00", + "amount": "10.00", + "totalFee": "0.00", + "method": "BankAccount", + "status": "Expired", + "createTime": 1626144956000, + "updateTime": 1626400907000 + } + ], + "total": 1, + "success": true + }`) + s.mockDo(data, nil) + defer s.assertDo() + + transactionType := "0" + s.assertReq(func(r *request) { + e := newSignedRequest().setParams(params{ + "transactionType": transactionType, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewGetFiatDepositWithdrawHistoryService().TransactionType(transactionType).Do(newContext()) + s.r().NoError(err) + s.Equal("000000", res.Code) + s.Equal("success", res.Message) + s.r().Len(res.Data, 1) + s.Equal("7d76d611-0568-4f43-afb6-24cac7767365", res.Data[0].OrderNo) + s.Equal("BRL", res.Data[0].FiatCurrency) + s.Equal("10.00", res.Data[0].IndicatedAmount) + s.Equal("10.00", res.Data[0].Amount) + s.Equal("0.00", res.Data[0].TotalFee) + s.Equal("BankAccount", res.Data[0].Method) + s.Equal("Expired", res.Data[0].Status) + s.Equal(uint64(1626144956000), res.Data[0].CreateTime) + s.Equal(uint64(1626400907000), res.Data[0].UpdateTime) + s.Equal(int64(1), res.Total) + s.True(res.Success) +} + +func (s *fiatTestSuite) TestGetPaymentFiatHistory() { + data := []byte(`{ + "code": "000000", + "message": "success", + "data": [ + { + "orderNo": "353fca443f06466db0c4dc89f94f027a", + "sourceAmount": "20.0", + "fiatCurrency": "EUR", + "obtainAmount": "4.462", + "cryptoCurrency": "LUNA", + "totalFee": "0.2", + "price": "4.437472", + "status": "Failed", + "paymentMethod": "Credit Card", + "createTime": 1624529919000, + "updateTime": 1624529919000 + } + ], + "total": 1, + "success": true + }`) + s.mockDo(data, nil) + defer s.assertDo() + + transactionType := "0" + s.assertReq(func(r *request) { + e := newSignedRequest().setParams(params{ + "transactionType": transactionType, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewGetFiatPaymentHistoryService().TransactionType(transactionType).Do(newContext()) + s.r().NoError(err) + s.Equal("000000", res.Code) + s.Equal("success", res.Message) + s.r().Len(res.Data, 1) + s.Equal("353fca443f06466db0c4dc89f94f027a", res.Data[0].OrderNo) + s.Equal("20.0", res.Data[0].SourceAmount) + s.Equal("EUR", res.Data[0].FiatCurrency) + s.Equal("4.462", res.Data[0].ObtainAmount) + s.Equal("LUNA", res.Data[0].CryptoCurrency) + s.Equal("0.2", res.Data[0].TotalFee) + s.Equal("4.437472", res.Data[0].Price) + s.Equal("Failed", res.Data[0].Status) + s.Equal("Credit Card", res.Data[0].PaymentMethod) + s.Equal(uint64(1624529919000), res.Data[0].CreateTime) + s.Equal(uint64(1624529919000), res.Data[0].UpdateTime) + s.Equal(int64(1), res.Total) + s.True(res.Success) +} diff --git a/market.go b/market.go index 5d221ed..01598a3 100644 --- a/market.go +++ b/market.go @@ -103,34 +103,53 @@ type ExchangeFilter struct { // Symbol define symbol type SymbolInfo struct { - Symbol string `json:"symbol"` - Status string `json:"status"` - BaseAsset string `json:"baseAsset"` - BaseAssetPrecision int64 `json:"baseAssetPrecision"` - QuoteAsset string `json:"quoteAsset"` - QuotePrecision int64 `json:"quotePrecision"` - OrderTypes []string `json:"orderTypes"` - IcebergAllowed bool `json:"icebergAllowed"` - OcoAllowed bool `json:"ocoAllowed"` - QuoteOrderQtyMarketAllowed bool `json:"quoteOrderQtyMarketAllowed"` - IsSpotTradingAllowed bool `json:"isSpotTradingAllowed"` - IsMarginTradingAllowed bool `json:"isMarginTradingAllowed"` - Filters []*SymbolFilter `json:"filters"` - Permissions []string `json:"permissions"` + Symbol string `json:"symbol"` + Status string `json:"status"` + BaseAsset string `json:"baseAsset"` + BaseAssetPrecision int64 `json:"baseAssetPrecision"` + QuoteAsset string `json:"quoteAsset"` + QuotePrecision int64 `json:"quotePrecision"` + QuoteAssetPrecision int64 `json:"quoteAssetPrecision"` + OrderTypes []string `json:"orderTypes"` + IcebergAllowed bool `json:"icebergAllowed"` + OcoAllowed bool `json:"ocoAllowed"` + QuoteOrderQtyMarketAllowed bool `json:"quoteOrderQtyMarketAllowed"` + AllowTrailingStop bool `json:"allowTrailingStop"` + CancelReplaceAllowed bool `json:"cancelReplaceAllowed"` + IsSpotTradingAllowed bool `json:"isSpotTradingAllowed"` + IsMarginTradingAllowed bool `json:"isMarginTradingAllowed"` + Filters []*SymbolFilter `json:"filters"` + Permissions []string `json:"permissions"` + PermissionSets [][]string `json:"permissionSets"` + DefaultSelfTradePreventionMode string `json:"defaultSelfTradePreventionMode"` + AllowedSelfTradePreventionModes []string `json:"allowedSelfTradePreventionModes"` } // SymbolFilter define symbol filter type SymbolFilter struct { - FilterType string `json:"filterType"` - MinPrice string `json:"minPrice"` - MaxPrice string `json:"maxPrice"` - TickSize string `json:"tickSize"` - MinQty string `json:"minQty"` - MaxQty string `json:"maxQty"` - StepSize string `json:"stepSize"` - MinNotional string `json:"minNotional"` - Limit uint `json:"limit"` - MaxNumAlgoOrders int64 `json:"maxNumAlgoOrders"` + ApplyMinToMarket bool `json:"applyMinToMarket"` + ApplyMaxToMarket bool `json:"applyMaxToMarket"` + AskMultiplierDown string `json:"askMultiplierDown"` + AskMultiplierUp string `json:"askMultiplierUp"` + AvgPriceMins int64 `json:"avgPriceMins"` + BidMultiplierDown string `json:"bidMultiplierDown"` + BidMultiplierUp string `json:"bidMultiplierUp"` + FilterType string `json:"filterType"` + Limit uint `json:"limit"` + MaxNotional string `json:"maxNotional"` + MaxNumAlgoOrders int64 `json:"maxNumAlgoOrders"` + MaxNumOrders int64 `json:"maxNumOrders"` + MaxPrice string `json:"maxPrice"` + MaxQty string `json:"maxQty"` + MaxTrailingAboveDelta int64 `json:"maxTrailingAboveDelta"` + MaxTrailingBelowDelta int64 `json:"maxTrailingBelowDelta"` + MinNotional string `json:"minNotional"` + MinPrice string `json:"minPrice"` + MinQty string `json:"minQty"` + MinTrailingAboveDelta int64 `json:"minTrailingAboveDelta"` + MinTrailingBelowDelta int64 `json:"minTrailingBelowDelta"` + StepSize string `json:"stepSize"` + TickSize string `json:"tickSize"` } // Binance Order Book endpoint (GET /api/v3/depth) @@ -659,7 +678,7 @@ func (s *Ticker24hr) Symbols(symbols []string) *Ticker24hr { } // Send the request -func (s *Ticker24hr) Do(ctx context.Context, opts ...RequestOption) (res *Ticker24hrResponse, err error) { +func (s *Ticker24hr) Do(ctx context.Context, opts ...RequestOption) (res []*Ticker24hrResponse, err error) { r := &request{ method: http.MethodGet, endpoint: "/api/v3/ticker/24hr", @@ -669,16 +688,33 @@ func (s *Ticker24hr) Do(ctx context.Context, opts ...RequestOption) (res *Ticker r.setParam("symbol", *s.symbol) } if s.symbols != nil { - r.setParam("symbols", *s.symbols) + s, _ := json.Marshal(s.symbols) + r.setParam("symbols", string(s)) } data, err := s.c.callAPI(ctx, r, opts...) if err != nil { - return nil, err + return []*Ticker24hrResponse{}, err } - res = new(Ticker24hrResponse) - err = json.Unmarshal(data, res) + var raw json.RawMessage + err = json.Unmarshal(data, &raw) if err != nil { - return nil, err + return []*Ticker24hrResponse{}, err + } + + if raw[0] == '[' { + res = make([]*Ticker24hrResponse, 0) + err = json.Unmarshal(data, &res) + if err != nil { + return []*Ticker24hrResponse{}, err + } + } else { + // The response is a single object, not an array, make sure to add it to the slice + singleRes := new(Ticker24hrResponse) + err = json.Unmarshal(data, &singleRes) + if err != nil { + return []*Ticker24hrResponse{}, err + } + res = append(res, singleRes) } return res, nil } diff --git a/market_test.go b/market_test.go index af0af5c..4352048 100644 --- a/market_test.go +++ b/market_test.go @@ -303,7 +303,7 @@ func (s *marketTestSuite) assertAvgPrice(e, a *AvgPriceResponse) { } func (s *marketTestSuite) Test24hrTicker() { - data := []byte(`{ + data := []byte(`[{ "symbol": "BNBBTC", "priceChange": "-94.99999800", "priceChangePercent": "-95.960", @@ -322,7 +322,7 @@ func (s *marketTestSuite) Test24hrTicker() { "firstId": 28385, "lastId": 28460, "count": 76 - }`) + }]`) s.mockDo(data, nil) defer s.assertDo() @@ -334,27 +334,31 @@ func (s *marketTestSuite) Test24hrTicker() { stats, err := s.client.NewTicker24hrService().Symbol(symbol).Do(newContext()) r := s.r() r.NoError(err) - e := &Ticker24hrResponse{ - Symbol: "BNBBTC", - PriceChange: "-94.99999800", - PriceChangePercent: "-95.960", - WeightedAvgPrice: "0.29628482", - PrevClosePrice: "0.10002000", - LastPrice: "4.00000200", - LastQty: "200.00000000", - BidPrice: "4.00000000", - AskPrice: "4.00000200", - OpenPrice: "99.00000000", - HighPrice: "100.00000000", - LowPrice: "0.10000000", - Volume: "8913.30000000", - OpenTime: 1499783499040, - CloseTime: 1499869899040, - FirstId: 28385, - LastId: 28460, - Count: 76, + e := []*Ticker24hrResponse{ + { + Symbol: "BNBBTC", + PriceChange: "-94.99999800", + PriceChangePercent: "-95.960", + WeightedAvgPrice: "0.29628482", + PrevClosePrice: "0.10002000", + LastPrice: "4.00000200", + LastQty: "200.00000000", + BidPrice: "4.00000000", + AskPrice: "4.00000200", + OpenPrice: "99.00000000", + HighPrice: "100.00000000", + LowPrice: "0.10000000", + Volume: "8913.30000000", + OpenTime: 1499783499040, + CloseTime: 1499869899040, + FirstId: 28385, + LastId: 28460, + Count: 76, + }, + } + for i := 0; i < len(stats); i++ { + s.assertPriceChangeStatsEqual(e[i], stats[i]) } - s.assertPriceChangeStatsEqual(e, stats) } func (s *marketTestSuite) assertPriceChangeStatsEqual(e, a *Ticker24hrResponse) { diff --git a/websocket.go b/websocket.go index cb839c3..99cc9d2 100644 --- a/websocket.go +++ b/websocket.go @@ -79,21 +79,26 @@ var wsServe = func(cfg *WsConfig, handler WsHandler, errHandler ErrHandler) (don // operation. silent := false go func() { - select { - case <-stopCh: - silent = true - case <-doneCh: + for { + _, message, err := c.ReadMessage() + if err != nil { + if !silent { + errHandler(err) + } + stopCh <- struct{}{} + return + } + handler(message) } }() + for { - _, message, err := c.ReadMessage() - if err != nil { - if !silent { - errHandler(err) - } + select { + case <-stopCh: + silent = true return + case <-doneCh: } - handler(message) } }() return diff --git a/websocket_api_userdata.go b/websocket_api_userdata.go index 9a5349f..831fc35 100644 --- a/websocket_api_userdata.go +++ b/websocket_api_userdata.go @@ -130,7 +130,7 @@ func (s *StopUserDataStreamService) Do(ctx context.Context) (*StopUserDataStream payload := map[string]interface{}{ "id": id, - "method": "userDataStream.close", + "method": "userDataStream.stop", "params": parameters, } diff --git a/websocket_service.go b/websocket_service.go index 90154ac..013f224 100644 --- a/websocket_service.go +++ b/websocket_service.go @@ -758,6 +758,28 @@ func (c *WebsocketStreamClient) WsAllMarketMiniTickersStatServe(handler WsAllMar // WsAllMarketMiniTickersStatEvent define array of websocket market mini-ticker statistics events type WsAllMarketMiniTickersStatEvent []*WsMarketMiniStatEvent +// WsMarketMiniTickersStatHandler handle websocket that push single market statistics for 24hr +type WsMarketMiniTickersStatHandler func(event WsMarketMiniTickerStatEvent) + +// WsMarketMiniTickersStatServe serve websocket that push mini version of 24hr statistics for single market every second +func (c *WebsocketStreamClient) WsMarketMiniTickersStatServe(symbol string, handler WsMarketMiniTickersStatHandler, errHandler ErrHandler) (doneCh, stopCh chan struct{}, err error) { + endpoint := fmt.Sprintf("%s/%s@miniTicker", c.Endpoint, strings.ToLower(symbol)) + cfg := newWsConfig(endpoint) + wsHandler := func(message []byte) { + var event WsMarketMiniTickerStatEvent + err := json.Unmarshal(message, &event) + if err != nil { + errHandler(err) + return + } + handler(event) + } + return wsServe(cfg, wsHandler, errHandler) +} + +// WsMarketMiniTickerStatEvent define array of websocket market mini-ticker statistics events +type WsMarketMiniTickerStatEvent *WsMarketMiniStatEvent + // WsMarketMiniStatEvent define websocket market mini-ticker statistics event type WsMarketMiniStatEvent struct { Event string `json:"e"` diff --git a/websocket_service_test.go b/websocket_service_test.go index a9d337d..4893869 100644 --- a/websocket_service_test.go +++ b/websocket_service_test.go @@ -293,11 +293,49 @@ func (s *websocketTestSuite) assertWsMarketMiniTickersStatEventEqual(e, a *WsMar r.Equal(e.QuoteVolume, a.QuoteVolume, "QuoteVolume") } +func (s *websocketTestSuite) TestWsMarketMiniTickersStatServe() { + websocketStreamClient := NewWebsocketStreamClient(false, "wss://stream.testnet.binance.vision") + data := []byte(`{ + "e": "24hrMiniTicker", + "E": 1523658017154, + "s": "BNBBTC", + "c": "0.00175640", + "o": "0.00161200", + "h": "0.00176000", + "l": "0.00159370", + "v": "3479863.89000000", + "q": "5725.90587704" + }`) + fakeErrMsg := "fake error" + s.mockWsServe(data, errors.New(fakeErrMsg)) + defer s.assertWsServe() + + doneC, stopC, err := websocketStreamClient.WsMarketMiniTickersStatServe("BNBBTC", func(event WsMarketMiniTickerStatEvent) { + e := &WsMarketMiniStatEvent{ + Event: "24hrMiniTicker", + Time: 1523658017154, + Symbol: "BNBBTC", + LastPrice: "0.00175640", + OpenPrice: "0.00161200", + HighPrice: "0.00176000", + LowPrice: "0.00159370", + BaseVolume: "3479863.89000000", + QuoteVolume: "5725.90587704", + } + s.assertWsMarketMiniTickersStatEventEqual(e, event) + }, func(err error) { + s.r().EqualError(err, fakeErrMsg) + }) + s.r().NoError(err) + stopC <- struct{}{} + <-doneC +} + func (s *websocketTestSuite) TestBookTickerServe() { websocketStreamClient := NewWebsocketStreamClient(false, "wss://stream.testnet.binance.vision") data := []byte(`{ "u":17242169, - "s":"BTCUSD_200626", + "s":"BNBUSDT", "b":"9548.1", "B":"52", "a":"9548.5", @@ -307,10 +345,10 @@ func (s *websocketTestSuite) TestBookTickerServe() { s.mockWsServe(data, errors.New(fakeErrMsg)) defer s.assertWsServe() - doneC, stopC, err := websocketStreamClient.WsBookTickerServe("BTCUSD_200626", func(event *WsBookTickerEvent) { + doneC, stopC, err := websocketStreamClient.WsBookTickerServe("BNBUSDT", func(event *WsBookTickerEvent) { e := &WsBookTickerEvent{ UpdateID: 17242169, - Symbol: "BTCUSD_200626", + Symbol: "BNBUSDT", BestBidPrice: "9548.1", BestBidQty: "52", BestAskPrice: "9548.5",