diff --git a/backtester/backtest/backtest.go b/backtester/backtest/backtest.go index 60c308ce..a5650a9a 100644 --- a/backtester/backtest/backtest.go +++ b/backtester/backtest/backtest.go @@ -485,7 +485,7 @@ func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange return resp, err } - if limits != nil { + if limits != (gctorder.MinMaxLevel{}) { if !cfg.CurrencySettings[i].CanUseExchangeLimits { log.Warnf(log.BackTester, "exchange %s order execution limits supported but disabled for %s %s, live results may differ", cfg.CurrencySettings[i].ExchangeName, diff --git a/backtester/eventhandlers/exchange/exchange_types.go b/backtester/eventhandlers/exchange/exchange_types.go index 382963b7..c265082d 100644 --- a/backtester/eventhandlers/exchange/exchange_types.go +++ b/backtester/eventhandlers/exchange/exchange_types.go @@ -54,7 +54,7 @@ type Settings struct { MinimumSlippageRate decimal.Decimal MaximumSlippageRate decimal.Decimal - Limits *gctorder.Limits + Limits gctorder.MinMaxLevel CanUseExchangeLimits bool SkipCandleVolumeFitting bool } diff --git a/currency/code.go b/currency/code.go index 6cc151fa..1c703b34 100644 --- a/currency/code.go +++ b/currency/code.go @@ -11,14 +11,16 @@ import ( var ( // ErrCurrencyCodeEmpty defines an error if the currency code is empty ErrCurrencyCodeEmpty = errors.New("currency code is empty") - errItemIsNil = errors.New("item is nil") - errItemIsEmpty = errors.New("item is empty") - errRoleUnset = errors.New("role unset") - + // ErrCurrencyPairEmpty defines an error if the currency pair is empty + ErrCurrencyPairEmpty = errors.New("currency pair is empty") // EMPTYCODE is an empty currency code EMPTYCODE = Code{} // EMPTYPAIR is an empty currency pair EMPTYPAIR = Pair{} + + errItemIsNil = errors.New("item is nil") + errItemIsEmpty = errors.New("item is empty") + errRoleUnset = errors.New("role unset") ) func (r Role) String() string { diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 1df921b9..e196ba99 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -1209,24 +1209,24 @@ func (b *Binance) FetchSpotExchangeLimits(ctx context.Context) ([]order.MinMaxLe } limits = append(limits, order.MinMaxLevel{ - Pair: cp, - Asset: assets[z], - MinPrice: spot.Symbols[x].Filters[0].MinPrice, - MaxPrice: spot.Symbols[x].Filters[0].MaxPrice, - StepPrice: 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, - StepAmount: 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, - MarketStepSize: spot.Symbols[x].Filters[5].StepSize, - MaxTotalOrders: spot.Symbols[x].Filters[6].MaxNumOrders, - MaxAlgoOrders: spot.Symbols[x].Filters[7].MaxNumAlgoOrders, + 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, }) } } diff --git a/exchanges/binance/binance_cfutures.go b/exchanges/binance/binance_cfutures.go index 00f62b7e..af96cc2c 100644 --- a/exchanges/binance/binance_cfutures.go +++ b/exchanges/binance/binance_cfutures.go @@ -1499,22 +1499,22 @@ func (b *Binance) FetchCoinMarginExchangeLimits(ctx context.Context) ([]order.Mi } limits = append(limits, order.MinMaxLevel{ - Pair: cp, - Asset: asset.CoinMarginedFutures, - MinPrice: coinFutures.Symbols[x].Filters[0].MinPrice, - MaxPrice: coinFutures.Symbols[x].Filters[0].MaxPrice, - StepPrice: coinFutures.Symbols[x].Filters[0].TickSize, - MaxAmount: coinFutures.Symbols[x].Filters[1].MaxQty, - MinAmount: coinFutures.Symbols[x].Filters[1].MinQty, - StepAmount: coinFutures.Symbols[x].Filters[1].StepSize, - MarketMinQty: coinFutures.Symbols[x].Filters[2].MinQty, - MarketMaxQty: coinFutures.Symbols[x].Filters[2].MaxQty, - MarketStepSize: coinFutures.Symbols[x].Filters[2].StepSize, - MaxTotalOrders: coinFutures.Symbols[x].Filters[3].Limit, - MaxAlgoOrders: coinFutures.Symbols[x].Filters[4].Limit, - MultiplierUp: coinFutures.Symbols[x].Filters[5].MultiplierUp, - MultiplierDown: coinFutures.Symbols[x].Filters[5].MultiplierDown, - MultiplierDecimal: coinFutures.Symbols[x].Filters[5].MultiplierDecimal, + Pair: cp, + Asset: asset.CoinMarginedFutures, + MinPrice: coinFutures.Symbols[x].Filters[0].MinPrice, + MaxPrice: coinFutures.Symbols[x].Filters[0].MaxPrice, + PriceStepIncrementSize: coinFutures.Symbols[x].Filters[0].TickSize, + MaxAmount: coinFutures.Symbols[x].Filters[1].MaxQty, + MinAmount: coinFutures.Symbols[x].Filters[1].MinQty, + AmountStepIncrementSize: coinFutures.Symbols[x].Filters[1].StepSize, + MarketMinQty: coinFutures.Symbols[x].Filters[2].MinQty, + MarketMaxQty: coinFutures.Symbols[x].Filters[2].MaxQty, + MarketStepIncrementSize: coinFutures.Symbols[x].Filters[2].StepSize, + MaxTotalOrders: coinFutures.Symbols[x].Filters[3].Limit, + MaxAlgoOrders: coinFutures.Symbols[x].Filters[4].Limit, + MultiplierUp: coinFutures.Symbols[x].Filters[5].MultiplierUp, + MultiplierDown: coinFutures.Symbols[x].Filters[5].MultiplierDown, + MultiplierDecimal: coinFutures.Symbols[x].Filters[5].MultiplierDecimal, }) } return limits, nil diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index ee0585d2..645d38cc 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -2622,7 +2622,7 @@ func TestSetExchangeOrderExecutionLimits(t *testing.T) { t.Fatal(err) } - if limit == nil { + if limit == (order.MinMaxLevel{}) { t.Fatal("exchange limit should be loaded") } diff --git a/exchanges/binance/binance_ufutures.go b/exchanges/binance/binance_ufutures.go index 963f2dbe..415f459b 100644 --- a/exchanges/binance/binance_ufutures.go +++ b/exchanges/binance/binance_ufutures.go @@ -1169,23 +1169,23 @@ func (b *Binance) FetchUSDTMarginExchangeLimits(ctx context.Context) ([]order.Mi } limits = append(limits, order.MinMaxLevel{ - Pair: cp, - Asset: asset.USDTMarginedFutures, - MinPrice: usdtFutures.Symbols[x].Filters[0].MinPrice, - MaxPrice: usdtFutures.Symbols[x].Filters[0].MaxPrice, - StepPrice: usdtFutures.Symbols[x].Filters[0].TickSize, - MaxAmount: usdtFutures.Symbols[x].Filters[1].MaxQty, - MinAmount: usdtFutures.Symbols[x].Filters[1].MinQty, - StepAmount: usdtFutures.Symbols[x].Filters[1].StepSize, - MarketMinQty: usdtFutures.Symbols[x].Filters[2].MinQty, - MarketMaxQty: usdtFutures.Symbols[x].Filters[2].MaxQty, - MarketStepSize: usdtFutures.Symbols[x].Filters[2].StepSize, - MaxTotalOrders: usdtFutures.Symbols[x].Filters[3].Limit, - MaxAlgoOrders: usdtFutures.Symbols[x].Filters[4].Limit, - MinNotional: usdtFutures.Symbols[x].Filters[5].Notional, - MultiplierUp: usdtFutures.Symbols[x].Filters[6].MultiplierUp, - MultiplierDown: usdtFutures.Symbols[x].Filters[6].MultiplierDown, - MultiplierDecimal: usdtFutures.Symbols[x].Filters[6].MultiplierDecimal, + Pair: cp, + Asset: asset.USDTMarginedFutures, + MinPrice: usdtFutures.Symbols[x].Filters[0].MinPrice, + MaxPrice: usdtFutures.Symbols[x].Filters[0].MaxPrice, + PriceStepIncrementSize: usdtFutures.Symbols[x].Filters[0].TickSize, + MaxAmount: usdtFutures.Symbols[x].Filters[1].MaxQty, + MinAmount: usdtFutures.Symbols[x].Filters[1].MinQty, + AmountStepIncrementSize: usdtFutures.Symbols[x].Filters[1].StepSize, + MarketMinQty: usdtFutures.Symbols[x].Filters[2].MinQty, + MarketMaxQty: usdtFutures.Symbols[x].Filters[2].MaxQty, + MarketStepIncrementSize: usdtFutures.Symbols[x].Filters[2].StepSize, + MaxTotalOrders: usdtFutures.Symbols[x].Filters[3].Limit, + MaxAlgoOrders: usdtFutures.Symbols[x].Filters[4].Limit, + MinNotional: usdtFutures.Symbols[x].Filters[5].Notional, + MultiplierUp: usdtFutures.Symbols[x].Filters[6].MultiplierUp, + MultiplierDown: usdtFutures.Symbols[x].Filters[6].MultiplierDown, + MultiplierDecimal: usdtFutures.Symbols[x].Filters[6].MultiplierDecimal, }) } return limits, nil diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index a2c9506a..ae957b10 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -1112,7 +1112,7 @@ func TestUpdateOrderExecutionLimits(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) } - if lim == nil { + if lim == (order.MinMaxLevel{}) { t.Fatal("expected value return") } } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 31a98900..b92701e0 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -1157,12 +1157,12 @@ func (b *BTCMarkets) UpdateOrderExecutionLimits(ctx context.Context, a asset.Ite } limits[x] = order.MinMaxLevel{ - Pair: pair, - Asset: asset.Spot, - MinAmount: markets[x].MinOrderAmount, - MaxAmount: markets[x].MaxOrderAmount, - StepAmount: math.Pow(10, -markets[x].AmountDecimals), - StepPrice: math.Pow(10, -markets[x].PriceDecimals), + Pair: pair, + Asset: asset.Spot, + MinAmount: markets[x].MinOrderAmount, + MaxAmount: markets[x].MaxOrderAmount, + AmountStepIncrementSize: math.Pow(10, -markets[x].AmountDecimals), + PriceStepIncrementSize: math.Pow(10, -markets[x].PriceDecimals), } } return b.LoadLimits(limits) diff --git a/exchanges/ftx/ftx.go b/exchanges/ftx/ftx.go index 552b997b..ba23ea59 100644 --- a/exchanges/ftx/ftx.go +++ b/exchanges/ftx/ftx.go @@ -1534,11 +1534,11 @@ func (f *FTX) FetchExchangeLimits(ctx context.Context) ([]order.MinMaxLevel, err } limits = append(limits, order.MinMaxLevel{ - Pair: cp, - Asset: a, - StepPrice: data[x].PriceIncrement, - StepAmount: data[x].SizeIncrement, - MinAmount: data[x].MinProvideSize, + Pair: cp, + Asset: a, + PriceStepIncrementSize: data[x].PriceIncrement, + AmountStepIncrementSize: data[x].SizeIncrement, + MinAmount: data[x].MinProvideSize, }) } return limits, nil diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go index a4847954..159b2ebd 100644 --- a/exchanges/interfaces.go +++ b/exchanges/interfaces.go @@ -79,7 +79,7 @@ type IBotExchange interface { FlushWebsocketChannels() error AuthenticateWebsocket(ctx context.Context) error - GetOrderExecutionLimits(a asset.Item, cp currency.Pair) (*order.Limits, error) + GetOrderExecutionLimits(a asset.Item, cp currency.Pair) (order.MinMaxLevel, error) CheckOrderExecutionLimits(a asset.Item, cp currency.Pair, price, amount float64, orderType order.Type) error UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error diff --git a/exchanges/order/limits.go b/exchanges/order/limits.go index 74573a6a..60fda4a1 100644 --- a/exchanges/order/limits.go +++ b/exchanges/order/limits.go @@ -58,32 +58,32 @@ var ( // order size, order pricing, total notional values, total maximum orders etc // for execution on an exchange. type ExecutionLimits struct { - m map[asset.Item]map[*currency.Item]map[*currency.Item]*Limits + m map[asset.Item]map[*currency.Item]map[*currency.Item]MinMaxLevel mtx sync.RWMutex } // MinMaxLevel defines the minimum and maximum parameters for a currency pair // for outbound exchange execution type MinMaxLevel struct { - Pair currency.Pair - Asset asset.Item - MinPrice float64 - MaxPrice float64 - StepPrice float64 - MultiplierUp float64 - MultiplierDown float64 - MultiplierDecimal float64 - AveragePriceMinutes int64 - MinAmount float64 - MaxAmount float64 - StepAmount float64 - MinNotional float64 - MaxIcebergParts int64 - MarketMinQty float64 - MarketMaxQty float64 - MarketStepSize float64 - MaxTotalOrders int64 - MaxAlgoOrders int64 + Pair currency.Pair + Asset asset.Item + MinPrice float64 + MaxPrice float64 + PriceStepIncrementSize float64 + MultiplierUp float64 + MultiplierDown float64 + MultiplierDecimal float64 + AveragePriceMinutes int64 + MinAmount float64 + MaxAmount float64 + AmountStepIncrementSize float64 + MinNotional float64 + MaxIcebergParts int64 + MarketMinQty float64 + MarketMaxQty float64 + MarketStepIncrementSize float64 + MaxTotalOrders int64 + MaxAlgoOrders int64 } // LoadLimits loads all limits levels into memory @@ -94,7 +94,7 @@ func (e *ExecutionLimits) LoadLimits(levels []MinMaxLevel) error { e.mtx.Lock() defer e.mtx.Unlock() if e.m == nil { - e.m = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Limits) + e.m = make(map[asset.Item]map[*currency.Item]map[*currency.Item]MinMaxLevel) } for x := range levels { @@ -105,22 +105,20 @@ func (e *ExecutionLimits) LoadLimits(levels []MinMaxLevel) error { } m1, ok := e.m[levels[x].Asset] if !ok { - m1 = make(map[*currency.Item]map[*currency.Item]*Limits) + m1 = make(map[*currency.Item]map[*currency.Item]MinMaxLevel) e.m[levels[x].Asset] = m1 } + if levels[x].Pair.IsEmpty() { + return currency.ErrCurrencyPairEmpty + } + m2, ok := m1[levels[x].Pair.Base.Item] if !ok { - m2 = make(map[*currency.Item]*Limits) + m2 = make(map[*currency.Item]MinMaxLevel) m1[levels[x].Pair.Base.Item] = m2 } - limit, ok := m2[levels[x].Pair.Quote.Item] - if !ok { - limit = new(Limits) - m2[levels[x].Pair.Quote.Item] = limit - } - if levels[x].MinPrice > 0 && levels[x].MaxPrice > 0 && levels[x].MinPrice > levels[x].MaxPrice { @@ -142,50 +140,34 @@ func (e *ExecutionLimits) LoadLimits(levels []MinMaxLevel) error { levels[x].MinAmount, levels[x].MaxAmount) } - limit.m.Lock() - limit.minPrice = levels[x].MinPrice - limit.maxPrice = levels[x].MaxPrice - limit.stepIncrementSizePrice = levels[x].StepPrice - limit.minAmount = levels[x].MinAmount - limit.maxAmount = levels[x].MaxAmount - limit.stepIncrementSizeAmount = levels[x].StepAmount - limit.minNotional = levels[x].MinNotional - limit.multiplierUp = levels[x].MultiplierUp - limit.multiplierDown = levels[x].MultiplierDown - limit.averagePriceMinutes = levels[x].AveragePriceMinutes - limit.maxIcebergParts = levels[x].MaxIcebergParts - limit.marketMinQty = levels[x].MarketMinQty - limit.marketMaxQty = levels[x].MarketMaxQty - limit.marketStepIncrementSize = levels[x].MarketStepSize - limit.maxTotalOrders = levels[x].MaxTotalOrders - limit.maxAlgoOrders = levels[x].MaxAlgoOrders - limit.m.Unlock() + + m2[levels[x].Pair.Quote.Item] = levels[x] } return nil } // GetOrderExecutionLimits returns the exchange limit parameters for a currency -func (e *ExecutionLimits) GetOrderExecutionLimits(a asset.Item, cp currency.Pair) (*Limits, error) { +func (e *ExecutionLimits) GetOrderExecutionLimits(a asset.Item, cp currency.Pair) (MinMaxLevel, error) { e.mtx.RLock() defer e.mtx.RUnlock() if e.m == nil { - return nil, ErrExchangeLimitNotLoaded + return MinMaxLevel{}, ErrExchangeLimitNotLoaded } m1, ok := e.m[a] if !ok { - return nil, errExchangeLimitAsset + return MinMaxLevel{}, errExchangeLimitAsset } m2, ok := m1[cp.Base.Item] if !ok { - return nil, errExchangeLimitBase + return MinMaxLevel{}, errExchangeLimitBase } limit, ok := m2[cp.Quote.Item] if !ok { - return nil, errExchangeLimitQuote + return MinMaxLevel{}, errExchangeLimitQuote } return limit, nil @@ -225,136 +207,111 @@ func (e *ExecutionLimits) CheckOrderExecutionLimits(a asset.Item, cp currency.Pa return nil } -// Limits defines total limit values for an associated currency to be checked -// before execution on an exchange -type Limits struct { - minPrice float64 - maxPrice float64 - stepIncrementSizePrice float64 - minAmount float64 - maxAmount float64 - stepIncrementSizeAmount float64 - minNotional float64 - multiplierUp float64 - multiplierDown float64 - averagePriceMinutes int64 - maxIcebergParts int64 - marketMinQty float64 - marketMaxQty float64 - marketStepIncrementSize float64 - maxTotalOrders int64 - maxAlgoOrders int64 - m sync.RWMutex -} - // Conforms checks outbound parameters -func (l *Limits) Conforms(price, amount float64, orderType Type) error { - if l == nil { - // For when we return a nil pointer we can assume there's nothing to - // check +func (m *MinMaxLevel) Conforms(price, amount float64, orderType Type) error { + if m == nil { return nil } - l.m.RLock() - defer l.m.RUnlock() - if l.minAmount != 0 && amount < l.minAmount { + if m.MinAmount != 0 && amount < m.MinAmount { return fmt.Errorf("%w min: %.8f supplied %.8f", ErrAmountBelowMin, - l.minAmount, + m.MinAmount, amount) } - if l.maxAmount != 0 && amount > l.maxAmount { + if m.MaxAmount != 0 && amount > m.MaxAmount { return fmt.Errorf("%w min: %.8f supplied %.8f", ErrAmountExceedsMax, - l.maxAmount, + m.MaxAmount, amount) } - if l.stepIncrementSizeAmount != 0 { + if m.AmountStepIncrementSize != 0 { dAmount := decimal.NewFromFloat(amount) - dMinAmount := decimal.NewFromFloat(l.minAmount) - dStep := decimal.NewFromFloat(l.stepIncrementSizeAmount) + dMinAmount := decimal.NewFromFloat(m.MinAmount) + dStep := decimal.NewFromFloat(m.AmountStepIncrementSize) if !dAmount.Sub(dMinAmount).Mod(dStep).IsZero() { return fmt.Errorf("%w stepSize: %.8f supplied %.8f", ErrAmountExceedsStep, - l.stepIncrementSizeAmount, + m.AmountStepIncrementSize, amount) } } // Multiplier checking not done due to the fact we need coherence with the // last average price (TODO) - // l.multiplierUp will be used to determine how far our price can go up - // l.multiplierDown will be used to determine how far our price can go down - // l.averagePriceMinutes will be used to determine mean over this period + // m.multiplierUp will be used to determine how far our price can go up + // m.multiplierDown will be used to determine how far our price can go down + // m.averagePriceMinutes will be used to determine mean over this period // Max iceberg parts checking not done as we do not have that // functionality yet (TODO) - // l.maxIcebergParts // How many components in an iceberg order + // m.maxIcebergParts // How many components in an iceberg order // Max total orders not done due to order manager limitations (TODO) - // l.maxTotalOrders + // m.maxTotalOrders // Max algo orders not done due to order manager limitations (TODO) - // l.maxAlgoOrders + // m.maxAlgoOrders // If order type is Market we do not need to do price checks if orderType != Market { - if l.minPrice != 0 && price < l.minPrice { + if m.MinPrice != 0 && price < m.MinPrice { return fmt.Errorf("%w min: %.8f supplied %.8f", ErrPriceBelowMin, - l.minPrice, + m.MinPrice, price) } - if l.maxPrice != 0 && price > l.maxPrice { + if m.MaxPrice != 0 && price > m.MaxPrice { return fmt.Errorf("%w max: %.8f supplied %.8f", ErrPriceExceedsMax, - l.maxPrice, + m.MaxPrice, price) } - if l.minNotional != 0 && (amount*price) < l.minNotional { + if m.MinNotional != 0 && (amount*price) < m.MinNotional { return fmt.Errorf("%w minimum notional: %.8f value of order %.8f", ErrNotionalValue, - l.minNotional, + m.MinNotional, amount*price) } - if l.stepIncrementSizePrice != 0 { + if m.PriceStepIncrementSize != 0 { dPrice := decimal.NewFromFloat(price) - dMinPrice := decimal.NewFromFloat(l.minPrice) - dStep := decimal.NewFromFloat(l.stepIncrementSizePrice) + dMinPrice := decimal.NewFromFloat(m.MinPrice) + dStep := decimal.NewFromFloat(m.PriceStepIncrementSize) if !dPrice.Sub(dMinPrice).Mod(dStep).IsZero() { return fmt.Errorf("%w stepSize: %.8f supplied %.8f", ErrPriceExceedsStep, - l.stepIncrementSizePrice, + m.PriceStepIncrementSize, price) } } return nil } - if l.marketMinQty != 0 && - l.minAmount < l.marketMinQty && - amount < l.marketMinQty { + if m.MarketMinQty != 0 && + m.MinAmount < m.MarketMinQty && + amount < m.MarketMinQty { return fmt.Errorf("%w min: %.8f supplied %.8f", ErrMarketAmountBelowMin, - l.marketMinQty, + m.MarketMinQty, amount) } - if l.marketMaxQty != 0 && - l.maxAmount > l.marketMaxQty && - amount > l.marketMaxQty { + if m.MarketMaxQty != 0 && + m.MaxAmount > m.MarketMaxQty && + amount > m.MarketMaxQty { return fmt.Errorf("%w max: %.8f supplied %.8f", ErrMarketAmountExceedsMax, - l.marketMaxQty, + m.MarketMaxQty, amount) } - if l.marketStepIncrementSize != 0 && l.stepIncrementSizeAmount != l.marketStepIncrementSize { + if m.MarketStepIncrementSize != 0 && + m.AmountStepIncrementSize != m.MarketStepIncrementSize { dAmount := decimal.NewFromFloat(amount) - dMinMAmount := decimal.NewFromFloat(l.marketMinQty) - dStep := decimal.NewFromFloat(l.marketStepIncrementSize) + dMinMAmount := decimal.NewFromFloat(m.MarketMinQty) + dStep := decimal.NewFromFloat(m.MarketStepIncrementSize) if !dAmount.Sub(dMinMAmount).Mod(dStep).IsZero() { return fmt.Errorf("%w stepSize: %.8f supplied %.8f", ErrMarketAmountExceedsStep, - l.marketStepIncrementSize, + m.MarketStepIncrementSize, amount) } } @@ -362,13 +319,12 @@ func (l *Limits) Conforms(price, amount float64, orderType Type) error { } // ConformToDecimalAmount (POC) conforms amount to its amount interval -func (l *Limits) ConformToDecimalAmount(amount decimal.Decimal) decimal.Decimal { - if l == nil { +func (m *MinMaxLevel) ConformToDecimalAmount(amount decimal.Decimal) decimal.Decimal { + if m == nil { return amount } - l.m.Lock() - defer l.m.Unlock() - dStep := decimal.NewFromFloat(l.stepIncrementSizeAmount) + + dStep := decimal.NewFromFloat(m.AmountStepIncrementSize) if dStep.IsZero() || amount.Equal(dStep) { return amount } @@ -382,25 +338,22 @@ func (l *Limits) ConformToDecimalAmount(amount decimal.Decimal) decimal.Decimal } // ConformToAmount (POC) conforms amount to its amount interval -func (l *Limits) ConformToAmount(amount float64) float64 { - if l == nil { - // For when we return a nil pointer we can assume there's nothing to - // check - return amount - } - l.m.Lock() - defer l.m.Unlock() - if l.stepIncrementSizeAmount == 0 || amount == l.stepIncrementSizeAmount { +func (m *MinMaxLevel) ConformToAmount(amount float64) float64 { + if m == nil { return amount } - if amount < l.stepIncrementSizeAmount { + if m.AmountStepIncrementSize == 0 || amount == m.AmountStepIncrementSize { + return amount + } + + if amount < m.AmountStepIncrementSize { return 0 } // Convert floats to decimal types dAmount := decimal.NewFromFloat(amount) - dStep := decimal.NewFromFloat(l.stepIncrementSizeAmount) + dStep := decimal.NewFromFloat(m.AmountStepIncrementSize) // derive modulus mod := dAmount.Mod(dStep) // subtract modulus to get the floor diff --git a/exchanges/order/limits_test.go b/exchanges/order/limits_test.go index da8a0056..bc6bc8e8 100644 --- a/exchanges/order/limits_test.go +++ b/exchanges/order/limits_test.go @@ -9,9 +9,11 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) -var btcusd = currency.NewPair(currency.BTC, currency.USD) -var ltcusd = currency.NewPair(currency.LTC, currency.USD) -var btcltc = currency.NewPair(currency.BTC, currency.LTC) +var ( + btcusd = currency.NewPair(currency.BTC, currency.USD) + ltcusd = currency.NewPair(currency.LTC, currency.USD) + btcltc = currency.NewPair(currency.BTC, currency.LTC) +) func TestLoadLimits(t *testing.T) { t.Parallel() @@ -37,6 +39,21 @@ func TestLoadLimits(t *testing.T) { err) } + invalidPairLoading := []MinMaxLevel{ + { + Asset: asset.Spot, + MinPrice: 100000, + MaxPrice: 1000000, + MinAmount: 1, + MaxAmount: 10, + }, + } + + err = e.LoadLimits(invalidPairLoading) + if !errors.Is(err, currency.ErrCurrencyPairEmpty) { + t.Fatalf("expected error %v but received %v", currency.ErrCurrencyPairEmpty, err) + } + newLimits := []MinMaxLevel{ { Pair: btcusd, @@ -168,10 +185,10 @@ func TestGetOrderExecutionLimits(t *testing.T) { t.Fatalf("expected error %v but received %v", nil, err) } - if tt.maxAmount != newLimits[0].MaxAmount || - tt.minAmount != newLimits[0].MinAmount || - tt.maxPrice != newLimits[0].MaxPrice || - tt.minPrice != newLimits[0].MinPrice { + if tt.MaxAmount != newLimits[0].MaxAmount || + tt.MinAmount != newLimits[0].MinAmount || + tt.MaxPrice != newLimits[0].MaxPrice || + tt.MinPrice != newLimits[0].MinPrice { t.Fatal("unexpected values") } } @@ -248,14 +265,14 @@ func TestCheckLimit(t *testing.T) { func TestConforms(t *testing.T) { t.Parallel() - var tt *Limits + var tt MinMaxLevel err := tt.Conforms(0, 0, Limit) if err != nil { t.Fatal(err) } - tt = &Limits{ - minNotional: 100, + tt = MinMaxLevel{ + MinNotional: 100, } err = tt.Conforms(1, 1, Limit) @@ -268,7 +285,7 @@ func TestConforms(t *testing.T) { t.Fatalf("expected error %v but received %v", nil, err) } - tt.stepIncrementSizePrice = 0.001 + tt.PriceStepIncrementSize = 0.001 err = tt.Conforms(200.0001, .5, Limit) if !errors.Is(err, ErrPriceExceedsStep) { t.Fatalf("expected error %v but received %v", ErrPriceExceedsStep, err) @@ -278,7 +295,7 @@ func TestConforms(t *testing.T) { t.Fatalf("expected error %v but received %v", nil, err) } - tt.stepIncrementSizeAmount = 0.001 + tt.AmountStepIncrementSize = 0.001 err = tt.Conforms(200, .0002, Limit) if !errors.Is(err, ErrAmountExceedsStep) { t.Fatalf("expected error %v but received %v", ErrAmountExceedsStep, err) @@ -288,10 +305,10 @@ func TestConforms(t *testing.T) { t.Fatalf("expected error %v but received %v", nil, err) } - tt.minAmount = 1 - tt.maxAmount = 10 - tt.marketMinQty = 1.1 - tt.marketMaxQty = 9.9 + tt.MinAmount = 1 + tt.MaxAmount = 10 + tt.MarketMinQty = 1.1 + tt.MarketMaxQty = 9.9 err = tt.Conforms(200000, 1, Market) if !errors.Is(err, ErrMarketAmountBelowMin) { @@ -303,12 +320,12 @@ func TestConforms(t *testing.T) { t.Fatalf("expected error %v but received: %v", ErrMarketAmountExceedsMax, err) } - tt.marketStepIncrementSize = 10 + tt.MarketStepIncrementSize = 10 err = tt.Conforms(200000, 9.1, Market) if !errors.Is(err, ErrMarketAmountExceedsStep) { t.Fatalf("expected error %v but received: %v", ErrMarketAmountExceedsStep, err) } - tt.marketStepIncrementSize = 1 + tt.MarketStepIncrementSize = 1 err = tt.Conforms(200000, 9.1, Market) if !errors.Is(err, nil) { t.Fatalf("expected error %v but received: %v", nil, err) @@ -317,19 +334,19 @@ func TestConforms(t *testing.T) { func TestConformToDecimalAmount(t *testing.T) { t.Parallel() - var tt *Limits + var tt MinMaxLevel if !tt.ConformToDecimalAmount(decimal.NewFromFloat(1.001)).Equal(decimal.NewFromFloat(1.001)) { t.Fatal("value should not be changed") } - tt = &Limits{} + tt = MinMaxLevel{} val := tt.ConformToDecimalAmount(decimal.NewFromInt(1)) if !val.Equal(decimal.NewFromInt(1)) { // If there is no step amount set this should not change // the inputted amount t.Fatal("unexpected amount") } - tt.stepIncrementSizeAmount = 0.001 + tt.AmountStepIncrementSize = 0.001 val = tt.ConformToDecimalAmount(decimal.NewFromFloat(1.001)) if !val.Equal(decimal.NewFromFloat(1.001)) { t.Error("unexpected amount", val) @@ -345,7 +362,7 @@ func TestConformToDecimalAmount(t *testing.T) { t.Error("unexpected amount", val) } - tt.stepIncrementSizeAmount = 100 + tt.AmountStepIncrementSize = 100 val = tt.ConformToDecimalAmount(decimal.NewFromInt(100)) if !val.Equal(decimal.NewFromInt(100)) { t.Fatal("unexpected amount", val) @@ -363,19 +380,19 @@ func TestConformToDecimalAmount(t *testing.T) { func TestConformToAmount(t *testing.T) { t.Parallel() - var tt *Limits + var tt MinMaxLevel if tt.ConformToAmount(1.001) != 1.001 { t.Fatal("value should not be changed") } - tt = &Limits{} + tt = MinMaxLevel{} val := tt.ConformToAmount(1) if val != 1 { // If there is no step amount set this should not change // the inputted amount t.Fatal("unexpected amount") } - tt.stepIncrementSizeAmount = 0.001 + tt.AmountStepIncrementSize = 0.001 val = tt.ConformToAmount(1.001) if val != 1.001 { t.Error("unexpected amount", val) @@ -391,7 +408,7 @@ func TestConformToAmount(t *testing.T) { t.Error("unexpected amount", val) } - tt.stepIncrementSizeAmount = 100 + tt.AmountStepIncrementSize = 100 val = tt.ConformToAmount(100) if val != 100 { t.Fatal("unexpected amount", val) diff --git a/exchanges/sharedtestvalues/customex.go b/exchanges/sharedtestvalues/customex.go index 1e6b6bb1..908e288b 100644 --- a/exchanges/sharedtestvalues/customex.go +++ b/exchanges/sharedtestvalues/customex.go @@ -274,8 +274,8 @@ func (c *CustomEx) AuthenticateWebsocket(ctx context.Context) error { return nil } -func (c *CustomEx) GetOrderExecutionLimits(a asset.Item, cp currency.Pair) (*order.Limits, error) { - return nil, nil +func (c *CustomEx) GetOrderExecutionLimits(a asset.Item, cp currency.Pair) (order.MinMaxLevel, error) { + return order.MinMaxLevel{}, nil } func (c *CustomEx) CheckOrderExecutionLimits(a asset.Item, cp currency.Pair, price, amount float64, orderType order.Type) error {