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/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/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_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 32a6e6f..013f224 100644 --- a/websocket_service.go +++ b/websocket_service.go @@ -758,19 +758,6 @@ func (c *WebsocketStreamClient) WsAllMarketMiniTickersStatServe(handler WsAllMar // WsAllMarketMiniTickersStatEvent define array of websocket market mini-ticker statistics events type WsAllMarketMiniTickersStatEvent []*WsMarketMiniStatEvent -// WsMarketMiniStatEvent define websocket market mini-ticker statistics event -type WsMarketMiniStatEvent struct { - Event string `json:"e"` - Time int64 `json:"E"` - Symbol string `json:"s"` - LastPrice string `json:"c"` - OpenPrice string `json:"o"` - HighPrice string `json:"h"` - LowPrice string `json:"l"` - BaseVolume string `json:"v"` - QuoteVolume string `json:"q"` -} - // WsMarketMiniTickersStatHandler handle websocket that push single market statistics for 24hr type WsMarketMiniTickersStatHandler func(event WsMarketMiniTickerStatEvent) @@ -793,6 +780,19 @@ func (c *WebsocketStreamClient) WsMarketMiniTickersStatServe(symbol string, hand // 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"` + Time int64 `json:"E"` + Symbol string `json:"s"` + LastPrice string `json:"c"` + OpenPrice string `json:"o"` + HighPrice string `json:"h"` + LowPrice string `json:"l"` + BaseVolume string `json:"v"` + QuoteVolume string `json:"q"` +} + // WsBookTickerEvent define websocket best book ticker event. type WsBookTickerEvent struct { UpdateID int64 `json:"u"` 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",