Binance: Fix exchange order limits not populating (#1187)

* Binance: Fix Exchange Order Limits not populating

The order of the filters cannot be trusted. New filters have been added
after the 2nd filter, breaking all filter passing afterwards.

This adds a basic test that the data is being populated, but more could
be done on testing.

We should have stricter typing on the filters, perhaps by unmarshalling into
json.RawMessage initially and then into typed stuct fields based on the
filterType. Finally we should spot both missing and unhandled filters, at least in
tests.

* Binance: Add TODO for unhandled filterTypes
This commit is contained in:
Gareth Kirwan
2023-05-12 08:20:01 +01:00
committed by GitHub
parent 8309ddf80c
commit 6464a8a7e0
3 changed files with 163 additions and 53 deletions

View File

@@ -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

View File

@@ -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)
}
}
}
}

View File

@@ -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"`