diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 0c04a7f9..bf010308 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -1196,30 +1196,42 @@ func (b *Binance) FetchSpotExchangeLimits(ctx context.Context) ([]order.MinMaxLe } for z := range assets { - if len(spot.Symbols[x].Filters) < 8 { - continue + l := order.MinMaxLevel{ + Pair: cp, + Asset: assets[z], } - limits = append(limits, order.MinMaxLevel{ - Pair: cp, - Asset: assets[z], - MinPrice: spot.Symbols[x].Filters[0].MinPrice, - MaxPrice: spot.Symbols[x].Filters[0].MaxPrice, - PriceStepIncrementSize: spot.Symbols[x].Filters[0].TickSize, - MultiplierUp: spot.Symbols[x].Filters[1].MultiplierUp, - MultiplierDown: spot.Symbols[x].Filters[1].MultiplierDown, - AveragePriceMinutes: spot.Symbols[x].Filters[1].AvgPriceMinutes, - MaxAmount: spot.Symbols[x].Filters[2].MaxQty, - MinAmount: spot.Symbols[x].Filters[2].MinQty, - AmountStepIncrementSize: spot.Symbols[x].Filters[2].StepSize, - MinNotional: spot.Symbols[x].Filters[3].MinNotional, - MaxIcebergParts: spot.Symbols[x].Filters[4].Limit, - MarketMinQty: spot.Symbols[x].Filters[5].MinQty, - MarketMaxQty: spot.Symbols[x].Filters[5].MaxQty, - MarketStepIncrementSize: spot.Symbols[x].Filters[5].StepSize, - MaxTotalOrders: spot.Symbols[x].Filters[6].MaxNumOrders, - MaxAlgoOrders: spot.Symbols[x].Filters[7].MaxNumAlgoOrders, - }) + for _, f := range spot.Symbols[x].Filters { + // TODO: Unhandled filters: + // maxPosition, trailingDelta, percentPriceBySide, maxNumAlgoOrders + switch f.FilterType { + case priceFilter: + l.MinPrice = f.MinPrice + l.MaxPrice = f.MaxPrice + l.PriceStepIncrementSize = f.TickSize + case percentPriceFilter: + l.MultiplierUp = f.MultiplierUp + l.MultiplierDown = f.MultiplierDown + l.AveragePriceMinutes = f.AvgPriceMinutes + case lotSizeFilter: + l.MaxAmount = f.MaxQty + l.MinAmount = f.MinQty + l.AmountStepIncrementSize = f.StepSize + case notionalFilter: + l.MinNotional = f.MinNotional + case icebergPartsFilter: + l.MaxIcebergParts = f.Limit + case marketLotSizeFilter: + l.MarketMinQty = f.MinQty + l.MarketMaxQty = f.MaxQty + l.MarketStepIncrementSize = f.StepSize + case maxNumOrdersFilter: + l.MaxTotalOrders = f.MaxNumOrders + l.MaxAlgoOrders = f.MaxNumAlgoOrders + } + } + + limits = append(limits, l) } } return limits, nil diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 1822e602..e33dca9f 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -2734,3 +2734,84 @@ func TestFetchSpotExchangeLimits(t *testing.T) { t.Error("expected a response") } } + +func TestUpdateOrderExecutionLimits(t *testing.T) { + t.Parallel() + + tests := map[asset.Item]currency.Pair{ + asset.Spot: currency.NewPair(currency.BTC, currency.USDT), + asset.Margin: currency.NewPair(currency.ETH, currency.BTC), + } + for _, a := range []asset.Item{asset.CoinMarginedFutures, asset.USDTMarginedFutures} { + pairs, err := b.FetchTradablePairs(context.Background(), a) + if err != nil { + t.Errorf("Error fetching dated %s pairs for test: %v", a, err) + } + tests[a] = pairs[0] + } + + for _, a := range b.GetAssetTypes(false) { + if err := b.UpdateOrderExecutionLimits(context.Background(), a); err != nil { + t.Error("Binance UpdateOrderExecutionLimits() error", err) + continue + } + + p := tests[a] + limits, err := b.GetOrderExecutionLimits(a, p) + if err != nil { + t.Errorf("Binance GetOrderExecutionLimits() error during TestUpdateOrderExecutionLimits; Asset: %s Pair: %s Err: %v", a, p, err) + continue + } + if limits.MinPrice == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MinPrice; Asset: %s, Pair: %s, Got: %v", a, p, limits.MinPrice) + } + if limits.MaxPrice == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MaxPrice; Asset: %s, Pair: %s, Got: %v", a, p, limits.MaxPrice) + } + if limits.PriceStepIncrementSize == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty PriceStepIncrementSize; Asset: %s, Pair: %s, Got: %v", a, p, limits.PriceStepIncrementSize) + } + if limits.MinAmount == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MinAmount; Asset: %s, Pair: %s, Got: %v", a, p, limits.MinAmount) + } + if limits.MaxAmount == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MaxAmount; Asset: %s, Pair: %s, Got: %v", a, p, limits.MaxAmount) + } + if limits.AmountStepIncrementSize == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty AmountStepIncrementSize; Asset: %s, Pair: %s, Got: %v", a, p, limits.AmountStepIncrementSize) + } + if a == asset.USDTMarginedFutures && limits.MinNotional == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MinNotional; Asset: %s, Pair: %s, Got: %v", a, p, limits.MinNotional) + } + if limits.MarketMaxQty == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MarketMaxQty; Asset: %s, Pair: %s, Got: %v", a, p, limits.MarketMaxQty) + } + if limits.MaxTotalOrders == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MaxTotalOrders; Asset: %s, Pair: %s, Got: %v", a, p, limits.MaxTotalOrders) + } + + if a == asset.Spot || a == asset.Margin { + if limits.MaxIcebergParts == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MaxIcebergParts; Asset: %s, Pair: %s, Got: %v", a, p, limits.MaxIcebergParts) + } + } + + if a == asset.CoinMarginedFutures || a == asset.USDTMarginedFutures { + if limits.MultiplierUp == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MultiplierUp; Asset: %s, Pair: %s, Got: %v", a, p, limits.MultiplierUp) + } + if limits.MultiplierDown == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MultiplierDown; Asset: %s, Pair: %s, Got: %v", a, p, limits.MultiplierDown) + } + if limits.MarketMinQty == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MarketMinQty; Asset: %s, Pair: %s, Got: %v", a, p, limits.MarketMinQty) + } + if limits.MarketStepIncrementSize == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MarketStepIncrementSize; Asset: %s, Pair: %s, Got: %v", a, p, limits.MarketStepIncrementSize) + } + if limits.MaxAlgoOrders == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MaxAlgoOrders; Asset: %s, Pair: %s, Got: %v", a, p, limits.MaxAlgoOrders) + } + } + } +} diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index a686e988..b742d7a1 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -22,6 +22,21 @@ const ( Completed ) +type filterType string + +const ( + priceFilter filterType = "PRICE_FILTER" + lotSizeFilter filterType = "LOT_SIZE" + icebergPartsFilter filterType = "ICEBERG_PARTS" + marketLotSizeFilter filterType = "MARKET_LOT_SIZE" + trailingDeltaFilter filterType = "TRAILING_DELTA" + percentPriceFilter filterType = "PERCENT_PRICE" + percentPriceBySizeFilter filterType = "PERCENT_PRICE_BY_SIDE" + notionalFilter filterType = "NOTIONAL" + maxNumOrdersFilter filterType = "MAX_NUM_ORDERS" + maxNumAlgoOrdersFilter filterType = "MAX_NUM_ALGO_ORDERS" +) + // ExchangeInfo holds the full exchange information type type ExchangeInfo struct { Code int `json:"code"` @@ -35,40 +50,42 @@ type ExchangeInfo struct { } `json:"rateLimits"` ExchangeFilters interface{} `json:"exchangeFilters"` Symbols []struct { - Symbol string `json:"symbol"` - Status string `json:"status"` - BaseAsset string `json:"baseAsset"` - BaseAssetPrecision int `json:"baseAssetPrecision"` - QuoteAsset string `json:"quoteAsset"` - QuotePrecision int `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 []struct { - FilterType string `json:"filterType"` - MinPrice float64 `json:"minPrice,string"` - MaxPrice float64 `json:"maxPrice,string"` - TickSize float64 `json:"tickSize,string"` - MultiplierUp float64 `json:"multiplierUp,string"` - MultiplierDown float64 `json:"multiplierDown,string"` - AvgPriceMinutes int64 `json:"avgPriceMins"` - MinQty float64 `json:"minQty,string"` - MaxQty float64 `json:"maxQty,string"` - StepSize float64 `json:"stepSize,string"` - MinNotional float64 `json:"minNotional,string"` - ApplyToMarket bool `json:"applyToMarket"` - Limit int64 `json:"limit"` - MaxNumAlgoOrders int64 `json:"maxNumAlgoOrders"` - MaxNumIcebergOrders int64 `json:"maxNumIcebergOrders"` - MaxNumOrders int64 `json:"maxNumOrders"` - } `json:"filters"` - Permissions []string `json:"permissions"` + Symbol string `json:"symbol"` + Status string `json:"status"` + BaseAsset string `json:"baseAsset"` + BaseAssetPrecision int `json:"baseAssetPrecision"` + QuoteAsset string `json:"quoteAsset"` + QuotePrecision int `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 []*filterData `json:"filters"` + Permissions []string `json:"permissions"` } `json:"symbols"` } +type filterData struct { + FilterType filterType `json:"filterType"` + MinPrice float64 `json:"minPrice,string"` + MaxPrice float64 `json:"maxPrice,string"` + TickSize float64 `json:"tickSize,string"` + MultiplierUp float64 `json:"multiplierUp,string"` + MultiplierDown float64 `json:"multiplierDown,string"` + AvgPriceMinutes int64 `json:"avgPriceMins"` + MinQty float64 `json:"minQty,string"` + MaxQty float64 `json:"maxQty,string"` + StepSize float64 `json:"stepSize,string"` + MinNotional float64 `json:"minNotional,string"` + ApplyToMarket bool `json:"applyToMarket"` + Limit int64 `json:"limit"` + MaxNumAlgoOrders int64 `json:"maxNumAlgoOrders"` + MaxNumIcebergOrders int64 `json:"maxNumIcebergOrders"` + MaxNumOrders int64 `json:"maxNumOrders"` +} + // CoinInfo stores information about all supported coins type CoinInfo struct { Coin string `json:"coin"`