mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-03 15:10:49 +00:00
exchange/order/limits: Migrate to new package and integrate with exchanges (#1860)
* move limits, transition to key gen * rollout NewExchangePairAssetKey everywhere * test improvements * self-review fixes * ok, lets go * fix merge issue * slower value func,assertify,drop IsValidPairString * remove binance reference for backtesting test * Redundant nil checks removed due to redundancy * Update order_test.go * Move limits back into /exchanges/ * puts limits in a different box again * SHAZBERT SPECIAL SUGGESTIONS * Update gateio_wrapper.go * fixes all build issues * Many niteroos! * something has gone awry * bugfix * gk's everywhere nits * lint * extra lint * re-remove IsValidPairString * lint fix * standardise test * revert some bads * dupe rm * another revert 360 mcgee * un-in-revertify * Update exchange/order/limits/levels_test.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * fix * Update exchanges/binance/binance_test.go HERE'S HOPING GITHUB FORMATS THIS CORRECTLY! Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> * update text * rn func, same line err gk4202000 --------- Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>
This commit is contained in:
@@ -1,356 +0,0 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
// Public errors for order limits
|
||||
var (
|
||||
ErrLoadLimitsFailed = errors.New("failed to load exchange limits")
|
||||
ErrExchangeLimitNotLoaded = errors.New("exchange limits not loaded")
|
||||
ErrPriceBelowMin = errors.New("price below minimum limit")
|
||||
ErrPriceExceedsMax = errors.New("price exceeds maximum limit")
|
||||
ErrPriceExceedsStep = errors.New("price exceeds step limit") // price is not divisible by its step
|
||||
ErrAmountBelowMin = errors.New("amount below minimum limit")
|
||||
ErrAmountExceedsMax = errors.New("amount exceeds maximum limit")
|
||||
ErrAmountExceedsStep = errors.New("amount exceeds step limit") // amount is not divisible by its step
|
||||
ErrNotionalValue = errors.New("total notional value is under minimum limit")
|
||||
ErrMarketAmountBelowMin = errors.New("market order amount below minimum limit")
|
||||
ErrMarketAmountExceedsMax = errors.New("market order amount exceeds maximum limit")
|
||||
ErrMarketAmountExceedsStep = errors.New("market order amount exceeds step limit") // amount is not divisible by its step for a market order
|
||||
ErrCannotValidateAsset = errors.New("cannot check limit, asset not loaded")
|
||||
ErrCannotValidateBaseCurrency = errors.New("cannot check limit, base currency not loaded")
|
||||
ErrCannotValidateQuoteCurrency = errors.New("cannot check limit, quote currency not loaded")
|
||||
)
|
||||
|
||||
var (
|
||||
errExchangeLimitBase = errors.New("exchange limits not found for base currency")
|
||||
errExchangeLimitQuote = errors.New("exchange limits not found for quote currency")
|
||||
errCannotLoadLimit = errors.New("cannot load limit, levels not supplied")
|
||||
errInvalidPriceLevels = errors.New("invalid price levels, cannot load limits")
|
||||
errInvalidAmountLevels = errors.New("invalid amount levels, cannot load limits")
|
||||
errInvalidQuoteLevels = errors.New("invalid quote levels, cannot load limits")
|
||||
)
|
||||
|
||||
// ExecutionLimits defines minimum and maximum values in relation to
|
||||
// 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]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
|
||||
PriceStepIncrementSize float64
|
||||
MultiplierUp float64
|
||||
MultiplierDown float64
|
||||
MultiplierDecimal float64
|
||||
AveragePriceMinutes int64
|
||||
MinimumBaseAmount float64
|
||||
MaximumBaseAmount float64
|
||||
MinimumQuoteAmount float64
|
||||
MaximumQuoteAmount float64
|
||||
AmountStepIncrementSize float64
|
||||
QuoteStepIncrementSize float64
|
||||
MinNotional float64
|
||||
MaxIcebergParts int64
|
||||
MarketMinQty float64
|
||||
MarketMaxQty float64
|
||||
MarketStepIncrementSize float64
|
||||
MaxTotalOrders int64
|
||||
MaxAlgoOrders int64
|
||||
}
|
||||
|
||||
// LoadLimits loads all limits levels into memory
|
||||
func (e *ExecutionLimits) LoadLimits(levels []MinMaxLevel) error {
|
||||
if len(levels) == 0 {
|
||||
return errCannotLoadLimit
|
||||
}
|
||||
e.mtx.Lock()
|
||||
defer e.mtx.Unlock()
|
||||
if e.m == nil {
|
||||
e.m = make(map[asset.Item]map[*currency.Item]map[*currency.Item]MinMaxLevel)
|
||||
}
|
||||
|
||||
for x := range levels {
|
||||
if !levels[x].Asset.IsValid() {
|
||||
return fmt.Errorf("cannot load levels for %q: %w", levels[x].Asset, asset.ErrNotSupported)
|
||||
}
|
||||
m1, ok := e.m[levels[x].Asset]
|
||||
if !ok {
|
||||
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]MinMaxLevel)
|
||||
m1[levels[x].Pair.Base.Item] = m2
|
||||
}
|
||||
|
||||
if levels[x].MinPrice > 0 &&
|
||||
levels[x].MaxPrice > 0 &&
|
||||
levels[x].MinPrice > levels[x].MaxPrice {
|
||||
return fmt.Errorf("%w for %s %s supplied min: %f max: %f",
|
||||
errInvalidPriceLevels,
|
||||
levels[x].Asset,
|
||||
levels[x].Pair,
|
||||
levels[x].MinPrice,
|
||||
levels[x].MaxPrice)
|
||||
}
|
||||
|
||||
if levels[x].MinimumBaseAmount > 0 &&
|
||||
levels[x].MaximumBaseAmount > 0 &&
|
||||
levels[x].MinimumBaseAmount > levels[x].MaximumBaseAmount {
|
||||
return fmt.Errorf("%w for %s %s supplied min: %f max: %f",
|
||||
errInvalidAmountLevels,
|
||||
levels[x].Asset,
|
||||
levels[x].Pair,
|
||||
levels[x].MinimumBaseAmount,
|
||||
levels[x].MaximumBaseAmount)
|
||||
}
|
||||
|
||||
if levels[x].MinimumQuoteAmount > 0 &&
|
||||
levels[x].MaximumQuoteAmount > 0 &&
|
||||
levels[x].MinimumQuoteAmount > levels[x].MaximumQuoteAmount {
|
||||
return fmt.Errorf("%w for %s %s supplied min: %f max: %f",
|
||||
errInvalidQuoteLevels,
|
||||
levels[x].Asset,
|
||||
levels[x].Pair,
|
||||
levels[x].MinimumQuoteAmount,
|
||||
levels[x].MaximumQuoteAmount)
|
||||
}
|
||||
|
||||
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) (MinMaxLevel, error) {
|
||||
e.mtx.RLock()
|
||||
defer e.mtx.RUnlock()
|
||||
if e.m == nil {
|
||||
return MinMaxLevel{}, ErrExchangeLimitNotLoaded
|
||||
}
|
||||
|
||||
m1, ok := e.m[a]
|
||||
if !ok {
|
||||
return MinMaxLevel{}, fmt.Errorf("%w %v", ErrCannotValidateAsset, a)
|
||||
}
|
||||
|
||||
m2, ok := m1[cp.Base.Item]
|
||||
if !ok {
|
||||
return MinMaxLevel{}, fmt.Errorf("%w %v", errExchangeLimitBase, cp.Base)
|
||||
}
|
||||
|
||||
limit, ok := m2[cp.Quote.Item]
|
||||
if !ok {
|
||||
return MinMaxLevel{}, fmt.Errorf("%w %v", errExchangeLimitQuote, cp.Quote)
|
||||
}
|
||||
|
||||
return limit, nil
|
||||
}
|
||||
|
||||
// CheckOrderExecutionLimits checks to see if the price and amount conforms with
|
||||
// exchange level order execution limits
|
||||
func (e *ExecutionLimits) CheckOrderExecutionLimits(a asset.Item, cp currency.Pair, price, amount float64, orderType Type) error {
|
||||
e.mtx.RLock()
|
||||
defer e.mtx.RUnlock()
|
||||
|
||||
if e.m == nil {
|
||||
// No exchange limits loaded so we can nil this
|
||||
return nil
|
||||
}
|
||||
|
||||
m1, ok := e.m[a]
|
||||
if !ok {
|
||||
return ErrCannotValidateAsset
|
||||
}
|
||||
|
||||
m2, ok := m1[cp.Base.Item]
|
||||
if !ok {
|
||||
return ErrCannotValidateBaseCurrency
|
||||
}
|
||||
|
||||
limit, ok := m2[cp.Quote.Item]
|
||||
if !ok {
|
||||
return ErrCannotValidateQuoteCurrency
|
||||
}
|
||||
|
||||
err := limit.Conforms(price, amount, orderType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w for %s %s", err, a, cp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Conforms checks outbound parameters
|
||||
func (m *MinMaxLevel) Conforms(price, amount float64, orderType Type) error {
|
||||
// TODO: Update to take in account Quote amounts as well as Base amounts.
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.MinimumBaseAmount != 0 && amount < m.MinimumBaseAmount {
|
||||
return fmt.Errorf("%w min: %.8f supplied %.8f",
|
||||
ErrAmountBelowMin,
|
||||
m.MinimumBaseAmount,
|
||||
amount)
|
||||
}
|
||||
if m.MaximumBaseAmount != 0 && amount > m.MaximumBaseAmount {
|
||||
return fmt.Errorf("%w min: %.8f supplied %.8f",
|
||||
ErrAmountExceedsMax,
|
||||
m.MaximumBaseAmount,
|
||||
amount)
|
||||
}
|
||||
if m.AmountStepIncrementSize != 0 {
|
||||
dAmount := decimal.NewFromFloat(amount)
|
||||
dStep := decimal.NewFromFloat(m.AmountStepIncrementSize)
|
||||
if !dAmount.Mod(dStep).IsZero() {
|
||||
return fmt.Errorf("%w stepSize: %.8f supplied %.8f",
|
||||
ErrAmountExceedsStep,
|
||||
m.AmountStepIncrementSize,
|
||||
amount)
|
||||
}
|
||||
}
|
||||
|
||||
// Multiplier checking not done due to the fact we need coherence with the
|
||||
// last average price (TODO)
|
||||
// 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)
|
||||
// m.maxIcebergParts // How many components in an iceberg order
|
||||
|
||||
// Max total orders not done due to order manager limitations (TODO)
|
||||
// m.maxTotalOrders
|
||||
|
||||
// Max algo orders not done due to order manager limitations (TODO)
|
||||
// m.maxAlgoOrders
|
||||
|
||||
// If order type is Market we do not need to do price checks
|
||||
if orderType != Market {
|
||||
if m.MinPrice != 0 && price < m.MinPrice {
|
||||
return fmt.Errorf("%w min: %.8f supplied %.8f",
|
||||
ErrPriceBelowMin,
|
||||
m.MinPrice,
|
||||
price)
|
||||
}
|
||||
if m.MaxPrice != 0 && price > m.MaxPrice {
|
||||
return fmt.Errorf("%w max: %.8f supplied %.8f",
|
||||
ErrPriceExceedsMax,
|
||||
m.MaxPrice,
|
||||
price)
|
||||
}
|
||||
if m.MinNotional != 0 && (amount*price) < m.MinNotional {
|
||||
return fmt.Errorf("%w minimum notional: %.8f value of order %.8f",
|
||||
ErrNotionalValue,
|
||||
m.MinNotional,
|
||||
amount*price)
|
||||
}
|
||||
if m.PriceStepIncrementSize != 0 {
|
||||
dPrice := decimal.NewFromFloat(price)
|
||||
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,
|
||||
m.PriceStepIncrementSize,
|
||||
price)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.MarketMinQty != 0 &&
|
||||
m.MinimumBaseAmount < m.MarketMinQty &&
|
||||
amount < m.MarketMinQty {
|
||||
return fmt.Errorf("%w min: %.8f supplied %.8f",
|
||||
ErrMarketAmountBelowMin,
|
||||
m.MarketMinQty,
|
||||
amount)
|
||||
}
|
||||
if m.MarketMaxQty != 0 &&
|
||||
m.MaximumBaseAmount > m.MarketMaxQty &&
|
||||
amount > m.MarketMaxQty {
|
||||
return fmt.Errorf("%w max: %.8f supplied %.8f",
|
||||
ErrMarketAmountExceedsMax,
|
||||
m.MarketMaxQty,
|
||||
amount)
|
||||
}
|
||||
if m.MarketStepIncrementSize != 0 &&
|
||||
m.AmountStepIncrementSize != m.MarketStepIncrementSize {
|
||||
dAmount := decimal.NewFromFloat(amount)
|
||||
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,
|
||||
m.MarketStepIncrementSize,
|
||||
amount)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConformToDecimalAmount (POC) conforms amount to its amount interval
|
||||
func (m *MinMaxLevel) ConformToDecimalAmount(amount decimal.Decimal) decimal.Decimal {
|
||||
if m == nil {
|
||||
return amount
|
||||
}
|
||||
|
||||
dStep := decimal.NewFromFloat(m.AmountStepIncrementSize)
|
||||
if dStep.IsZero() || amount.Equal(dStep) {
|
||||
return amount
|
||||
}
|
||||
|
||||
if amount.LessThan(dStep) {
|
||||
return decimal.Zero
|
||||
}
|
||||
mod := amount.Mod(dStep)
|
||||
// subtract modulus to get the floor
|
||||
return amount.Sub(mod)
|
||||
}
|
||||
|
||||
// ConformToAmount (POC) conforms amount to its amount interval
|
||||
func (m *MinMaxLevel) ConformToAmount(amount float64) float64 {
|
||||
if m == nil {
|
||||
return amount
|
||||
}
|
||||
|
||||
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(m.AmountStepIncrementSize)
|
||||
// derive modulus
|
||||
mod := dAmount.Mod(dStep)
|
||||
// subtract modulus to get the floor
|
||||
return dAmount.Sub(mod).InexactFloat64()
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
var (
|
||||
btcusd = currency.NewBTCUSD()
|
||||
ltcusd = currency.NewPair(currency.LTC, currency.USD)
|
||||
btcltc = currency.NewPair(currency.BTC, currency.LTC)
|
||||
)
|
||||
|
||||
func TestLoadLimits(t *testing.T) {
|
||||
t.Parallel()
|
||||
e := ExecutionLimits{}
|
||||
err := e.LoadLimits(nil)
|
||||
assert.ErrorIs(t, err, errCannotLoadLimit)
|
||||
|
||||
invalidAsset := []MinMaxLevel{
|
||||
{
|
||||
Pair: btcusd,
|
||||
MinPrice: 100000,
|
||||
MaxPrice: 1000000,
|
||||
MinimumBaseAmount: 1,
|
||||
MaximumBaseAmount: 10,
|
||||
},
|
||||
}
|
||||
err = e.LoadLimits(invalidAsset)
|
||||
require.ErrorIs(t, err, asset.ErrNotSupported)
|
||||
|
||||
invalidPairLoading := []MinMaxLevel{
|
||||
{
|
||||
Asset: asset.Spot,
|
||||
MinPrice: 100000,
|
||||
MaxPrice: 1000000,
|
||||
MinimumBaseAmount: 1,
|
||||
MaximumBaseAmount: 10,
|
||||
},
|
||||
}
|
||||
|
||||
err = e.LoadLimits(invalidPairLoading)
|
||||
assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty)
|
||||
|
||||
newLimits := []MinMaxLevel{
|
||||
{
|
||||
Pair: btcusd,
|
||||
Asset: asset.Spot,
|
||||
MinPrice: 100000,
|
||||
MaxPrice: 1000000,
|
||||
MinimumBaseAmount: 1,
|
||||
MaximumBaseAmount: 10,
|
||||
},
|
||||
}
|
||||
|
||||
err = e.LoadLimits(newLimits)
|
||||
require.NoError(t, err)
|
||||
|
||||
badLimit := []MinMaxLevel{
|
||||
{
|
||||
Pair: btcusd,
|
||||
Asset: asset.Spot,
|
||||
MinPrice: 2,
|
||||
MaxPrice: 1,
|
||||
MinimumBaseAmount: 1,
|
||||
MaximumBaseAmount: 10,
|
||||
},
|
||||
}
|
||||
|
||||
err = e.LoadLimits(badLimit)
|
||||
require.ErrorIs(t, err, errInvalidPriceLevels)
|
||||
|
||||
badLimit = []MinMaxLevel{
|
||||
{
|
||||
Pair: btcusd,
|
||||
Asset: asset.Spot,
|
||||
MinPrice: 1,
|
||||
MaxPrice: 2,
|
||||
MinimumBaseAmount: 10,
|
||||
MaximumBaseAmount: 9,
|
||||
},
|
||||
}
|
||||
|
||||
err = e.LoadLimits(badLimit)
|
||||
require.ErrorIs(t, err, errInvalidAmountLevels)
|
||||
|
||||
goodLimit := []MinMaxLevel{
|
||||
{
|
||||
Pair: btcusd,
|
||||
Asset: asset.Spot,
|
||||
},
|
||||
}
|
||||
|
||||
err = e.LoadLimits(goodLimit)
|
||||
require.NoError(t, err)
|
||||
|
||||
noCompare := []MinMaxLevel{
|
||||
{
|
||||
Pair: btcusd,
|
||||
Asset: asset.Spot,
|
||||
MinimumBaseAmount: 10,
|
||||
},
|
||||
}
|
||||
|
||||
err = e.LoadLimits(noCompare)
|
||||
require.NoError(t, err)
|
||||
|
||||
noCompare = []MinMaxLevel{
|
||||
{
|
||||
Pair: btcusd,
|
||||
Asset: asset.Spot,
|
||||
MinPrice: 10,
|
||||
},
|
||||
}
|
||||
|
||||
err = e.LoadLimits(noCompare)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetOrderExecutionLimits(t *testing.T) {
|
||||
t.Parallel()
|
||||
e := ExecutionLimits{}
|
||||
_, err := e.GetOrderExecutionLimits(asset.Spot, btcusd)
|
||||
require.ErrorIs(t, err, ErrExchangeLimitNotLoaded)
|
||||
|
||||
newLimits := []MinMaxLevel{
|
||||
{
|
||||
Pair: btcusd,
|
||||
Asset: asset.Spot,
|
||||
MinPrice: 100000,
|
||||
MaxPrice: 1000000,
|
||||
MinimumBaseAmount: 1,
|
||||
MaximumBaseAmount: 10,
|
||||
},
|
||||
}
|
||||
|
||||
err = e.LoadLimits(newLimits)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = e.GetOrderExecutionLimits(asset.Futures, ltcusd)
|
||||
require.ErrorIs(t, err, ErrCannotValidateAsset)
|
||||
|
||||
_, err = e.GetOrderExecutionLimits(asset.Spot, ltcusd)
|
||||
require.ErrorIs(t, err, errExchangeLimitBase)
|
||||
|
||||
_, err = e.GetOrderExecutionLimits(asset.Spot, btcltc)
|
||||
require.ErrorIs(t, err, errExchangeLimitQuote)
|
||||
|
||||
tt, err := e.GetOrderExecutionLimits(asset.Spot, btcusd)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newLimits[0].MaximumBaseAmount, tt.MaximumBaseAmount)
|
||||
assert.Equal(t, newLimits[0].MinimumBaseAmount, tt.MinimumBaseAmount)
|
||||
assert.Equal(t, newLimits[0].MaxPrice, tt.MaxPrice)
|
||||
assert.Equal(t, newLimits[0].MinPrice, tt.MinPrice)
|
||||
}
|
||||
|
||||
func TestCheckLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
e := ExecutionLimits{}
|
||||
err := e.CheckOrderExecutionLimits(asset.Spot, btcusd, 1337, 1337, Limit)
|
||||
require.NoError(t, err)
|
||||
|
||||
newLimits := []MinMaxLevel{
|
||||
{
|
||||
Pair: btcusd,
|
||||
Asset: asset.Spot,
|
||||
MinPrice: 100000,
|
||||
MaxPrice: 1000000,
|
||||
MinimumBaseAmount: 1,
|
||||
MaximumBaseAmount: 10,
|
||||
},
|
||||
}
|
||||
|
||||
err = e.LoadLimits(newLimits)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = e.CheckOrderExecutionLimits(asset.Futures, ltcusd, 1337, 1337, Limit)
|
||||
require.ErrorIs(t, err, ErrCannotValidateAsset)
|
||||
|
||||
err = e.CheckOrderExecutionLimits(asset.Spot, ltcusd, 1337, 1337, Limit)
|
||||
require.ErrorIs(t, err, ErrCannotValidateBaseCurrency)
|
||||
|
||||
err = e.CheckOrderExecutionLimits(asset.Spot, btcltc, 1337, 1337, Limit)
|
||||
require.ErrorIs(t, err, ErrCannotValidateQuoteCurrency)
|
||||
|
||||
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 1337, 9, Limit)
|
||||
require.ErrorIs(t, err, ErrPriceBelowMin)
|
||||
|
||||
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 1000001, 9, Limit)
|
||||
require.ErrorIs(t, err, ErrPriceExceedsMax)
|
||||
|
||||
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, .5, Limit)
|
||||
require.ErrorIs(t, err, ErrAmountBelowMin)
|
||||
|
||||
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, 11, Limit)
|
||||
require.ErrorIs(t, err, ErrAmountExceedsMax)
|
||||
|
||||
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, 7, Limit)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, 7, Market)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestConforms(t *testing.T) {
|
||||
t.Parallel()
|
||||
var tt MinMaxLevel
|
||||
err := tt.Conforms(0, 0, Limit)
|
||||
require.NoError(t, err)
|
||||
|
||||
tt = MinMaxLevel{
|
||||
MinNotional: 100,
|
||||
}
|
||||
|
||||
err = tt.Conforms(1, 1, Limit)
|
||||
require.ErrorIs(t, err, ErrNotionalValue)
|
||||
|
||||
err = tt.Conforms(200, .5, Limit)
|
||||
require.NoError(t, err)
|
||||
|
||||
tt.PriceStepIncrementSize = 0.001
|
||||
err = tt.Conforms(200.0001, .5, Limit)
|
||||
require.ErrorIs(t, err, ErrPriceExceedsStep)
|
||||
err = tt.Conforms(200.004, .5, Limit)
|
||||
require.NoError(t, err)
|
||||
|
||||
tt.AmountStepIncrementSize = 0.001
|
||||
err = tt.Conforms(200, .0002, Limit)
|
||||
require.ErrorIs(t, err, ErrAmountExceedsStep)
|
||||
err = tt.Conforms(200000, .003, Limit)
|
||||
require.NoError(t, err)
|
||||
|
||||
tt.MinimumBaseAmount = 1
|
||||
tt.MaximumBaseAmount = 10
|
||||
tt.MarketMinQty = 1.1
|
||||
tt.MarketMaxQty = 9.9
|
||||
|
||||
err = tt.Conforms(200000, 1, Market)
|
||||
require.ErrorIs(t, err, ErrMarketAmountBelowMin)
|
||||
|
||||
err = tt.Conforms(200000, 10, Market)
|
||||
require.ErrorIs(t, err, ErrMarketAmountExceedsMax)
|
||||
|
||||
tt.MarketStepIncrementSize = 10
|
||||
err = tt.Conforms(200000, 9.1, Market)
|
||||
require.ErrorIs(t, err, ErrMarketAmountExceedsStep)
|
||||
tt.MarketStepIncrementSize = 1
|
||||
err = tt.Conforms(200000, 9.1, Market)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestConformToDecimalAmount(t *testing.T) {
|
||||
t.Parallel()
|
||||
var tt MinMaxLevel
|
||||
require.True(t, tt.ConformToDecimalAmount(decimal.NewFromFloat(1.001)).Equal(decimal.NewFromFloat(1.001)))
|
||||
|
||||
tt = MinMaxLevel{}
|
||||
val := tt.ConformToDecimalAmount(decimal.NewFromInt(1))
|
||||
assert.True(t, val.Equal(decimal.NewFromInt(1))) // If there is no step amount set, this should not change the inputted amount
|
||||
|
||||
tt.AmountStepIncrementSize = 0.001
|
||||
val = tt.ConformToDecimalAmount(decimal.NewFromFloat(1.001))
|
||||
assert.True(t, val.Equal(decimal.NewFromFloat(1.001)))
|
||||
|
||||
val = tt.ConformToDecimalAmount(decimal.NewFromFloat(0.0001))
|
||||
assert.True(t, val.IsZero())
|
||||
|
||||
val = tt.ConformToDecimalAmount(decimal.NewFromFloat(0.7777))
|
||||
assert.True(t, val.Equal(decimal.NewFromFloat(0.777)))
|
||||
|
||||
tt.AmountStepIncrementSize = 100
|
||||
val = tt.ConformToDecimalAmount(decimal.NewFromInt(100))
|
||||
assert.True(t, val.Equal(decimal.NewFromInt(100)))
|
||||
|
||||
val = tt.ConformToDecimalAmount(decimal.NewFromInt(200))
|
||||
assert.True(t, val.Equal(decimal.NewFromInt(200)))
|
||||
val = tt.ConformToDecimalAmount(decimal.NewFromInt(150))
|
||||
assert.True(t, val.Equal(decimal.NewFromInt(100)))
|
||||
}
|
||||
|
||||
func TestConformToAmount(t *testing.T) {
|
||||
t.Parallel()
|
||||
var tt MinMaxLevel
|
||||
require.Equal(t, 1.001, tt.ConformToAmount(1.001))
|
||||
|
||||
tt = MinMaxLevel{}
|
||||
val := tt.ConformToAmount(1)
|
||||
assert.Equal(t, 1.0, val, "ConformToAmount should return the same value with no step amount set")
|
||||
|
||||
tt.AmountStepIncrementSize = 0.001
|
||||
val = tt.ConformToAmount(1.001)
|
||||
assert.Equal(t, 1.001, val)
|
||||
|
||||
val = tt.ConformToAmount(0.0001)
|
||||
assert.Zero(t, val)
|
||||
|
||||
val = tt.ConformToAmount(0.7777)
|
||||
assert.Equal(t, 0.777, val)
|
||||
|
||||
tt.AmountStepIncrementSize = 100
|
||||
val = tt.ConformToAmount(100)
|
||||
assert.Equal(t, 100.0, val)
|
||||
|
||||
val = tt.ConformToAmount(200)
|
||||
require.Equal(t, 200.0, val)
|
||||
val = tt.ConformToAmount(150)
|
||||
assert.Equal(t, 100.0, val)
|
||||
}
|
||||
@@ -1569,24 +1569,25 @@ func TestGetOrdersRequest_Filter(t *testing.T) {
|
||||
request.AssetType = asset.Spot
|
||||
request.Type = AnyType
|
||||
request.Side = AnySide
|
||||
|
||||
BTCUSD := currency.NewBTCUSD()
|
||||
LTCUSD := currency.NewPair(currency.LTC, currency.USD)
|
||||
orders := []Detail{
|
||||
{OrderID: "0", Pair: btcusd, AssetType: asset.Spot, Type: Limit, Side: Buy},
|
||||
{OrderID: "1", Pair: btcusd, AssetType: asset.Spot, Type: Limit, Side: Sell},
|
||||
{OrderID: "2", Pair: btcusd, AssetType: asset.Spot, Type: Market, Side: Buy},
|
||||
{OrderID: "3", Pair: btcusd, AssetType: asset.Spot, Type: Market, Side: Sell},
|
||||
{OrderID: "4", Pair: btcusd, AssetType: asset.Futures, Type: Limit, Side: Buy},
|
||||
{OrderID: "5", Pair: btcusd, AssetType: asset.Futures, Type: Limit, Side: Sell},
|
||||
{OrderID: "6", Pair: btcusd, AssetType: asset.Futures, Type: Market, Side: Buy},
|
||||
{OrderID: "7", Pair: btcusd, AssetType: asset.Futures, Type: Market, Side: Sell},
|
||||
{OrderID: "8", Pair: btcltc, AssetType: asset.Spot, Type: Limit, Side: Buy},
|
||||
{OrderID: "9", Pair: btcltc, AssetType: asset.Spot, Type: Limit, Side: Sell},
|
||||
{OrderID: "10", Pair: btcltc, AssetType: asset.Spot, Type: Market, Side: Buy},
|
||||
{OrderID: "11", Pair: btcltc, AssetType: asset.Spot, Type: Market, Side: Sell},
|
||||
{OrderID: "12", Pair: btcltc, AssetType: asset.Futures, Type: Limit, Side: Buy},
|
||||
{OrderID: "13", Pair: btcltc, AssetType: asset.Futures, Type: Limit, Side: Sell},
|
||||
{OrderID: "14", Pair: btcltc, AssetType: asset.Futures, Type: Market, Side: Buy},
|
||||
{OrderID: "15", Pair: btcltc, AssetType: asset.Futures, Type: Market, Side: Sell},
|
||||
{OrderID: "0", Pair: BTCUSD, AssetType: asset.Spot, Type: Limit, Side: Buy},
|
||||
{OrderID: "1", Pair: BTCUSD, AssetType: asset.Spot, Type: Limit, Side: Sell},
|
||||
{OrderID: "2", Pair: BTCUSD, AssetType: asset.Spot, Type: Market, Side: Buy},
|
||||
{OrderID: "3", Pair: BTCUSD, AssetType: asset.Spot, Type: Market, Side: Sell},
|
||||
{OrderID: "4", Pair: BTCUSD, AssetType: asset.Futures, Type: Limit, Side: Buy},
|
||||
{OrderID: "5", Pair: BTCUSD, AssetType: asset.Futures, Type: Limit, Side: Sell},
|
||||
{OrderID: "6", Pair: BTCUSD, AssetType: asset.Futures, Type: Market, Side: Buy},
|
||||
{OrderID: "7", Pair: BTCUSD, AssetType: asset.Futures, Type: Market, Side: Sell},
|
||||
{OrderID: "8", Pair: LTCUSD, AssetType: asset.Spot, Type: Limit, Side: Buy},
|
||||
{OrderID: "9", Pair: LTCUSD, AssetType: asset.Spot, Type: Limit, Side: Sell},
|
||||
{OrderID: "10", Pair: LTCUSD, AssetType: asset.Spot, Type: Market, Side: Buy},
|
||||
{OrderID: "11", Pair: LTCUSD, AssetType: asset.Spot, Type: Market, Side: Sell},
|
||||
{OrderID: "12", Pair: LTCUSD, AssetType: asset.Futures, Type: Limit, Side: Buy},
|
||||
{OrderID: "13", Pair: LTCUSD, AssetType: asset.Futures, Type: Limit, Side: Sell},
|
||||
{OrderID: "14", Pair: LTCUSD, AssetType: asset.Futures, Type: Market, Side: Buy},
|
||||
{OrderID: "15", Pair: LTCUSD, AssetType: asset.Futures, Type: Market, Side: Sell},
|
||||
}
|
||||
|
||||
shinyAndClean := request.Filter("test", orders)
|
||||
@@ -1596,7 +1597,7 @@ func TestGetOrdersRequest_Filter(t *testing.T) {
|
||||
require.Equal(t, strconv.FormatInt(int64(x), 10), shinyAndClean[x].OrderID)
|
||||
}
|
||||
|
||||
request.Pairs = []currency.Pair{btcltc}
|
||||
request.Pairs = []currency.Pair{LTCUSD}
|
||||
|
||||
// Kicks off time error
|
||||
request.EndTime = time.Unix(1336, 0)
|
||||
|
||||
Reference in New Issue
Block a user