mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-01 15:10:44 +00:00
orders: adds method to retrieve snapshot of order execution limit values (#946)
* orders: add method to Limit to retrieve order execution limit snapshots * currency/btcmarkets: add error and update field name to standard * linter: fix * limts: don't return pointer * limit: Add notes * glorious: nits * linter: fix * limit: reinstate nil check * exchanges: change field names to be more consistent (@thrasher-) suggestion Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -54,7 +54,7 @@ type Settings struct {
|
||||
MinimumSlippageRate decimal.Decimal
|
||||
MaximumSlippageRate decimal.Decimal
|
||||
|
||||
Limits *gctorder.Limits
|
||||
Limits gctorder.MinMaxLevel
|
||||
CanUseExchangeLimits bool
|
||||
SkipCandleVolumeFitting bool
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user