From 576f3e847a2b3f149709fd1dce8f67018176d55e Mon Sep 17 00:00:00 2001 From: Haiss2 Date: Fri, 13 Oct 2023 15:47:14 +0700 Subject: [PATCH] sync_adshao_go_binance --- .github/workflows/review.yml | 22 ++ v2/account_service.go | 30 ++- v2/account_service_test.go | 26 +- v2/client.go | 35 ++- v2/delivery/ticker_service.go | 2 +- v2/delivery/ticker_service_test.go | 12 +- v2/exchange_info_service.go | 37 +++ v2/exchange_info_service_test.go | 26 +- v2/futures/account_service.go | 11 +- v2/futures/account_service_test.go | 32 ++- v2/futures/client.go | 23 +- v2/futures/continuous_kline_service.go | 120 +++++++++ v2/futures/continuous_kline_service_test.go | 116 +++++++++ v2/futures/index_price_kline_service_test.go | 12 +- v2/futures/order_service.go | 4 +- v2/futures/position_risk.go | 1 + v2/futures/position_risk_test.go | 3 + v2/futures/position_service.go | 67 ++++- v2/futures/position_service_test.go | 32 +++ v2/futures/premium_index_kline_service.go | 92 +++++++ .../premium_index_kline_service_test.go | 103 ++++++++ v2/futures/ticker_service.go | 2 +- v2/futures/ticker_service_test.go | 6 +- v2/futures/trade_service.go | 10 + v2/futures/websocket_service.go | 147 +++++++++++ v2/futures/websocket_service_test.go | 244 ++++++++++++++++++ v2/go.mod | 4 +- v2/order_service.go | 2 +- v2/pay_service.go | 27 ++ v2/subaccount_service.go | 225 ++++++++++++++++ v2/subaccount_service_test.go | 193 ++++++++++++++ v2/ticker_service.go | 19 +- v2/ticker_service_test.go | 112 +++++++- 33 files changed, 1720 insertions(+), 77 deletions(-) create mode 100644 .github/workflows/review.yml create mode 100644 v2/futures/continuous_kline_service.go create mode 100644 v2/futures/continuous_kline_service_test.go create mode 100644 v2/futures/premium_index_kline_service.go create mode 100644 v2/futures/premium_index_kline_service_test.go diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml new file mode 100644 index 00000000..19253f56 --- /dev/null +++ b/.github/workflows/review.yml @@ -0,0 +1,22 @@ +name: Code Review + +on: + issue_comment: + types: [created, edited] + +jobs: + code-review: + if: | + github.event_name == 'pull_request' || + (github.event.comment.user.login == 'adshao' && + startsWith(github.event.comment.body, 'chatgpt')) + runs-on: ubuntu-latest + steps: + - name: OpenAI ChatGPT Code Review + uses: adshao/chatgpt-code-review-action@v0.2.5 + with: + PROGRAMMING_LANGUAGE: 'Go' + REVIEW_COMMENT_PREFIX: 'chatgpt:' + FULL_REVIEW_COMMENT: 'chatgpt' + OPENAI_TOKEN: ${{ secrets.OPENAI_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/v2/account_service.go b/v2/account_service.go index 34103274..4959337d 100644 --- a/v2/account_service.go +++ b/v2/account_service.go @@ -31,17 +31,18 @@ func (s *GetAccountService) Do(ctx context.Context, opts ...RequestOption) (res // Account define account info type Account struct { - MakerCommission int64 `json:"makerCommission"` - TakerCommission int64 `json:"takerCommission"` - BuyerCommission int64 `json:"buyerCommission"` - SellerCommission int64 `json:"sellerCommission"` - CanTrade bool `json:"canTrade"` - CanWithdraw bool `json:"canWithdraw"` - CanDeposit bool `json:"canDeposit"` - UpdateTime uint64 `json:"updateTime"` - AccountType string `json:"accountType"` - Balances []Balance `json:"balances"` - Permissions []string `json:"permissions"` + MakerCommission int64 `json:"makerCommission"` + TakerCommission int64 `json:"takerCommission"` + BuyerCommission int64 `json:"buyerCommission"` + SellerCommission int64 `json:"sellerCommission"` + CommissionRates CommissionRates `json:"commissionRates"` + CanTrade bool `json:"canTrade"` + CanWithdraw bool `json:"canWithdraw"` + CanDeposit bool `json:"canDeposit"` + UpdateTime uint64 `json:"updateTime"` + AccountType string `json:"accountType"` + Balances []Balance `json:"balances"` + Permissions []string `json:"permissions"` } // Balance define user balance of your account @@ -60,6 +61,13 @@ type GetAccountSnapshotService struct { limit *int } +type CommissionRates struct { + Maker string `json:"maker"` + Taker string `json:"taker"` + Buyer string `json:"buyer"` + Seller string `json:"seller"` +} + // Type set account type ("SPOT", "MARGIN", "FUTURES") func (s *GetAccountSnapshotService) Type(accountType string) *GetAccountSnapshotService { s.accountType = accountType diff --git a/v2/account_service_test.go b/v2/account_service_test.go index 0e641866..bfef7074 100644 --- a/v2/account_service_test.go +++ b/v2/account_service_test.go @@ -20,6 +20,12 @@ func (s *accountServiceTestSuite) TestGetAccount() { "takerCommission": 15, "buyerCommission": 0, "sellerCommission": 0, + "commissionRates": { + "maker": "0.00150000", + "taker": "0.00150000", + "buyer": "0.00000000", + "seller": "0.00000000" + }, "canTrade": true, "canWithdraw": true, "canDeposit": true, @@ -55,11 +61,17 @@ func (s *accountServiceTestSuite) TestGetAccount() { TakerCommission: 15, BuyerCommission: 0, SellerCommission: 0, - CanTrade: true, - CanWithdraw: true, - CanDeposit: true, - UpdateTime: 123456789, - AccountType: "SPOT", + CommissionRates: CommissionRates{ + Maker: "0.00150000", + Taker: "0.00150000", + Buyer: "0.00000000", + Seller: "0.00000000", + }, + CanTrade: true, + CanWithdraw: true, + CanDeposit: true, + UpdateTime: 123456789, + AccountType: "SPOT", Balances: []Balance{ { Asset: "BTC", @@ -83,6 +95,10 @@ func (s *accountServiceTestSuite) assertAccountEqual(e, a *Account) { r.Equal(e.TakerCommission, a.TakerCommission, "TakerCommission") r.Equal(e.BuyerCommission, a.BuyerCommission, "BuyerCommission") r.Equal(e.SellerCommission, a.SellerCommission, "SellerCommission") + r.Equal(e.CommissionRates.Maker, a.CommissionRates.Maker, "CommissionRates.Maker") + r.Equal(e.CommissionRates.Taker, a.CommissionRates.Taker, "CommissionRates.Taker") + r.Equal(e.CommissionRates.Buyer, a.CommissionRates.Buyer, "CommissionRates.Buyer") + r.Equal(e.CommissionRates.Seller, a.CommissionRates.Seller, "CommissionRates.Seller") r.Equal(e.CanTrade, a.CanTrade, "CanTrade") r.Equal(e.CanWithdraw, a.CanWithdraw, "CanWithdraw") r.Equal(e.CanDeposit, a.CanDeposit, "CanDeposit") diff --git a/v2/client.go b/v2/client.go index 04c94fb9..aba78acf 100644 --- a/v2/client.go +++ b/v2/client.go @@ -14,11 +14,12 @@ import ( "os" "time" + "github.com/bitly/go-simplejson" + jsoniter "github.com/json-iterator/go" + "github.com/adshao/go-binance/v2/common" "github.com/adshao/go-binance/v2/delivery" "github.com/adshao/go-binance/v2/futures" - "github.com/bitly/go-simplejson" - jsoniter "github.com/json-iterator/go" ) // SideType define side type of order @@ -151,10 +152,12 @@ const ( SymbolStatusTypeAuctionMatch SymbolStatusType = "AUCTION_MATCH" SymbolStatusTypeBreak SymbolStatusType = "BREAK" - SymbolFilterTypeLotSize SymbolFilterType = "LOT_SIZE" - SymbolFilterTypePriceFilter SymbolFilterType = "PRICE_FILTER" - SymbolFilterTypePercentPrice SymbolFilterType = "PERCENT_PRICE" + SymbolFilterTypeLotSize SymbolFilterType = "LOT_SIZE" + SymbolFilterTypePriceFilter SymbolFilterType = "PRICE_FILTER" + SymbolFilterTypePercentPrice SymbolFilterType = "PERCENT_PRICE" + // Deprecated: use SymbolFilterTypePercentPrice instead SymbolFilterTypeMinNotional SymbolFilterType = "MIN_NOTIONAL" + SymbolFilterTypeNotional SymbolFilterType = "NOTIONAL" SymbolFilterTypeIcebergParts SymbolFilterType = "ICEBERG_PARTS" SymbolFilterTypeMarketLotSize SymbolFilterType = "MARKET_LOT_SIZE" SymbolFilterTypeMaxNumAlgoOrders SymbolFilterType = "MAX_NUM_ALGO_ORDERS" @@ -987,7 +990,27 @@ func (c *Client) NewGetUserAsset() *GetUserAssetService { return &GetUserAssetService{c: c} } -// NewSubTransferHistoryService query transfer histroy (for sub-account) +// NewManagedSubAccountDepositService Deposit Assets Into The Managed Sub-account(For Investor Master Account) +func (c *Client) NewManagedSubAccountDepositService() *ManagedSubAccountDepositService { + return &ManagedSubAccountDepositService{c: c} +} + +// NewManagedSubAccountWithdrawalService Withdrawal Assets From The Managed Sub-account(For Investor Master Account) +func (c *Client) NewManagedSubAccountWithdrawalService() *ManagedSubAccountWithdrawalService { + return &ManagedSubAccountWithdrawalService{c: c} +} + +// NewManagedSubAccountAssetsService Withdrawal Assets From The Managed Sub-account(For Investor Master Account) +func (c *Client) NewManagedSubAccountAssetsService() *ManagedSubAccountAssetsService { + return &ManagedSubAccountAssetsService{c: c} +} + +// NewSubAccountFuturesAccountService Get Detail on Sub-account's Futures Account (For Master Account) +func (c *Client) NewSubAccountFuturesAccountService() *SubAccountFuturesAccountService { + return &SubAccountFuturesAccountService{c: c} +} + +// NewSubTransferHistoryService query transfer history (for sub-account) func (c *Client) NewSubTransferHistoryService() *SubTransferHistoryService { return &SubTransferHistoryService{c: c} } diff --git a/v2/delivery/ticker_service.go b/v2/delivery/ticker_service.go index e45182cf..49e169fa 100644 --- a/v2/delivery/ticker_service.go +++ b/v2/delivery/ticker_service.go @@ -171,7 +171,7 @@ type PriceChangeStats struct { BaseVolume string `json:"baseVolume"` OpenTime int64 `json:"openTime"` CloseTime int64 `json:"closeTime"` - FristID int64 `json:"firstId"` + FirstID int64 `json:"firstId"` LastID int64 `json:"lastId"` Count int64 `json:"count"` } diff --git a/v2/delivery/ticker_service_test.go b/v2/delivery/ticker_service_test.go index 886fd51f..f0660607 100644 --- a/v2/delivery/ticker_service_test.go +++ b/v2/delivery/ticker_service_test.go @@ -345,7 +345,7 @@ func (s *tickerServiceTestSuite) TestListPriceChangeStats() { BaseVolume: "138965.76942775", OpenTime: 1623748920000, CloseTime: 1623835355736, - FristID: 172749700, + FirstID: 172749700, LastID: 173464362, Count: 714658, } @@ -364,7 +364,7 @@ func (s *tickerServiceTestSuite) TestListPriceChangeStats() { BaseVolume: "648179.55304919", OpenTime: 1623748920000, CloseTime: 1623835355187, - FristID: 138575549, + FirstID: 138575549, LastID: 139103143, Count: 527595, } @@ -423,7 +423,7 @@ func (s *tickerServiceTestSuite) TestSinglePriceChangeStats() { BaseVolume: "137750.93213717", OpenTime: 1623752520000, CloseTime: 1623838964257, - FristID: 172782522, + FirstID: 172782522, LastID: 173490102, Count: 707576, } @@ -498,7 +498,7 @@ func (s *tickerServiceTestSuite) TestPriceChangeStatsWithPair() { BaseVolume: "8300.32545198", OpenTime: 1623755580000, CloseTime: 1623842010140, - FristID: 7516015, + FirstID: 7516015, LastID: 7591268, Count: 75254, } @@ -517,7 +517,7 @@ func (s *tickerServiceTestSuite) TestPriceChangeStatsWithPair() { BaseVolume: "13637.88521626", OpenTime: 1623755580000, CloseTime: 1623842010656, - FristID: 32157829, + FirstID: 32157829, LastID: 32307537, Count: 149709, } @@ -541,7 +541,7 @@ func (s *tickerServiceTestSuite) assertPriceChangeStatsEqual(e, a *PriceChangeSt r.Equal(e.BaseVolume, a.BaseVolume, "BaseVolume") r.Equal(e.OpenTime, a.OpenTime, "OpenTime") r.Equal(e.CloseTime, a.CloseTime, "CloseTime") - r.Equal(e.FristID, a.FristID, "FristID") + r.Equal(e.FirstID, a.FirstID, "FirstID") r.Equal(e.LastID, a.LastID, "LastID") r.Equal(e.Count, a.Count, "Count") } diff --git a/v2/exchange_info_service.go b/v2/exchange_info_service.go index bbafc9a5..8744789d 100644 --- a/v2/exchange_info_service.go +++ b/v2/exchange_info_service.go @@ -124,12 +124,22 @@ type PercentPriceFilter struct { } // MinNotionalFilter define min notional filter of symbol +// Deprecated: use NotionalFilter instead type MinNotionalFilter struct { MinNotional string `json:"minNotional"` AveragePriceMins int `json:"avgPriceMins"` ApplyToMarket bool `json:"applyToMarket"` } +// NotionalFilter define notional filter of symbol +type NotionalFilter struct { + MinNotional string `json:"minNotional"` + ApplyMinToMarket bool `json:"applyMinToMarket"` + MaxNotional string `json:"maxNotional"` + ApplyMaxToMarket bool `json:"applyMaxToMarket"` + AvgPriceMins int `json:"avgPriceMins"` +} + // IcebergPartsFilter define iceberg part filter of symbol type IcebergPartsFilter struct { Limit int `json:"limit"` @@ -208,6 +218,7 @@ func (s *Symbol) PercentPriceFilter() *PercentPriceFilter { } // MinNotionalFilter return min notional filter of symbol +// Deprecated: use NotionalFilter instead func (s *Symbol) MinNotionalFilter() *MinNotionalFilter { for _, filter := range s.Filters { if filter["filterType"].(string) == string(SymbolFilterTypeMinNotional) { @@ -227,6 +238,32 @@ func (s *Symbol) MinNotionalFilter() *MinNotionalFilter { return nil } +// NotionalFilter return notional filter of symbol +func (s *Symbol) NotionalFilter() *NotionalFilter { + for _, filter := range s.Filters { + if filter["filterType"].(string) == string(SymbolFilterTypeNotional) { + f := &NotionalFilter{} + if i, ok := filter["minNotional"]; ok { + f.MinNotional = i.(string) + } + if i, ok := filter["applyMinToMarket"]; ok { + f.ApplyMinToMarket = i.(bool) + } + if i, ok := filter["maxNotional"]; ok { + f.MaxNotional = i.(string) + } + if i, ok := filter["applyMaxToMarket"]; ok { + f.ApplyMaxToMarket = i.(bool) + } + if i, ok := filter["avgPriceMins"]; ok { + f.AvgPriceMins = int(i.(float64)) + } + return f + } + } + return nil +} + // IcebergPartsFilter return iceberg part filter of symbol func (s *Symbol) IcebergPartsFilter() *IcebergPartsFilter { for _, filter := range s.Filters { diff --git a/v2/exchange_info_service_test.go b/v2/exchange_info_service_test.go index 75e8a2ce..e3cb45e3 100644 --- a/v2/exchange_info_service_test.go +++ b/v2/exchange_info_service_test.go @@ -52,7 +52,7 @@ func (s *exchangeInfoServiceTestSuite) TestExchangeInfo() { "ocoAllowed": true, "isSpotTradingAllowed": true, "isMarginTradingAllowed": false, - "filters":[{"filterType":"PRICE_FILTER","minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"},{"filterType":"LOT_SIZE","minQty":"0.00100000","maxQty":"100000.00000000","stepSize":"0.00100000"},{"filterType":"MIN_NOTIONAL","minNotional":"0.00100000"},{"filterType": "MAX_NUM_ALGO_ORDERS", "maxNumAlgoOrders": 5}], + "filters":[{"filterType":"PRICE_FILTER","minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"},{"filterType":"LOT_SIZE","minQty":"0.00100000","maxQty":"100000.00000000","stepSize":"0.00100000"},{"filterType":"NOTIONAL","minNotional":"5.00000000", "applyMinToMarket": true, "maxNotional": "9000000.00000000", "applyMaxToMarket": false, "avgPriceMins": 5},{"filterType": "MAX_NUM_ALGO_ORDERS", "maxNumAlgoOrders": 5}], "permissions": ["SPOT","MARGIN"] } ] @@ -97,7 +97,7 @@ func (s *exchangeInfoServiceTestSuite) TestExchangeInfo() { Filters: []map[string]interface{}{ {"filterType": "PRICE_FILTER", "minPrice": "0.00000100", "maxPrice": "100000.00000000", "tickSize": "0.00000100"}, {"filterType": "LOT_SIZE", "minQty": "0.00100000", "maxQty": "100000.00000000", "stepSize": "0.00100000"}, - {"filterType": "MIN_NOTIONAL", "minNotional": "0.00100000"}, + {"filterType": "NOTIONAL", "minNotional": "5.00000000", "applyMinToMarket": true, "maxNotional": "9000000.00000000", "applyMaxToMarket": false, "avgPriceMins": 5}, {"filterType": "MAX_NUM_ALGO_ORDERS", "maxNumAlgoOrders": 5}, }, Permissions: []string{"SPOT", "MARGIN"}, @@ -118,12 +118,14 @@ func (s *exchangeInfoServiceTestSuite) TestExchangeInfo() { TickSize: "0.00000100", } s.assertPriceFilterEqual(ePriceFilter, res.Symbols[0].PriceFilter()) - eMinNotionalFilter := &MinNotionalFilter{ - MinNotional: "0.00100000", - AveragePriceMins: 0, - ApplyToMarket: false, + eMinNotionalFilter := &NotionalFilter{ + MinNotional: "5.00000000", + ApplyMinToMarket: true, + MaxNotional: "9000000.00000000", + ApplyMaxToMarket: false, + AvgPriceMins: 5, } - s.assertMinNotionalFilterEqual(eMinNotionalFilter, res.Symbols[0].MinNotionalFilter()) + s.assertMinNotionalFilterEqual(eMinNotionalFilter, res.Symbols[0].NotionalFilter()) eMaxNumAlgoOrdersFilter := &MaxNumAlgoOrdersFilter{ MaxNumAlgoOrders: 5, } @@ -167,7 +169,7 @@ func (s *exchangeInfoServiceTestSuite) assertExchangeInfoEqual(e, a *ExchangeInf r.Equal(e.Symbols[i].Filters[fi]["minQty"], currentFilter["minQty"], "minQty") r.Equal(e.Symbols[i].Filters[fi]["maxQty"], currentFilter["maxQty"], "maxQty") r.Equal(e.Symbols[i].Filters[fi]["stepSize"], currentFilter["stepSize"], "stepSize") - case "MIN_NOTIONAL": + case "NOTIONAL": r.Equal(e.Symbols[i].Filters[fi]["minNotional"], currentFilter["minNotional"], "minNotional") } @@ -203,11 +205,13 @@ func (s *exchangeInfoServiceTestSuite) assertPercentPriceFilterEqual(e, a *Perce r.Equal(e.MultiplierDown, a.MultiplierDown, "MultiplierDown") } -func (s *exchangeInfoServiceTestSuite) assertMinNotionalFilterEqual(e, a *MinNotionalFilter) { +func (s *exchangeInfoServiceTestSuite) assertMinNotionalFilterEqual(e, a *NotionalFilter) { r := s.r() r.Equal(e.MinNotional, a.MinNotional, "MinNotional") - r.Equal(e.AveragePriceMins, a.AveragePriceMins, "AveragePriceMins") - r.Equal(e.ApplyToMarket, a.ApplyToMarket, "ApplyToMarket") + r.Equal(e.ApplyMinToMarket, a.ApplyMinToMarket, "ApplyMinToMarket") + r.Equal(e.MaxNotional, a.MaxNotional, "MaxNotional") + r.Equal(e.ApplyMaxToMarket, a.ApplyMaxToMarket, "ApplyMaxToMarket") + r.Equal(e.AvgPriceMins, a.AvgPriceMins, "AvgPriceMins") } func (s *exchangeInfoServiceTestSuite) assertIcebergPartsFilterEqual(e, a *IcebergPartsFilter) { diff --git a/v2/futures/account_service.go b/v2/futures/account_service.go index 63dbd830..6da91c52 100644 --- a/v2/futures/account_service.go +++ b/v2/futures/account_service.go @@ -50,7 +50,7 @@ type GetAccountService struct { func (s *GetAccountService) Do(ctx context.Context, opts ...RequestOption) (res *Account, err error) { r := &request{ method: http.MethodGet, - endpoint: "/fapi/v1/account", + endpoint: "/fapi/v2/account", secType: secTypeSigned, } data, _, err := s.c.callAPI(ctx, r, opts...) @@ -73,6 +73,7 @@ type Account struct { CanDeposit bool `json:"canDeposit"` CanWithdraw bool `json:"canWithdraw"` UpdateTime int64 `json:"updateTime"` + MultiAssetsMargin bool `json:"multiAssetsMargin"` TotalInitialMargin string `json:"totalInitialMargin"` TotalMaintMargin string `json:"totalMaintMargin"` TotalWalletBalance string `json:"totalWalletBalance"` @@ -98,6 +99,11 @@ type AccountAsset struct { PositionInitialMargin string `json:"positionInitialMargin"` UnrealizedProfit string `json:"unrealizedProfit"` WalletBalance string `json:"walletBalance"` + CrossWalletBalance string `json:"crossWalletBalance"` + CrossUnPnl string `json:"crossUnPnl"` + AvailableBalance string `json:"availableBalance"` + MarginAvailable bool `json:"marginAvailable"` + UpdateTime int64 `json:"updateTime"` } // AccountPosition define account position @@ -115,6 +121,7 @@ type AccountPosition struct { PositionSide PositionSideType `json:"positionSide"` PositionAmt string `json:"positionAmt"` Notional string `json:"notional"` - IsolatedWallet string `json:"isolatedWallet"` + BidNotional string `json:"bidNotional"` + AskNotional string `json:"askNotional"` UpdateTime int64 `json:"updateTime"` } diff --git a/v2/futures/account_service_test.go b/v2/futures/account_service_test.go index c51be669..3332de1c 100644 --- a/v2/futures/account_service_test.go +++ b/v2/futures/account_service_test.go @@ -71,7 +71,12 @@ func (s *accountServiceTestSuite) TestGetAccount() { "openOrderInitialMargin": "0.00000000", "positionInitialMargin": "0.33683000", "unrealizedProfit": "-0.44537584", - "walletBalance": "9.19485176" + "walletBalance": "9.19485176", + "crossWalletBalance": "23.72469206", + "crossUnPnl": "0.00000000", + "availableBalance": "126.72469206", + "marginAvailable": true, + "updateTime": 1625474304765 } ], "canDeposit": true, @@ -79,6 +84,7 @@ func (s *accountServiceTestSuite) TestGetAccount() { "canWithdraw": true, "feeTier": 2, "maxWithdrawAmount": "8.41264592", + "multiAssetsMargin": false, "positions": [ { "isolated": false, @@ -93,8 +99,8 @@ func (s *accountServiceTestSuite) TestGetAccount() { "maxNotional": "250000", "positionSide": "BOTH", "positionAmt": "0.436", - "notional":"0.1234", - "isolatedWallet":"0.5678", + "bidNotional": "0", + "askNotional": "0", "updateTime":1618646402359 } ], @@ -128,6 +134,11 @@ func (s *accountServiceTestSuite) TestGetAccount() { PositionInitialMargin: "0.33683000", UnrealizedProfit: "-0.44537584", WalletBalance: "9.19485176", + CrossWalletBalance: "23.72469206", + CrossUnPnl: "0.00000000", + AvailableBalance: "126.72469206", + MarginAvailable: true, + UpdateTime: 1625474304765, }, }, CanTrade: true, @@ -135,6 +146,7 @@ func (s *accountServiceTestSuite) TestGetAccount() { CanDeposit: true, FeeTier: 2, MaxWithdrawAmount: "8.41264592", + MultiAssetsMargin: false, Positions: []*AccountPosition{ { Isolated: false, @@ -149,8 +161,8 @@ func (s *accountServiceTestSuite) TestGetAccount() { MaxNotional: "250000", PositionSide: "BOTH", PositionAmt: "0.436", - Notional: "0.1234", - IsolatedWallet: "0.5678", + BidNotional: "0", + AskNotional: "0", UpdateTime: 1618646402359, }, }, @@ -181,6 +193,7 @@ func (s *accountServiceTestSuite) assertAccountEqual(e, a *Account) { r.Equal(e.TotalUnrealizedProfit, a.TotalUnrealizedProfit, "TotalUnrealizedProfit") r.Equal(e.TotalWalletBalance, a.TotalWalletBalance, "TotalWalletBalance") r.Equal(e.UpdateTime, a.UpdateTime, "UpdateTime") + r.Equal(e.MultiAssetsMargin, a.MultiAssetsMargin, "MultiAssetsMargin") r.Len(a.Assets, len(e.Assets)) for i := 0; i < len(a.Assets); i++ { @@ -193,6 +206,11 @@ func (s *accountServiceTestSuite) assertAccountEqual(e, a *Account) { r.Equal(e.Assets[i].PositionInitialMargin, a.Assets[i].PositionInitialMargin, "PositionInitialMargin") r.Equal(e.Assets[i].UnrealizedProfit, a.Assets[i].UnrealizedProfit, "UnrealizedProfit") r.Equal(e.Assets[i].WalletBalance, a.Assets[i].WalletBalance, "WalletBalance") + r.Equal(e.Assets[i].CrossWalletBalance, a.Assets[i].CrossWalletBalance, "CrossWalletBalance") + r.Equal(e.Assets[i].CrossUnPnl, a.Assets[i].CrossUnPnl, "CrossUnPnl") + r.Equal(e.Assets[i].AvailableBalance, a.Assets[i].AvailableBalance, "AvailableBalance") + r.Equal(e.Assets[i].MarginAvailable, a.Assets[i].MarginAvailable, "MarginAvailable") + r.Equal(e.Assets[i].UpdateTime, a.Assets[i].UpdateTime, "UpdateTime") } r.Len(a.Positions, len(e.Positions)) @@ -209,8 +227,8 @@ func (s *accountServiceTestSuite) assertAccountEqual(e, a *Account) { r.Equal(e.Positions[i].MaxNotional, a.Positions[i].MaxNotional, "MaxNotional") r.Equal(e.Positions[i].PositionSide, a.Positions[i].PositionSide, "PositionSide") r.Equal(e.Positions[i].PositionAmt, a.Positions[i].PositionAmt, "PositionAmt") - r.Equal(e.Positions[i].Notional, a.Positions[i].Notional, "Notional") - r.Equal(e.Positions[i].IsolatedWallet, a.Positions[i].IsolatedWallet, "IsolatedWallet") + r.Equal(e.Positions[i].BidNotional, a.Positions[i].BidNotional, "BidNotional") + r.Equal(e.Positions[i].AskNotional, a.Positions[i].AskNotional, "AskNotional") r.Equal(e.Positions[i].UpdateTime, a.Positions[i].UpdateTime, "UpdateTime") } } diff --git a/v2/futures/client.go b/v2/futures/client.go index 7b09ed93..e8832dc9 100644 --- a/v2/futures/client.go +++ b/v2/futures/client.go @@ -15,8 +15,9 @@ import ( "os" "time" - "github.com/adshao/go-binance/v2/common" "github.com/bitly/go-simplejson" + + "github.com/adshao/go-binance/v2/common" ) // SideType define side type of order @@ -400,6 +401,11 @@ func (c *Client) NewKlinesService() *KlinesService { return &KlinesService{c: c} } +// NewContinuousKlinesService init continuous klines service +func (c *Client) NewContinuousKlinesService() *ContinuousKlinesService { + return &ContinuousKlinesService{c: c} +} + // NewIndexPriceKlinesService init index price klines service func (c *Client) NewIndexPriceKlinesService() *IndexPriceKlinesService { return &IndexPriceKlinesService{c: c} @@ -530,6 +536,11 @@ func (c *Client) NewPremiumIndexService() *PremiumIndexService { return &PremiumIndexService{c: c} } +// NewPremiumIndexKlinesService init premium index klines service +func (c *Client) NewPremiumIndexKlinesService() *PremiumIndexKlinesService { + return &PremiumIndexKlinesService{c: c} +} + // NewFundingRateService init funding rate service func (c *Client) NewFundingRateService() *FundingRateService { return &FundingRateService{c: c} @@ -575,6 +586,16 @@ func (c *Client) NewGetPositionModeService() *GetPositionModeService { return &GetPositionModeService{c: c} } +// NewChangeMultiAssetModeService init change multi-asset mode service +func (c *Client) NewChangeMultiAssetModeService() *ChangeMultiAssetModeService { + return &ChangeMultiAssetModeService{c: c} +} + +// NewGetMultiAssetModeService init get multi-asset mode service +func (c *Client) NewGetMultiAssetModeService() *GetMultiAssetModeService { + return &GetMultiAssetModeService{c: c} +} + // NewGetRebateNewUserService init get rebate_newuser service func (c *Client) NewGetRebateNewUserService() *GetRebateNewUserService { return &GetRebateNewUserService{c: c} diff --git a/v2/futures/continuous_kline_service.go b/v2/futures/continuous_kline_service.go new file mode 100644 index 00000000..745ea2f7 --- /dev/null +++ b/v2/futures/continuous_kline_service.go @@ -0,0 +1,120 @@ +package futures + +import ( + "context" + "fmt" + "net/http" +) + +// ContinuousKlinesService list klines +type ContinuousKlinesService struct { + c *Client + pair string + contractType string + interval string + limit *int + startTime *int64 + endTime *int64 +} + +// pair set pair +func (s *ContinuousKlinesService) Pair(pair string) *ContinuousKlinesService { + s.pair = pair + return s +} + +// contractType set contractType +func (s *ContinuousKlinesService) ContractType(contractType string) *ContinuousKlinesService { + s.contractType = contractType + return s +} + +// Interval set interval +func (s *ContinuousKlinesService) Interval(interval string) *ContinuousKlinesService { + s.interval = interval + return s +} + +// Limit set limit +func (s *ContinuousKlinesService) Limit(limit int) *ContinuousKlinesService { + s.limit = &limit + return s +} + +// StartTime set startTime +func (s *ContinuousKlinesService) StartTime(startTime int64) *ContinuousKlinesService { + s.startTime = &startTime + return s +} + +// EndTime set endTime +func (s *ContinuousKlinesService) EndTime(endTime int64) *ContinuousKlinesService { + s.endTime = &endTime + return s +} + +// Do send request +func (s *ContinuousKlinesService) Do(ctx context.Context, opts ...RequestOption) (res []*ContinuousKline, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/fapi/v1/continuousKlines", + } + r.setParam("pair", s.pair) + r.setParam("contractType", s.contractType) + r.setParam("interval", s.interval) + if s.limit != nil { + r.setParam("limit", *s.limit) + } + if s.startTime != nil { + r.setParam("startTime", *s.startTime) + } + if s.endTime != nil { + r.setParam("endTime", *s.endTime) + } + data, _, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return []*ContinuousKline{}, err + } + j, err := newJSON(data) + if err != nil { + return []*ContinuousKline{}, err + } + num := len(j.MustArray()) + res = make([]*ContinuousKline, num) + for i := 0; i < num; i++ { + item := j.GetIndex(i) + if len(item.MustArray()) < 11 { + err = fmt.Errorf("invalid kline response") + return []*ContinuousKline{}, err + } + res[i] = &ContinuousKline{ + OpenTime: item.GetIndex(0).MustInt64(), + Open: item.GetIndex(1).MustString(), + High: item.GetIndex(2).MustString(), + Low: item.GetIndex(3).MustString(), + Close: item.GetIndex(4).MustString(), + Volume: item.GetIndex(5).MustString(), + CloseTime: item.GetIndex(6).MustInt64(), + QuoteAssetVolume: item.GetIndex(7).MustString(), + TradeNum: item.GetIndex(8).MustInt64(), + TakerBuyBaseAssetVolume: item.GetIndex(9).MustString(), + TakerBuyQuoteAssetVolume: item.GetIndex(10).MustString(), + } + } + return res, nil +} + +// ContinuousKline define ContinuousKline info +type ContinuousKline struct { + OpenTime int64 `json:"openTime"` + Open string `json:"open"` + High string `json:"high"` + Low string `json:"low"` + Close string `json:"close"` + Volume string `json:"volume"` + CloseTime int64 `json:"closeTime"` + QuoteAssetVolume string `json:"quoteAssetVolume"` + TradeNum int64 `json:"tradeNum"` + TakerBuyBaseAssetVolume string `json:"takerBuyBaseAssetVolume"` + TakerBuyQuoteAssetVolume string `json:"takerBuyQuoteAssetVolume"` +} diff --git a/v2/futures/continuous_kline_service_test.go b/v2/futures/continuous_kline_service_test.go new file mode 100644 index 00000000..95864549 --- /dev/null +++ b/v2/futures/continuous_kline_service_test.go @@ -0,0 +1,116 @@ +package futures + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type ContinuousklineServiceTestSuite struct { + baseTestSuite +} + +func TestContinuousKlineService(t *testing.T) { + suite.Run(t, new(ContinuousklineServiceTestSuite)) +} + +func (s *ContinuousklineServiceTestSuite) TestContinuousKlines() { + data := []byte(`[ + [ + 1499040000000, + "0.01634790", + "0.80000000", + "0.01575800", + "0.01577100", + "148976.11427815", + 1499644799999, + "2434.19055334", + 308, + "1756.87402397", + "28.46694368", + "17928899.62484339" + ], + [ + 1499040000001, + "0.01634790", + "0.80000000", + "0.01575800", + "0.01577101", + "148976.11427815", + 1499644799999, + "2434.19055334", + 308, + "1756.87402397", + "28.46694368", + "17928899.62484339" + ] + ]`) + s.mockDo(data, nil) + defer s.assertDo() + + pair := "LTCBTC" + contractType := "PERPETUAL" + interval := "15m" + limit := 10 + startTime := int64(1499040000000) + endTime := int64(1499040000001) + s.assertReq(func(r *request) { + e := newRequest().setParams(params{ + "pair": pair, + "contractType": contractType, + "interval": interval, + "limit": limit, + "startTime": startTime, + "endTime": endTime, + }) + s.assertRequestEqual(e, r) + }) + klines, err := s.client.NewContinuousKlinesService().Pair(pair). + ContractType(contractType).Interval(interval).Limit(limit). + StartTime(startTime).EndTime(endTime).Do(newContext()) + s.r().NoError(err) + s.Len(klines, 2) + kline1 := &ContinuousKline{ + OpenTime: 1499040000000, + Open: "0.01634790", + High: "0.80000000", + Low: "0.01575800", + Close: "0.01577100", + Volume: "148976.11427815", + CloseTime: 1499644799999, + QuoteAssetVolume: "2434.19055334", + TradeNum: 308, + TakerBuyBaseAssetVolume: "1756.87402397", + TakerBuyQuoteAssetVolume: "28.46694368", + } + kline2 := &ContinuousKline{ + OpenTime: 1499040000001, + Open: "0.01634790", + High: "0.80000000", + Low: "0.01575800", + Close: "0.01577101", + Volume: "148976.11427815", + CloseTime: 1499644799999, + QuoteAssetVolume: "2434.19055334", + TradeNum: 308, + TakerBuyBaseAssetVolume: "1756.87402397", + TakerBuyQuoteAssetVolume: "28.46694368", + } + s.assertContinuousKlineEqual(kline1, klines[0]) + s.assertContinuousKlineEqual(kline2, klines[1]) +} + +func (s *ContinuousklineServiceTestSuite) assertContinuousKlineEqual(e, a *ContinuousKline) { + r := s.r() + r.Equal(e.OpenTime, a.OpenTime, "OpenTime") + r.Equal(e.Open, a.Open, "Open") + r.Equal(e.High, a.High, "High") + r.Equal(e.Low, a.Low, "Low") + r.Equal(e.Close, a.Close, "Close") + r.Equal(e.Volume, a.Volume, "Volume") + r.Equal(e.CloseTime, a.CloseTime, "CloseTime") + r.Equal(e.QuoteAssetVolume, a.QuoteAssetVolume, "QuoteAssetVolume") + r.Equal(e.TradeNum, a.TradeNum, "TradeNum") + r.Equal(e.TakerBuyBaseAssetVolume, a.TakerBuyBaseAssetVolume, "TakerBuyBaseAssetVolume") + r.Equal(e.TakerBuyQuoteAssetVolume, a.TakerBuyQuoteAssetVolume, "TakerBuyQuoteAssetVolume") +} diff --git a/v2/futures/index_price_kline_service_test.go b/v2/futures/index_price_kline_service_test.go index a0da7bfe..2fb7a9a9 100644 --- a/v2/futures/index_price_kline_service_test.go +++ b/v2/futures/index_price_kline_service_test.go @@ -55,7 +55,7 @@ func (s *indexPriceKlineServiceTestSuite) TestKlines() { endTime := int64(1499040000001) s.assertReq(func(r *request) { e := newRequest().setParams(params{ - "symbol": symbol, + "pair": symbol, "interval": interval, "limit": limit, "startTime": startTime, @@ -63,9 +63,13 @@ func (s *indexPriceKlineServiceTestSuite) TestKlines() { }) s.assertRequestEqual(e, r) }) - klines, err := s.client.NewKlinesService().Symbol(symbol). - Interval(interval).Limit(limit).StartTime(startTime). - EndTime(endTime).Do(newContext()) + klines, err := s.client.NewIndexPriceKlinesService(). + Pair(symbol). + Interval(interval). + Limit(limit). + StartTime(startTime). + EndTime(endTime). + Do(newContext()) s.r().NoError(err) s.Len(klines, 2) kline1 := &Kline{ diff --git a/v2/futures/order_service.go b/v2/futures/order_service.go index d1680567..4fc44b4a 100644 --- a/v2/futures/order_service.go +++ b/v2/futures/order_service.go @@ -137,9 +137,11 @@ func (s *CreateOrderService) createOrder(ctx context.Context, endpoint string, o "symbol": s.symbol, "side": s.side, "type": s.orderType, - "quantity": s.quantity, "newOrderRespType": s.newOrderRespType, } + if s.quantity != "" { + m["quantity"] = s.quantity + } if s.positionSide != nil { m["positionSide"] = *s.positionSide } diff --git a/v2/futures/position_risk.go b/v2/futures/position_risk.go index 67a79eba..a22b9fa4 100644 --- a/v2/futures/position_risk.go +++ b/v2/futures/position_risk.go @@ -43,6 +43,7 @@ func (s *GetPositionRiskService) Do(ctx context.Context, opts ...RequestOption) // PositionRisk define position risk info type PositionRisk struct { EntryPrice string `json:"entryPrice"` + BreakEvenPrice string `json:"breakEvenPrice"` MarginType string `json:"marginType"` IsAutoAddMargin string `json:"isAutoAddMargin"` IsolatedMargin string `json:"isolatedMargin"` diff --git a/v2/futures/position_risk_test.go b/v2/futures/position_risk_test.go index f867752c..0e3fb611 100644 --- a/v2/futures/position_risk_test.go +++ b/v2/futures/position_risk_test.go @@ -18,6 +18,7 @@ func (s *positionRiskServiceTestSuite) TestGetPositionRisk() { data := []byte(`[ { "entryPrice": "10359.38000", + "breakEvenPrice": "10387.38000", "marginType": "isolated", "isAutoAddMargin": "false", "isolatedMargin": "3.15899368", @@ -50,6 +51,7 @@ func (s *positionRiskServiceTestSuite) TestGetPositionRisk() { r.Len(res, 1) e := &PositionRisk{ EntryPrice: "10359.38000", + BreakEvenPrice: "10387.38000", MarginType: "isolated", IsAutoAddMargin: "false", IsolatedMargin: "3.15899368", @@ -68,6 +70,7 @@ func (s *positionRiskServiceTestSuite) TestGetPositionRisk() { func (s *positionRiskServiceTestSuite) assertPositionRiskEqual(e, a *PositionRisk) { r := s.r() r.Equal(e.EntryPrice, a.EntryPrice, "EntryPrice") + r.Equal(e.BreakEvenPrice, a.BreakEvenPrice, "BreakEvenPrice") r.Equal(e.MarginType, a.MarginType, "MarginType") r.Equal(e.IsAutoAddMargin, a.IsAutoAddMargin, "IsAutoAddMargin") r.Equal(e.IsolatedMargin, a.IsolatedMargin, "IsolatedMargin") diff --git a/v2/futures/position_service.go b/v2/futures/position_service.go index f4c2a9a9..37c5bf55 100644 --- a/v2/futures/position_service.go +++ b/v2/futures/position_service.go @@ -152,16 +152,12 @@ func (s *UpdatePositionMarginService) Do(ctx context.Context, opts ...RequestOpt // ChangePositionModeService change user's position mode type ChangePositionModeService struct { c *Client - dualSide string + dualSide bool } // Change user's position mode: true - Hedge Mode, false - One-way Mode func (s *ChangePositionModeService) DualSide(dualSide bool) *ChangePositionModeService { - if dualSide { - s.dualSide = "true" - } else { - s.dualSide = "false" - } + s.dualSide = dualSide return s } @@ -211,3 +207,62 @@ func (s *GetPositionModeService) Do(ctx context.Context, opts ...RequestOption) } return res, nil } + +// ChangeMultiAssetModeService change user's multi-asset mode +type ChangeMultiAssetModeService struct { + c *Client + multiAssetsMargin bool +} + +// MultiAssetsMargin set multiAssetsMargin +func (s *ChangeMultiAssetModeService) MultiAssetsMargin(multiAssetsMargin bool) *ChangeMultiAssetModeService { + s.multiAssetsMargin = multiAssetsMargin + return s +} + +// Do send request +func (s *ChangeMultiAssetModeService) Do(ctx context.Context, opts ...RequestOption) (err error) { + r := &request{ + method: http.MethodPost, + endpoint: "/fapi/v1/multiAssetsMargin", + secType: secTypeSigned, + } + r.setFormParams(params{ + "multiAssetsMargin": s.multiAssetsMargin, + }) + _, _, err = s.c.callAPI(ctx, r, opts...) + if err != nil { + return err + } + return nil +} + +// GetMultiAssetModeService get user's multi-asset mode +type GetMultiAssetModeService struct { + c *Client +} + +// Response of user's multi-asset mode +type MultiAssetMode struct { + MultiAssetsMargin bool `json:"multiAssetsMargin"` +} + +// Do send request +func (s *GetMultiAssetModeService) Do(ctx context.Context, opts ...RequestOption) (res *MultiAssetMode, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/fapi/v1/multiAssetsMargin", + secType: secTypeSigned, + } + r.setFormParams(params{}) + data, _, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = &MultiAssetMode{} + err = json.Unmarshal(data, &res) + if err != nil { + return nil, err + } + return res, nil +} diff --git a/v2/futures/position_service_test.go b/v2/futures/position_service_test.go index a0fbe8e2..ec52cef0 100644 --- a/v2/futures/position_service_test.go +++ b/v2/futures/position_service_test.go @@ -122,3 +122,35 @@ func (s *positionServiceTestSuite) TestGetPositionMode() { s.r().NoError(err) s.r().Equal(res.DualSidePosition, true) } + +func (s *positionServiceTestSuite) TestChangeMultiAssetMode() { + data := []byte(`{ + "code": 200, + "msg": "success" + }`) + s.mockDo(data, nil) + defer s.assertDo() + s.assertReq(func(r *request) { + e := newSignedRequest().setFormParams(params{ + "multiAssetsMargin": "true", + }) + s.assertRequestEqual(e, r) + }) + err := s.client.NewChangeMultiAssetModeService().MultiAssetsMargin(true).Do(newContext()) + s.r().NoError(err) +} + +func (s *positionServiceTestSuite) TestGetMultiAssetMode() { + data := []byte(`{ + "multiAssetsMargin": true + }`) + s.mockDo(data, nil) + defer s.assertDo() + s.assertReq(func(r *request) { + e := newSignedRequest().setFormParams(params{}) + s.assertRequestEqual(e, r) + }) + res, err := s.client.NewGetMultiAssetModeService().Do(newContext()) + s.r().NoError(err) + s.r().Equal(res.MultiAssetsMargin, true) +} diff --git a/v2/futures/premium_index_kline_service.go b/v2/futures/premium_index_kline_service.go new file mode 100644 index 00000000..79ba4b9c --- /dev/null +++ b/v2/futures/premium_index_kline_service.go @@ -0,0 +1,92 @@ +package futures + +import ( + "context" + "fmt" + "net/http" +) + +// PremiumIndexKlinesService list klines +type PremiumIndexKlinesService struct { + c *Client + symbol string + interval string + limit *int + startTime *int64 + endTime *int64 +} + +// Symbol sets symbol +func (piks *PremiumIndexKlinesService) Symbol(symbol string) *PremiumIndexKlinesService { + piks.symbol = symbol + return piks +} + +// Interval set interval +func (piks *PremiumIndexKlinesService) Interval(interval string) *PremiumIndexKlinesService { + piks.interval = interval + return piks +} + +// Limit set limit +func (piks *PremiumIndexKlinesService) Limit(limit int) *PremiumIndexKlinesService { + piks.limit = &limit + return piks +} + +// StartTime set startTime +func (piks *PremiumIndexKlinesService) StartTime(startTime int64) *PremiumIndexKlinesService { + piks.startTime = &startTime + return piks +} + +// EndTime set endTime +func (piks *PremiumIndexKlinesService) EndTime(endTime int64) *PremiumIndexKlinesService { + piks.endTime = &endTime + return piks +} + +// Do send request +func (piks *PremiumIndexKlinesService) Do(ctx context.Context, opts ...RequestOption) (res []*Kline, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/fapi/v1/premiumIndexKlines", + } + r.setParam("symbol", piks.symbol) + r.setParam("interval", piks.interval) + if piks.limit != nil { + r.setParam("limit", *piks.limit) + } + if piks.startTime != nil { + r.setParam("startTime", *piks.startTime) + } + if piks.endTime != nil { + r.setParam("endTime", *piks.endTime) + } + data, _, err := piks.c.callAPI(ctx, r, opts...) + if err != nil { + return []*Kline{}, err + } + j, err := newJSON(data) + if err != nil { + return []*Kline{}, err + } + num := len(j.MustArray()) + res = make([]*Kline, num) + for i := 0; i < num; i++ { + item := j.GetIndex(i) + if len(item.MustArray()) < 11 { + err = fmt.Errorf("invalid kline response") + return []*Kline{}, err + } + res[i] = &Kline{ + OpenTime: item.GetIndex(0).MustInt64(), + Open: item.GetIndex(1).MustString(), + High: item.GetIndex(2).MustString(), + Low: item.GetIndex(3).MustString(), + Close: item.GetIndex(4).MustString(), + CloseTime: item.GetIndex(6).MustInt64(), + } + } + return res, nil +} diff --git a/v2/futures/premium_index_kline_service_test.go b/v2/futures/premium_index_kline_service_test.go new file mode 100644 index 00000000..c6218f6b --- /dev/null +++ b/v2/futures/premium_index_kline_service_test.go @@ -0,0 +1,103 @@ +package futures + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type premiumIndexKlinesServiceTestSuite struct { + baseTestSuite +} + +func TestPremiumIndexKlinesService(t *testing.T) { + suite.Run(t, new(premiumIndexKlinesServiceTestSuite)) +} + +func (s *premiumIndexKlinesServiceTestSuite) TestKlines() { + data := []byte(`[ + [ + 1499040000000, + "0.01634790", + "0.80000000", + "0.01575800", + "0.01577100", + "148976.11427815", + 1499644799999, + "2434.19055334", + 308, + "1756.87402397", + "28.46694368", + "17928899.62484339" + ], + [ + 1499040000001, + "0.01634790", + "0.80000000", + "0.01575800", + "0.01577101", + "148976.11427815", + 1499644799999, + "2434.19055334", + 308, + "1756.87402397", + "28.46694368", + "17928899.62484339" + ] + ]`) + s.mockDo(data, nil) + defer s.assertDo() + + symbol := "LTCBTC" + interval := "15m" + limit := 10 + startTime := int64(1499040000000) + endTime := int64(1499040000001) + s.assertReq(func(r *request) { + e := newRequest().setParams(params{ + "symbol": symbol, + "interval": interval, + "limit": limit, + "startTime": startTime, + "endTime": endTime, + }) + s.assertRequestEqual(e, r) + }) + klines, err := s.client.NewPremiumIndexKlinesService(). + Symbol(symbol). + Interval(interval). + Limit(limit). + StartTime(startTime). + EndTime(endTime). + Do(newContext()) + s.r().NoError(err) + s.Len(klines, 2) + kline1 := &Kline{ + OpenTime: 1499040000000, + Open: "0.01634790", + High: "0.80000000", + Low: "0.01575800", + Close: "0.01577100", + CloseTime: 1499644799999, + } + kline2 := &Kline{ + OpenTime: 1499040000001, + Open: "0.01634790", + High: "0.80000000", + Low: "0.01575800", + Close: "0.01577101", + CloseTime: 1499644799999, + } + s.assertKlineEqual(kline1, klines[0]) + s.assertKlineEqual(kline2, klines[1]) +} + +func (s *premiumIndexKlinesServiceTestSuite) assertKlineEqual(e, a *Kline) { + r := s.r() + r.Equal(e.OpenTime, a.OpenTime, "OpenTime") + r.Equal(e.Open, a.Open, "Open") + r.Equal(e.High, a.High, "High") + r.Equal(e.Low, a.Low, "Low") + r.Equal(e.Close, a.Close, "Close") + r.Equal(e.CloseTime, a.CloseTime, "CloseTime") +} diff --git a/v2/futures/ticker_service.go b/v2/futures/ticker_service.go index 568eb245..f19e1847 100644 --- a/v2/futures/ticker_service.go +++ b/v2/futures/ticker_service.go @@ -141,7 +141,7 @@ type PriceChangeStats struct { QuoteVolume string `json:"quoteVolume"` OpenTime int64 `json:"openTime"` CloseTime int64 `json:"closeTime"` - FristID int64 `json:"firstId"` + FirstID int64 `json:"firstId"` LastID int64 `json:"lastId"` Count int64 `json:"count"` } diff --git a/v2/futures/ticker_service_test.go b/v2/futures/ticker_service_test.go index c5813956..9def7258 100644 --- a/v2/futures/ticker_service_test.go +++ b/v2/futures/ticker_service_test.go @@ -214,7 +214,7 @@ func (s *tickerServiceTestSuite) TestPriceChangeStats() { Volume: "8913.30000000", OpenTime: 1499783499040, CloseTime: 1499869899040, - FristID: 28385, + FirstID: 28385, LastID: 28460, Count: 76, } @@ -236,7 +236,7 @@ func (s *tickerServiceTestSuite) assertPriceChangeStatsEqual(e, a *PriceChangeSt r.Equal(e.Volume, a.Volume, "Volume") r.Equal(e.OpenTime, a.OpenTime, "OpenTime") r.Equal(e.CloseTime, a.CloseTime, "CloseTime") - r.Equal(e.FristID, a.FristID, "FristID") + r.Equal(e.FirstID, a.FirstID, "FirstID") r.Equal(e.LastID, a.LastID, "LastID") r.Equal(e.Count, a.Count, "Count") } @@ -289,7 +289,7 @@ func (s *tickerServiceTestSuite) TestListPriceChangeStats() { QuoteVolume: "15.30000000", OpenTime: 1499783499040, CloseTime: 1499869899040, - FristID: 28385, + FirstID: 28385, LastID: 28460, Count: 76, }, diff --git a/v2/futures/trade_service.go b/v2/futures/trade_service.go index 585cf15a..ad54cdf8 100644 --- a/v2/futures/trade_service.go +++ b/v2/futures/trade_service.go @@ -212,6 +212,7 @@ func (s *RecentTradesService) Do(ctx context.Context, opts ...RequestOption) (re type ListAccountTradeService struct { c *Client symbol string + orderId *int64 startTime *int64 endTime *int64 fromID *int64 @@ -224,6 +225,12 @@ func (s *ListAccountTradeService) Symbol(symbol string) *ListAccountTradeService return s } +// OrderID set orderId +func (s *ListAccountTradeService) OrderID(orderID int64) *ListAccountTradeService { + s.orderId = &orderID + return s +} + // StartTime set startTime func (s *ListAccountTradeService) StartTime(startTime int64) *ListAccountTradeService { s.startTime = &startTime @@ -256,6 +263,9 @@ func (s *ListAccountTradeService) Do(ctx context.Context, opts ...RequestOption) secType: secTypeSigned, } r.setParam("symbol", s.symbol) + if s.orderId != nil { + r.setParam("orderId", *s.orderId) + } if s.startTime != nil { r.setParam("startTime", *s.startTime) } diff --git a/v2/futures/websocket_service.go b/v2/futures/websocket_service.go index 0652bc99..70ddf572 100644 --- a/v2/futures/websocket_service.go +++ b/v2/futures/websocket_service.go @@ -159,6 +159,64 @@ func WsMarkPriceServeWithRate(symbol string, rate time.Duration, handler WsMarkP return wsMarkPriceServe(endpoint, handler, errHandler) } +func wsCombinedMarkPriceServe(endpoint string, handler WsMarkPriceHandler, errHandler ErrHandler) (doneC, stopC chan struct{}, err error) { + cfg := newWsConfig(endpoint) + wsHandler := func(message []byte) { + j, err := newJSON(message) + if err != nil { + errHandler(err) + return + } + + data := j.Get("data").MustMap() + jsonData, _ := json.Marshal(data) + + event := new(WsMarkPriceEvent) + err = json.Unmarshal(jsonData, event) + if err != nil { + errHandler(err) + return + } + + handler(event) + } + + return wsServe(cfg, wsHandler, errHandler) +} + +// WsCombinedMarkPriceServe is similar to WsMarkPriceServe, but it handles multiple symbols +func WsCombinedMarkPriceServe(symbols []string, handler WsMarkPriceHandler, errHandler ErrHandler) (doneC, stopC chan struct{}, err error) { + endpoint := getCombinedEndpoint() + for _, s := range symbols { + endpoint += fmt.Sprintf("%s@markPrice", strings.ToLower(s)) + "/" + } + endpoint = endpoint[:len(endpoint)-1] + + return wsCombinedMarkPriceServe(endpoint, handler, errHandler) +} + +// WsCombinedMarkPriceServeWithRate is similar to WsMarkPriceServeWithRate, but it for multiple symbols +func WsCombinedMarkPriceServeWithRate(symbolLevels map[string]time.Duration, handler WsMarkPriceHandler, errHandler ErrHandler) (doneC, stopC chan struct{}, err error) { + endpoint := getCombinedEndpoint() + for symbol, rate := range symbolLevels { + var rateStr string + switch rate { + case 3 * time.Second: + rateStr = "" + case 1 * time.Second: + rateStr = "@1s" + default: + return nil, nil, fmt.Errorf("invalid rate. Symbol %s (rate %d)", symbol, rate) + } + + endpoint += fmt.Sprintf("%s@markPrice%s", strings.ToLower(symbol), rateStr) + "/" + } + + endpoint = endpoint[:len(endpoint)-1] + + return wsCombinedMarkPriceServe(endpoint, handler, errHandler) +} + // WsAllMarkPriceEvent defines an array of websocket markPriceUpdate events. type WsAllMarkPriceEvent []*WsMarkPriceEvent @@ -282,6 +340,95 @@ func WsCombinedKlineServe(symbolIntervalPair map[string]string, handler WsKlineH return wsServe(cfg, wsHandler, errHandler) } +// WsContinuousKlineEvent define websocket continuous kline event +type WsContinuousKlineEvent struct { + Event string `json:"e"` + Time int64 `json:"E"` + PairSymbol string `json:"ps"` + ContractType string `json:"ct"` + Kline WsContinuousKline `json:"k"` +} + +// WsContinuousKline define websocket continuous kline +type WsContinuousKline struct { + StartTime int64 `json:"t"` + EndTime int64 `json:"T"` + Interval string `json:"i"` + FirstTradeID int64 `json:"f"` + LastTradeID int64 `json:"L"` + Open string `json:"o"` + Close string `json:"c"` + High string `json:"h"` + Low string `json:"l"` + Volume string `json:"v"` + TradeNum int64 `json:"n"` + IsFinal bool `json:"x"` + QuoteVolume string `json:"q"` + ActiveBuyVolume string `json:"V"` + ActiveBuyQuoteVolume string `json:"Q"` +} + +// WsContinuousKlineSubcribeArgs used with WsContinuousKlineServe or WsCombinedContinuousKlineServe +type WsContinuousKlineSubcribeArgs struct { + Pair string + ContractType string + Interval string +} + +// WsContinuousKlineHandler handle websocket continuous kline event +type WsContinuousKlineHandler func(event *WsContinuousKlineEvent) + +// WsContinuousKlineServe serve websocket continuous kline handler with a pair and contractType and interval like 15m, 30s +func WsContinuousKlineServe(subscribeArgs *WsContinuousKlineSubcribeArgs, handler WsContinuousKlineHandler, + errHandler ErrHandler) (doneC, stopC chan struct{}, err error) { + endpoint := fmt.Sprintf("%s/%s_%s@continuousKline_%s", getWsEndpoint(), strings.ToLower(subscribeArgs.Pair), + strings.ToLower(subscribeArgs.ContractType), subscribeArgs.Interval) + cfg := newWsConfig(endpoint) + wsHandler := func(message []byte) { + event := new(WsContinuousKlineEvent) + err := json.Unmarshal(message, event) + if err != nil { + errHandler(err) + return + } + handler(event) + } + return wsServe(cfg, wsHandler, errHandler) +} + +// WsCombinedContinuousKlineServe is similar to WsContinuousKlineServe, but it handles multiple pairs of different contractType with its interval +func WsCombinedContinuousKlineServe(subscribeArgsList []*WsContinuousKlineSubcribeArgs, + handler WsContinuousKlineHandler, errHandler ErrHandler) (doneC, stopC chan struct{}, err error) { + endpoint := getCombinedEndpoint() + for _, val := range subscribeArgsList { + endpoint += fmt.Sprintf("%s_%s@continuousKline_%s", strings.ToLower(val.Pair), + strings.ToLower(val.ContractType), val.Interval) + "/" + } + endpoint = endpoint[:len(endpoint)-1] + cfg := newWsConfig(endpoint) + wsHandler := func(message []byte) { + j, err := newJSON(message) + if err != nil { + errHandler(err) + return + } + + data := j.Get("data").MustMap() + + jsonData, _ := json.Marshal(data) + + event := new(WsContinuousKlineEvent) + err = json.Unmarshal(jsonData, event) + if err != nil { + errHandler(err) + return + } + + handler(event) + } + return wsServe(cfg, wsHandler, errHandler) +} + // WsMiniMarketTickerEvent define websocket mini market ticker event. type WsMiniMarketTickerEvent struct { Event string `json:"e"` diff --git a/v2/futures/websocket_service_test.go b/v2/futures/websocket_service_test.go index 1a11de9a..cb77b7bc 100644 --- a/v2/futures/websocket_service_test.go +++ b/v2/futures/websocket_service_test.go @@ -2,6 +2,7 @@ package futures import ( "errors" + "fmt" "math/rand" "testing" "time" @@ -312,6 +313,88 @@ func (s *websocketServiceTestSuite) TestAllMarkPriceServeWithInvalidRate() { } } +func (s *websocketServiceTestSuite) testCombinedMarkPriceServe(rate *time.Duration, expectedErr error, expectedServeCnt int) { + data := []byte(`{ + "stream": "btcusdt@markPrice", + "data": { + "e": "markPriceUpdate", + "E": 1681724175000, + "s": "BTCUSDT", + "p": "29892.78738889", + "P": "29903.84541674", + "i": "29904.57564103", + "r": "0.00010000", + "T": 1681747200000 + }}`) + s.mockWsServe(data, expectedErr) + defer s.assertWsServe(expectedServeCnt) + + handler := func(event *WsMarkPriceEvent) { + e := &WsMarkPriceEvent{ + Event: "markPriceUpdate", + Time: 1681724175000, + Symbol: "BTCUSDT", + MarkPrice: "29892.78738889", + IndexPrice: "29904.57564103", + FundingRate: "0.00010000", + NextFundingTime: 1681747200000, + } + s.assertWsMarkPriceEvent(e, event) + } + errHandler := func(err error) { + } + + var doneC, stopC chan struct{} + var err error + + if rate == nil { + input := []string{"BTCUSDT"} + doneC, stopC, err = WsCombinedMarkPriceServe(input, handler, errHandler) + } else { + input := map[string]time.Duration{"BTCUSDT": *rate} + doneC, stopC, err = WsCombinedMarkPriceServeWithRate(input, handler, errHandler) + } + + if expectedErr == nil { + s.r().NoError(err) + } else { + s.r().EqualError(err, expectedErr.Error()) + } + + if stopC != nil { + stopC <- struct{}{} + } + if doneC != nil { + <-doneC + } +} + +func (s *websocketServiceTestSuite) TestCombinedMarkPriceServe() { + s.testCombinedMarkPriceServe(nil, nil, 1) +} + +func (s *websocketServiceTestSuite) TestCombinedMarkPriceServeWithValidRate() { + rate := 3 * time.Second + s.testCombinedMarkPriceServe(&rate, nil, 1) + rate = time.Second + s.testCombinedMarkPriceServe(&rate, nil, 2) +} + +func (s *websocketServiceTestSuite) TestCombinedMarkPriceServeWithInvalidRate() { + randSrc := rand.NewSource(time.Now().UnixNano()) + rand := rand.New(randSrc) + for { + rate := time.Duration(rand.Intn(10)) * time.Second + switch rate { + case 3 * time.Second: + case 1 * time.Second: + default: + s.testCombinedMarkPriceServe(&rate, errors.New(fmt.Sprintf("invalid rate. Symbol BTCUSDT (rate %d)", rate)), 0) + return + } + } +} + func (s *websocketServiceTestSuite) TestKlineServe() { data := []byte(`{ "e": "kline", @@ -464,6 +547,167 @@ func (s *websocketServiceTestSuite) TestWsCombinedKlineServe() { <-doneC } +func (s *websocketServiceTestSuite) TestContinuousKlineServe() { + data := []byte(`{ + "e": "continuous_kline", + "E": 123456789, + "ps": "BTCUSDT", + "ct": "PERPETUAL", + "k": { + "t": 123400000, + "T": 123460000, + "i": "1m", + "f": 100, + "L": 200, + "o": "0.0010", + "c": "0.0020", + "h": "0.0025", + "l": "0.0015", + "v": "1000", + "n": 100, + "x": false, + "q": "1.0000", + "V": "500", + "Q": "0.500" + } + }`) + fakeErrMsg := "fake error" + s.mockWsServe(data, errors.New(fakeErrMsg)) + defer s.assertWsServe() + + doneC, stopC, err := WsContinuousKlineServe(&WsContinuousKlineSubcribeArgs{ + Pair: "BTCUSDT", + ContractType: "PERPETUAL", + Interval: "1m", + }, + func(event *WsContinuousKlineEvent) { + e := &WsContinuousKlineEvent{ + Event: "continuous_kline", + Time: 123456789, + PairSymbol: "BTCUSDT", + ContractType: "PERPETUAL", + Kline: WsContinuousKline{ + StartTime: 123400000, + EndTime: 123460000, + Interval: "1m", + FirstTradeID: 100, + LastTradeID: 200, + Open: "0.0010", + Close: "0.0020", + High: "0.0025", + Low: "0.0015", + Volume: "1000", + TradeNum: 100, + IsFinal: false, + QuoteVolume: "1.0000", + ActiveBuyVolume: "500", + ActiveBuyQuoteVolume: "0.500", + }, + } + s.assertWsContinuousKlineEventEqual(e, event) + }, func(err error) { + s.r().EqualError(err, fakeErrMsg) + }) + s.r().NoError(err) + stopC <- struct{}{} + <-doneC +} + +func (s *websocketServiceTestSuite) assertWsContinuousKlineEventEqual(e, a *WsContinuousKlineEvent) { + r := s.r() + r.Equal(e.Event, a.Event, "Event") + r.Equal(e.Time, a.Time, "Time") + r.Equal(e.PairSymbol, a.PairSymbol, "PairSymbol") + ek, ak := e.Kline, a.Kline + r.Equal(ek.StartTime, ak.StartTime, "StartTime") + r.Equal(ek.EndTime, ak.EndTime, "EndTime") + r.Equal(ek.Interval, ak.Interval, "Interval") + r.Equal(ek.FirstTradeID, ak.FirstTradeID, "FirstTradeID") + r.Equal(ek.LastTradeID, ak.LastTradeID, "LastTradeID") + r.Equal(ek.Open, ak.Open, "Open") + r.Equal(ek.Close, ak.Close, "Close") + r.Equal(ek.High, ak.High, "High") + r.Equal(ek.Low, ak.Low, "Low") + r.Equal(ek.Volume, ak.Volume, "Volume") + r.Equal(ek.TradeNum, ak.TradeNum, "TradeNum") + r.Equal(ek.IsFinal, ak.IsFinal, "IsFinal") + r.Equal(ek.QuoteVolume, ak.QuoteVolume, "QuoteVolume") + r.Equal(ek.ActiveBuyVolume, ak.ActiveBuyVolume, "ActiveBuyVolume") + r.Equal(ek.ActiveBuyQuoteVolume, ak.ActiveBuyQuoteVolume, "ActiveBuyQuoteVolume") +} + +func (s *websocketServiceTestSuite) TestWsCombinedContinuousKlineServe() { + data := []byte(`{ + "stream":"ethbtc_perpetual@continuousKline_1m", + "data": { + "e": "continuous_kline", + "E": 1499404907056, + "ps": "ETHBTC", + "ct": "PERPETUAL", + "k": { + "t": 1499404860000, + "T": 1499404919999, + "s": "ETHBTC", + "i": "1m", + "f": 77462, + "L": 77465, + "o": "0.10278577", + "c": "0.10278645", + "h": "0.10278712", + "l": "0.10278518", + "v": "17.47929838", + "n": 4, + "x": false, + "q": "1.79662878", + "V": "2.34879839", + "Q": "0.24142166", + "B": "13279784.01349473" + } + }}`) + fakeErrMsg := "fake error" + s.mockWsServe(data, errors.New(fakeErrMsg)) + defer s.assertWsServe() + + input := []*WsContinuousKlineSubcribeArgs{ + { + Pair: "ETHBTC", + ContractType: "PERPETUAL", + Interval: "1m", + }, + } + doneC, stopC, err := WsCombinedContinuousKlineServe(input, func(event *WsContinuousKlineEvent) { + e := &WsContinuousKlineEvent{ + Event: "continuous_kline", + Time: 1499404907056, + PairSymbol: "ETHBTC", + ContractType: "PERPETUAL", + Kline: WsContinuousKline{ + StartTime: 1499404860000, + EndTime: 1499404919999, + Interval: "1m", + FirstTradeID: 77462, + LastTradeID: 77465, + Open: "0.10278577", + Close: "0.10278645", + High: "0.10278712", + Low: "0.10278518", + Volume: "17.47929838", + TradeNum: 4, + IsFinal: false, + QuoteVolume: "1.79662878", + ActiveBuyVolume: "2.34879839", + ActiveBuyQuoteVolume: "0.24142166", + }, + } + s.assertWsContinuousKlineEventEqual(e, event) + }, func(err error) { + s.r().EqualError(err, fakeErrMsg) + }) + s.r().NoError(err) + stopC <- struct{}{} + <-doneC +} + func (s *websocketServiceTestSuite) TestMiniMarketTickerServe() { data := []byte(`{ "e": "24hrMiniTicker", diff --git a/v2/go.mod b/v2/go.mod index c62f5639..cb8489e2 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -3,7 +3,7 @@ module github.com/KyberNetwork/go-binance/v2 go 1.18 require ( - github.com/adshao/go-binance/v2 v2.4.1 + github.com/adshao/go-binance/v2 v2.4.5 github.com/bitly/go-simplejson v0.5.0 github.com/gorilla/websocket v1.5.0 github.com/json-iterator/go v1.1.12 @@ -21,4 +21,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/adshao/go-binance/v2 v2.4.1 => ./ +replace github.com/adshao/go-binance/v2 v2.4.5 => ./ diff --git a/v2/order_service.go b/v2/order_service.go index 584dd605..ba2a18bf 100644 --- a/v2/order_service.go +++ b/v2/order_service.go @@ -186,7 +186,7 @@ type CreateOrderResponse struct { // Fill may be returned in an array of fills in a CreateOrderResponse. type Fill struct { - TradeID int `json:"tradeId"` + TradeID int64 `json:"tradeId"` Price string `json:"price"` Quantity string `json:"qty"` Commission string `json:"commission"` diff --git a/v2/pay_service.go b/v2/pay_service.go index 7f070716..dbf1f5a2 100644 --- a/v2/pay_service.go +++ b/v2/pay_service.go @@ -71,6 +71,8 @@ type PayTradeItem struct { TransactionTime int64 `json:"transactionTime"` Amount string `json:"amount"` Currency string `json:"currency"` + PayerInfo *PayerInfo `json:"payerInfo"` + ReceiverInfo *ReceiverInfo `json:"receiverInfo"` FundsDetail []FundsDetail `json:"fundsDetail"` } @@ -78,3 +80,28 @@ type FundsDetail struct { Currency string `json:"currency"` Amount string `json:"amount"` } +type PayerInfo struct { + Name string `json:"name"` + Type string `json:"type"` + Email string `json:"email"` + BinanceId int `json:"binanceId"` + AccountId int `json:"accountId"` + CountryCode int `json:"countryCode"` + PhoneNumber string `json:"phoneNumber"` + MobileCode string `json:"mobileCode"` + UnmaskData bool `json:"unmaskData"` +} +type ReceiverInfo struct { + Name string `json:"name"` + Type string `json:"type"` + Email string `json:"email,omitempty"` + BinanceId int `json:"binanceId,omitempty"` + AccountId int `json:"accountId"` + CountryCode int `json:"countryCode,omitempty"` + PhoneNumber string `json:"phoneNumber,omitempty"` + MobileCode string `json:"mobileCode,omitempty"` + UnmaskData bool `json:"unmaskData"` + Extend struct { + PhoneOrEmailChanged bool `json:"phoneOrEmailChanged"` + } `json:"extend,omitempty"` +} diff --git a/v2/subaccount_service.go b/v2/subaccount_service.go index 4ed5180b..b5a7596e 100644 --- a/v2/subaccount_service.go +++ b/v2/subaccount_service.go @@ -338,6 +338,231 @@ type SubAccount struct { IsAssetManagementSubAccount bool `json:"isAssetManagementSubAccount"` } +// ManagedSubAccountDepositService +// Deposit Assets Into The Managed Sub-account(For Investor Master Account) +// https://binance-docs.github.io/apidocs/spot/en/#deposit-assets-into-the-managed-sub-account-for-investor-master-account +type ManagedSubAccountDepositService struct { + c *Client + toEmail string + asset string + amount float64 +} + +func (s *ManagedSubAccountDepositService) ToEmail(email string) *ManagedSubAccountDepositService { + s.toEmail = email + return s +} + +func (s *ManagedSubAccountDepositService) Asset(asset string) *ManagedSubAccountDepositService { + s.asset = asset + return s +} + +func (s *ManagedSubAccountDepositService) Amount(amount float64) *ManagedSubAccountDepositService { + s.amount = amount + return s +} + +type ManagedSubAccountDepositResponse struct { + ID int64 `json:"tranId"` +} + +// Do send request +func (s *ManagedSubAccountDepositService) Do(ctx context.Context, opts ...RequestOption) (*ManagedSubAccountDepositResponse, error) { + r := &request{ + method: "POST", + endpoint: "/sapi/v1/managed-subaccount/deposit", + secType: secTypeSigned, + } + + r.setParam("toEmail", s.toEmail) + r.setParam("asset", s.asset) + r.setParam("amount", s.amount) + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + + res := &ManagedSubAccountDepositResponse{} + if err := json.Unmarshal(data, res); err != nil { + return nil, err + } + + return res, nil +} + +// ManagedSubAccountWithdrawalService +// Withdrawal Assets From The Managed Sub-account(For Investor Master Account) +// https://binance-docs.github.io/apidocs/spot/en/#withdrawl-assets-from-the-managed-sub-account-for-investor-master-account +type ManagedSubAccountWithdrawalService struct { + c *Client + fromEmail string + asset string + amount float64 + transferDate int64 // Withdrawals is automatically occur on the transfer date(UTC0). If a date is not selected, the withdrawal occurs right now +} + +func (s *ManagedSubAccountWithdrawalService) FromEmail(email string) *ManagedSubAccountWithdrawalService { + s.fromEmail = email + return s +} + +func (s *ManagedSubAccountWithdrawalService) Asset(asset string) *ManagedSubAccountWithdrawalService { + s.asset = asset + return s +} + +func (s *ManagedSubAccountWithdrawalService) Amount(amount float64) *ManagedSubAccountWithdrawalService { + s.amount = amount + return s +} + +func (s *ManagedSubAccountWithdrawalService) TransferDate(val int64) *ManagedSubAccountWithdrawalService { + s.transferDate = val + return s +} + +type ManagedSubAccountWithdrawalResponse struct { + ID int64 `json:"tranId"` +} + +// Do send request +func (s *ManagedSubAccountWithdrawalService) Do(ctx context.Context, opts ...RequestOption) (*ManagedSubAccountWithdrawalResponse, error) { + r := &request{ + method: "POST", + endpoint: "/sapi/v1/managed-subaccount/withdraw", + secType: secTypeSigned, + } + + r.setParam("fromEmail", s.fromEmail) + r.setParam("asset", s.asset) + r.setParam("amount", s.amount) + if s.transferDate > 0 { + r.setParam("transferDate", s.transferDate) + } + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + + res := &ManagedSubAccountWithdrawalResponse{} + if err := json.Unmarshal(data, res); err != nil { + return nil, err + } + + return res, nil +} + +// ManagedSubAccountAssetsService +// Query Managed Sub-account Asset Details(For Investor Master Account) +// https://binance-docs.github.io/apidocs/spot/en/#query-managed-sub-account-asset-details-for-investor-master-account +type ManagedSubAccountAssetsService struct { + c *Client + email string +} + +func (s *ManagedSubAccountAssetsService) Email(email string) *ManagedSubAccountAssetsService { + s.email = email + return s +} + +type ManagedSubAccountAsset struct { + Coin string `json:"coin"` + Name string `json:"name"` + TotalBalance string `json:"totalBalance"` + AvailableBalance string `json:"availableBalance"` + InOrder string `json:"inOrder"` + BtcValue string `json:"btcValue"` +} + +func (s *ManagedSubAccountAssetsService) Do(ctx context.Context, opts ...RequestOption) ([]*ManagedSubAccountAsset, error) { + r := &request{ + method: "GET", + endpoint: "/sapi/v1/managed-subaccount/asset", + secType: secTypeSigned, + } + + r.setParam("email", s.email) + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + + res := make([]*ManagedSubAccountAsset, 0) + if err := json.Unmarshal(data, &res); err != nil { + return nil, err + } + + return res, nil +} + +// SubAccountFuturesAccountService Get Detail on Sub-account's Futures Account (For Master Account) +// https://binance-docs.github.io/apidocs/spot/en/#get-detail-on-sub-account-39-s-futures-account-for-master-account +type SubAccountFuturesAccountService struct { + c *Client + email *string +} + +func (s *SubAccountFuturesAccountService) Email(v string) *SubAccountFuturesAccountService { + s.email = &v + return s +} + +func (s *SubAccountFuturesAccountService) Do(ctx context.Context, opts ...RequestOption) (res *SubAccountFuturesAccount, err error) { + r := &request{ + method: "GET", + endpoint: "/sapi/v1/sub-account/futures/account", + secType: secTypeSigned, + } + if s.email != nil { + r.setParam("email", *s.email) + } + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(SubAccountFuturesAccount) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +type SubAccountFuturesAccount struct { + Email string `json:"email"` + Asset string `json:"asset"` + Assets []SubAccountFuturesAccountAsset `json:"assets"` + CanDeposit bool `json:"canDeposit"` + CanTrade bool `json:"canTrade"` + CanWithdraw bool `json:"canWithdraw"` + FeeTier int `json:"feeTier"` + MaxWithdrawAmount string `json:"maxWithdrawAmount"` + TotalInitialMargin string `json:"totalInitialMargin"` + TotalMaintenanceMargin string `json:"totalMaintenanceMargin"` + TotalMarginBalance string `json:"totalMarginBalance"` + TotalOpenOrderInitialMargin string `json:"totalOpenOrderInitialMargin"` + TotalPositionInitialMargin string `json:"totalPositionInitialMargin"` + TotalUnrealizedProfit string `json:"totalUnrealizedProfit"` + TotalWalletBalance string `json:"totalWalletBalance"` + UpdateTime int64 `json:"updateTime"` +} + +type SubAccountFuturesAccountAsset struct { + Asset string `json:"asset"` + InitialMargin string `json:"initialMargin"` + MaintenanceMargin string `json:"maintenanceMargin"` + MarginBalance string `json:"marginBalance"` + MaxWithdrawAmount string `json:"maxWithdrawAmount"` + OpenOrderInitialMargin string `json:"openOrderInitialMargin"` + PositionInitialMargin string `json:"positionInitialMargin"` + UnrealizedProfit string `json:"unrealizedProfit"` + WalletBalance string `json:"walletBalance"` +} + // Get transfer history from sub-account // See https://binance-docs.github.io/apidocs/spot/en/#transfer-to-master-for-sub-account type SubTransferHistoryService struct { diff --git a/v2/subaccount_service_test.go b/v2/subaccount_service_test.go index 8641bae5..7ea06dd1 100644 --- a/v2/subaccount_service_test.go +++ b/v2/subaccount_service_test.go @@ -108,3 +108,196 @@ func (s *subAccountServiceTestSuite) assertSubAccountEqual(e, a SubAccount) { r.Equal(e.IsManagedSubAccount, a.IsManagedSubAccount, "IsManagedSubAccount") r.Equal(e.IsAssetManagementSubAccount, a.IsAssetManagementSubAccount, "IsAssetManagementSubAccount") } + +func (s *subAccountServiceTestSuite) TestSubManagedSubAccountDepositService() { + data := []byte(` { "tranId": 12345678 } `) + s.mockDo(data, nil) + defer s.assertDo() + + email := "testsub@gmail.com" + asset := "USDT" + amount := 1.0 + s.assertReq(func(r *request) { + e := newSignedRequest().setParams(params{ + "toEmail": email, + "asset": asset, + "amount": amount, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewManagedSubAccountDepositService(). + ToEmail(email). + Asset(asset). + Amount(amount). + Do(newContext()) + + r := s.r() + r.NoError(err) + + r.Equal(int64(12345678), res.ID) +} + +func (s *subAccountServiceTestSuite) TestSubManagedSubAccountWithdrawalService() { + data := []byte(` { "tranId": 12345678 } `) + s.mockDo(data, nil) + defer s.assertDo() + + email := "testsub@gmail.com" + asset := "USDT" + amount := 1.0 + s.assertReq(func(r *request) { + e := newSignedRequest().setParams(params{ + "fromEmail": email, + "asset": asset, + "amount": amount, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewManagedSubAccountWithdrawalService(). + FromEmail(email). + Asset(asset). + Amount(amount). + Do(newContext()) + + r := s.r() + r.NoError(err) + + r.Equal(int64(12345678), res.ID) +} + +func (s *subAccountServiceTestSuite) TestSubManagedSubAccountAssetsService() { + data := []byte(` + [ + { + "coin": "INJ", + "name": "Injective Protocol", + "totalBalance": "0", + "availableBalance": "0", + "inOrder": "0", + "btcValue": "0" + } + ] + `) + s.mockDo(data, nil) + defer s.assertDo() + + email := "testsub@gmail.com" + s.assertReq(func(r *request) { + e := newSignedRequest().setParams(params{"email": email}) + s.assertRequestEqual(e, r) + }) + + assets, err := s.client.NewManagedSubAccountAssetsService().Email(email).Do(newContext()) + + r := s.r() + r.NoError(err) + + s.assertAssetsEqual(&ManagedSubAccountAsset{ + Coin: "INJ", + Name: "Injective Protocol", + TotalBalance: "0", + AvailableBalance: "0", + InOrder: "0", + BtcValue: "0", + }, assets[0]) +} + +func (s *subAccountServiceTestSuite) assertAssetsEqual(e, a *ManagedSubAccountAsset) { + r := s.r() + r.Equal(e.Coin, a.Coin, "Coin") + r.Equal(e.Name, a.Name, "Name") + r.Equal(e.TotalBalance, a.TotalBalance, "TotalBalance") + r.Equal(e.AvailableBalance, a.AvailableBalance, "AvailableBalance") + r.Equal(e.InOrder, a.InOrder, "InOrder") + r.Equal(e.BtcValue, a.BtcValue, "BtcValue") +} + +func (s *subAccountServiceTestSuite) TestSubAccountFuturesService() { + data := []byte(` + { + "email": "abc@test.com", + "asset": "USDT", + "assets":[ + { + "asset": "USDT", + "initialMargin": "0.00000000", + "maintenanceMargin": "0.00000000", + "marginBalance": "0.88308000", + "maxWithdrawAmount": "0.88308000", + "openOrderInitialMargin": "0.00000000", + "positionInitialMargin": "0.00000000", + "unrealizedProfit": "0.00000000", + "walletBalance": "0.88308000" + } + ], + "canDeposit": true, + "canTrade": true, + "canWithdraw": true, + "feeTier": 2, + "maxWithdrawAmount": "0.88308000", + "totalInitialMargin": "0.00000000", + "totalMaintenanceMargin": "0.00000000", + "totalMarginBalance": "0.88308000", + "totalOpenOrderInitialMargin": "0.00000000", + "totalPositionInitialMargin": "0.00000000", + "totalUnrealizedProfit": "0.00000000", + "totalWalletBalance": "0.88308000", + "updateTime": 1576756674610 + } + `) + s.mockDo(data, nil) + defer s.assertDo() + + email := "abc@test.com" + s.assertReq(func(r *request) { + e := newSignedRequest().setParams(params{"email": email}) + s.assertRequestEqual(e, r) + }) + + account, err := s.client.NewSubAccountFuturesAccountService().Email(email).Do(newContext()) + + r := s.r() + r.NoError(err) + r.Equal("abc@test.com", account.Email, "Email") + r.Equal("USDT", account.Asset, "Asset") + r.Equal(true, account.CanDeposit, "CanDeposit") + r.Equal(true, account.CanTrade, "CanTrade") + r.Equal(true, account.CanWithdraw, "CanWithdraw") + r.Equal(2, account.FeeTier, "FeeTier") + r.Equal("0.88308000", account.MaxWithdrawAmount, "MaxWithdrawAmount") + r.Equal("0.00000000", account.TotalInitialMargin, "TotalInitialMargin") + r.Equal("0.00000000", account.TotalMaintenanceMargin, "TotalMaintenanceMargin") + r.Equal("0.88308000", account.TotalMarginBalance, "TotalMarginBalance") + r.Equal("0.00000000", account.TotalOpenOrderInitialMargin, "TotalOpenOrderInitialMargin") + r.Equal("0.00000000", account.TotalPositionInitialMargin, "TotalPositionInitialMargin") + r.Equal("0.00000000", account.TotalUnrealizedProfit, "TotalUnrealizedProfit") + r.Equal("0.88308000", account.TotalWalletBalance, "TotalWalletBalance") + r.Equal(int64(1576756674610), account.UpdateTime, "UpdateTime") + + s.assertAccountFuturesAssetsEqual(&SubAccountFuturesAccountAsset{ + Asset: "USDT", + InitialMargin: "0.00000000", + MaintenanceMargin: "0.00000000", + MarginBalance: "0.88308000", + MaxWithdrawAmount: "0.88308000", + OpenOrderInitialMargin: "0.00000000", + PositionInitialMargin: "0.00000000", + UnrealizedProfit: "0.00000000", + WalletBalance: "0.88308000", + }, &account.Assets[0]) +} + +func (s *subAccountServiceTestSuite) assertAccountFuturesAssetsEqual(e, a *SubAccountFuturesAccountAsset) { + r := s.r() + r.Equal(e.Asset, a.Asset, "Asset") + r.Equal(e.InitialMargin, a.InitialMargin, "InitialMargin") + r.Equal(e.MaintenanceMargin, a.MaintenanceMargin, "MaintenanceMargin") + r.Equal(e.MarginBalance, a.MarginBalance, "MarginBalance") + r.Equal(e.MaxWithdrawAmount, a.MaxWithdrawAmount, "MaxWithdrawAmount") + r.Equal(e.OpenOrderInitialMargin, a.OpenOrderInitialMargin, "OpenOrderInitialMargin") + r.Equal(e.PositionInitialMargin, a.PositionInitialMargin, "PositionInitialMargin") + r.Equal(e.UnrealizedProfit, a.UnrealizedProfit, "UnrealizedProfit") + r.Equal(e.WalletBalance, a.WalletBalance, "WalletBalance") +} diff --git a/v2/ticker_service.go b/v2/ticker_service.go index 1d0ab68c..ba631d8d 100644 --- a/v2/ticker_service.go +++ b/v2/ticker_service.go @@ -96,8 +96,9 @@ type SymbolPrice struct { // ListPriceChangeStatsService show stats of price change in last 24 hours for all symbols type ListPriceChangeStatsService struct { - c *Client - symbol *string + c *Client + symbol *string + symbols []string } // Symbol set symbol @@ -106,6 +107,12 @@ func (s *ListPriceChangeStatsService) Symbol(symbol string) *ListPriceChangeStat return s } +// Symbols set symbols +func (s *ListPriceChangeStatsService) Symbols(symbols []string) *ListPriceChangeStatsService { + s.symbols = symbols + return s +} + // Symbols set symbols func (s *ListPricesService) Symbols(symbols []string) *ListPricesService { s.symbols = symbols @@ -118,9 +125,13 @@ func (s *ListPriceChangeStatsService) Do(ctx context.Context, opts ...RequestOpt method: http.MethodGet, endpoint: "/api/v3/ticker/24hr", } + if s.symbol != nil { r.setParam("symbol", *s.symbol) + } else if s.symbols != nil { + r.setParam("symbols", s.symbols) } + data, err := s.c.callAPI(ctx, r, opts...) if err != nil { return res, err @@ -144,7 +155,9 @@ type PriceChangeStats struct { LastPrice string `json:"lastPrice"` LastQty string `json:"lastQty"` BidPrice string `json:"bidPrice"` + BidQty string `json:"bidQty"` AskPrice string `json:"askPrice"` + AskQty string `json:"askQty"` OpenPrice string `json:"openPrice"` HighPrice string `json:"highPrice"` LowPrice string `json:"lowPrice"` @@ -152,7 +165,7 @@ type PriceChangeStats struct { QuoteVolume string `json:"quoteVolume"` OpenTime int64 `json:"openTime"` CloseTime int64 `json:"closeTime"` - FristID int64 `json:"firstId"` + FirstID int64 `json:"firstId"` LastID int64 `json:"lastId"` Count int64 `json:"count"` } diff --git a/v2/ticker_service_test.go b/v2/ticker_service_test.go index db7cfb4b..ab7c7e57 100644 --- a/v2/ticker_service_test.go +++ b/v2/ticker_service_test.go @@ -233,7 +233,9 @@ func (s *tickerServiceTestSuite) TestPriceChangeStats() { "closeTime": 1499869899040, "firstId": 28385, "lastId": 28460, - "count": 76 + "count": 76, + "bidQty": "300.00000000", + "askQty": "400.00000000" }`) s.mockDo(data, nil) defer s.assertDo() @@ -263,9 +265,11 @@ func (s *tickerServiceTestSuite) TestPriceChangeStats() { Volume: "8913.30000000", OpenTime: 1499783499040, CloseTime: 1499869899040, - FristID: 28385, + FirstID: 28385, LastID: 28460, Count: 76, + BidQty: "300.00000000", + AskQty: "400.00000000", } s.assertPriceChangeStatsEqual(e, stats[0]) } @@ -287,11 +291,108 @@ func (s *tickerServiceTestSuite) assertPriceChangeStatsEqual(e, a *PriceChangeSt r.Equal(e.Volume, a.Volume, "Volume") r.Equal(e.OpenTime, a.OpenTime, "OpenTime") r.Equal(e.CloseTime, a.CloseTime, "CloseTime") - r.Equal(e.FristID, a.FristID, "FristID") + r.Equal(e.FirstID, a.FirstID, "FirstID") r.Equal(e.LastID, a.LastID, "LastID") r.Equal(e.Count, a.Count, "Count") } +func (s *tickerServiceTestSuite) TestMultiplePriceChangeStats() { + data := []byte(`[{ + "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", + "quoteVolume": "15.30000000", + "openTime": 1499783499040, + "closeTime": 1499869899040, + "firstId": 28385, + "lastId": 28460, + "count": 76 + },{ + "symbol": "ETHBTC", + "priceChange": "-194.99999800", + "priceChangePercent": "-195.960", + "weightedAvgPrice": "10.29628482", + "prevClosePrice": "10.10002000", + "lastPrice": "14.00000200", + "lastQty": "1200.00000000", + "bidPrice": "14.00000000", + "askPrice": "14.00000200", + "openPrice": "199.00000000", + "highPrice": "1100.00000000", + "lowPrice": "10.10000000", + "volume": "18913.30000000", + "quoteVolume": "115.30000000", + "openTime": 1499783499041, + "closeTime": 1499869899041, + "firstId": 28381, + "lastId": 28461, + "count": 71 + }]`) + s.mockDo(data, nil) + defer s.assertDo() + + s.assertReq(func(r *request) { + e := newRequest().setParam("symbols", `["BNBBTC","ETHBTC"]`) + s.assertRequestEqual(e, r) + }) + res, err := s.client.NewListPriceChangeStatsService().Symbols([]string{"BNBBTC", "ETHBTC"}).Do(newContext()) + r := s.r() + r.NoError(err) + e := []*PriceChangeStats{ + { + Symbol: "BNBBTC", + PriceChange: "-94.99999800", + PriceChangePercent: "-95.960", + WeightedAvgPrice: "0.29628482", + PrevClosePrice: "0.10002000", + LastPrice: "4.00000200", + BidPrice: "4.00000000", + AskPrice: "4.00000200", + OpenPrice: "99.00000000", + HighPrice: "100.00000000", + LowPrice: "0.10000000", + Volume: "8913.30000000", + QuoteVolume: "15.30000000", + OpenTime: 1499783499040, + CloseTime: 1499869899040, + FirstID: 28385, + LastID: 28460, + Count: 76, + }, + { + Symbol: "ETHBTC", + PriceChange: "-194.99999800", + PriceChangePercent: "-195.960", + WeightedAvgPrice: "10.29628482", + PrevClosePrice: "10.10002000", + LastPrice: "14.00000200", + BidPrice: "14.00000000", + AskPrice: "14.00000200", + OpenPrice: "199.00000000", + HighPrice: "1100.00000000", + LowPrice: "10.10000000", + Volume: "18913.30000000", + QuoteVolume: "115.30000000", + OpenTime: 1499783499041, + CloseTime: 1499869899041, + FirstID: 28381, + LastID: 28461, + Count: 71, + }, + } + s.assertListPriceChangeStatsEqual(e, res) +} + func (s *tickerServiceTestSuite) TestListPriceChangeStats() { data := []byte(`[{ "symbol": "BNBBTC", @@ -341,7 +442,7 @@ func (s *tickerServiceTestSuite) TestListPriceChangeStats() { QuoteVolume: "15.30000000", OpenTime: 1499783499040, CloseTime: 1499869899040, - FristID: 28385, + FirstID: 28385, LastID: 28460, Count: 76, }, @@ -366,11 +467,10 @@ func (s *tickerServiceTestSuite) assertListPriceChangeStatsEqual(e, a []*PriceCh r.Equal(e[i].Volume, a[i].Volume, "Volume") r.Equal(e[i].OpenTime, a[i].OpenTime, "OpenTime") r.Equal(e[i].CloseTime, a[i].CloseTime, "CloseTime") - r.Equal(e[i].FristID, a[i].FristID, "FristID") + r.Equal(e[i].FirstID, a[i].FirstID, "FirstID") r.Equal(e[i].LastID, a[i].LastID, "LastID") r.Equal(e[i].Count, a[i].Count, "Count") } - } func (s *tickerServiceTestSuite) TestAveragePrice() {