BTSE: Various fixes (#1550)

* Common: DriveBy sanitisation of vars

* BTSE: Unify to exchange OrderLimits

* BTSE: Remove SeedAssets and test GetOrderExcutionLimit

* BTSE: Fix handling for K_* pairs

In addition to the M_ pairs we previously handled, BTSE has now
introduced K_* pairs (K_SATS-USD*).

This change moves the handling over to look for an exponent, and moves
the filtering to happen on all market data.
The original MarketSummary is still availiable, but I can't see any of
our current use-cases wanting to get the unfiltered list

* BTSE: Fix marketSummary futures field

BTSE returns no futures field for futures api marketInfo, and the documentation for the futures api shows returning false.
When we know we asked for futures, it makes the data flow much saner if
we can trust this field and not have to track what asset we asked for

* BTSE: Abstract marketPair.Pair()

* BTSE: Fix UpdateTicker symbol format
This commit is contained in:
Gareth Kirwan
2024-06-14 10:09:19 +07:00
committed by GitHub
parent 4c4b6935be
commit 98f025e38f
7 changed files with 223 additions and 269 deletions

View File

@@ -7,12 +7,10 @@ import (
"strings"
)
// Public errors related to assets
var (
// ErrNotSupported is an error for an unsupported asset type
ErrNotSupported = errors.New("unsupported asset type")
// ErrNotEnabled is an error for an asset not enabled
ErrNotEnabled = errors.New("asset type not enabled")
// ErrInvalidAsset is returned when the assist isn't valid
ErrNotEnabled = errors.New("asset type not enabled")
ErrInvalidAsset = errors.New("asset is invalid")
)

View File

@@ -68,8 +68,9 @@ func (b *BTSE) FetchFundingHistory(ctx context.Context, symbol string) (map[stri
return resp, b.SendHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, btseFuturesFunding+params.Encode(), &resp, false, queryFunc)
}
// GetMarketSummary stores market summary data
func (b *BTSE) GetMarketSummary(ctx context.Context, symbol string, spot bool) (MarketSummary, error) {
// GetRawMarketSummary returns an unfiltered list of market pairs
// Consider using the wrapper function GetMarketSummary instead
func (b *BTSE) GetRawMarketSummary(ctx context.Context, symbol string, spot bool) (MarketSummary, error) {
var m MarketSummary
path := btseMarketOverview
if symbol != "" {
@@ -619,23 +620,7 @@ func parseOrderTime(timeStr string) (time.Time, error) {
return time.Parse(time.DateTime, timeStr)
}
// MillionPairs returns a map of symbol names which have a IsMillion equivalent
func (m *MarketSummary) MillionPairs() map[string]bool {
pairs := map[string]bool{}
for _, s := range *m {
if s.Active && s.HasLiquidity() && s.IsMillions() {
pairs[strings.TrimPrefix(s.Symbol, "M_")] = true
}
}
return pairs
}
// HasLiquidity returns if a market pair has a bid or ask != 0
func (m *MarketPair) HasLiquidity() bool {
return m.LowestAsk != 0 || m.HighestBid != 0
}
// IsMillions returns if a market pair represents a million of Base / Quote
func (m *MarketPair) IsMillions() bool {
return strings.HasPrefix(m.Symbol, "M_")
}

View File

@@ -144,6 +144,10 @@ func TestFormatExchangeKlineInterval(t *testing.T) {
func TestGetHistoricCandles(t *testing.T) {
t.Parallel()
r := b.Requester
b := new(BTSE) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(b), "Test exchange Setup must not error")
b.Requester = r
start := time.Now().AddDate(0, 0, -3)
_, err := b.GetHistoricCandles(context.Background(), spotPair, asset.Spot, kline.OneHour, start, time.Now())
assert.NoError(t, err, "GetHistoricCandles should not error")
@@ -154,6 +158,10 @@ func TestGetHistoricCandles(t *testing.T) {
func TestGetHistoricCandlesExtended(t *testing.T) {
t.Parallel()
r := b.Requester
b := new(BTSE) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(b), "Test exchange Setup must not error")
b.Requester = r
err := b.CurrencyPairs.StorePairs(asset.Futures, currency.Pairs{futuresPair}, true)
assert.NoError(t, err, "StorePairs should not error")
@@ -542,80 +550,28 @@ func TestMatchType(t *testing.T) {
assert.True(t, ret, "matchType should match")
}
func TestSeedOrderSizeLimits(t *testing.T) {
t.Parallel()
err := b.seedOrderSizeLimits(context.Background())
assert.NoError(t, err, "seedOrderSizeLimits should not error")
}
func TestOrderSizeLimits(t *testing.T) {
t.Parallel()
seedOrderSizeLimitMap()
_, ok := OrderSizeLimits(spotPair.String())
assert.True(t, ok, "OrderSizeLimits should find BTC-USD")
_, ok = OrderSizeLimits("XRP-GARBAGE")
assert.False(t, ok, "OrderSizeLimits should not find XRP-GARBAGE until the next bull market")
}
func seedOrderSizeLimitMap() {
testOrderSizeLimits := []struct {
name string
o OrderSizeLimit
}{
{
name: "XRP-USD",
o: OrderSizeLimit{
MinSizeIncrement: 1,
MinOrderSize: 1,
MaxOrderSize: 1000000,
},
},
{
name: "LTC-USD",
o: OrderSizeLimit{
MinSizeIncrement: 0.01,
MinOrderSize: 0.01,
MaxOrderSize: 5000,
},
},
{
name: "BTC-USD",
o: OrderSizeLimit{
MinSizeIncrement: 0.0001,
MinOrderSize: 1,
MaxOrderSize: 1000000,
},
},
}
orderSizeLimitMap.Range(func(key interface{}, _ interface{}) bool {
orderSizeLimitMap.Delete(key)
return true
})
for x := range testOrderSizeLimits {
orderSizeLimitMap.Store(testOrderSizeLimits[x].name, testOrderSizeLimits[x].o)
}
}
func TestWithinLimits(t *testing.T) {
// TestUpdateOrderExecutionLimits exercises UpdateOrderExecutionLimits
func TestUpdateOrderExecutionLimits(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, b)
seedOrderSizeLimitMap()
p, _ := currency.NewPairDelimiter("XRP-USD", "-")
assert.NoError(t, b.withinLimits(p, 1.0), "withinLimits should not error")
assert.NoError(t, b.withinLimits(p, 5.0000001), "withinLimits should not error")
assert.NoError(t, b.withinLimits(p, 100), "withinLimits should not error")
assert.NoError(t, b.withinLimits(p, 10.1), "withinLimits should not error")
for _, a := range b.GetAssetTypes(false) {
err := b.UpdateOrderExecutionLimits(context.Background(), a)
require.NoErrorf(t, err, "UpdateOrderExecutionLimits must not error for %s", a)
p.Base = currency.LTC
assert.NoError(t, b.withinLimits(p, 10), "withinLimits should not error")
assert.ErrorIs(t, b.withinLimits(p, 0.009), order.ErrAmountBelowMin, "withinLimits should error correctly")
pairs, err := b.GetAvailablePairs(a)
require.NoErrorf(t, err, "GetAvailablePairs must not error for %s", a)
require.NotEmpty(t, pairs, "GetAvailablePairs must return some pairs")
p.Base = currency.BTC
assert.NoError(t, b.withinLimits(p, 10), "withinLimits should not error")
assert.ErrorIs(t, b.withinLimits(p, 0.001), order.ErrAmountBelowMin, "withinLimits should error correctly")
for _, p := range pairs {
limits, err := b.GetOrderExecutionLimits(a, p)
require.NoErrorf(t, err, "GetOrderExecutionLimits must not error for %s %s", a, p)
assert.Positivef(t, limits.MinimumBaseAmount, "MinimumBaseAmount must be positive for %s %s", a, p)
assert.Positivef(t, limits.MaximumBaseAmount, "MaximumBaseAmount must be positive for %s %s", a, p)
assert.Positivef(t, limits.AmountStepIncrementSize, "AmountStepIncrementSize must be positive for %s %s", a, p)
assert.Positivef(t, limits.MinPrice, "MinPrice must be positive for %s %s", a, p)
assert.Positivef(t, limits.PriceStepIncrementSize, "PriceStepIncrementSize must be positive for %s %s", a, p)
}
}
}
func TestGetRecentTrades(t *testing.T) {
@@ -718,7 +674,11 @@ func TestIsPerpetualFutureCurrency(t *testing.T) {
func TestGetOpenInterest(t *testing.T) {
t.Parallel()
r := b.Requester
b := new(BTSE) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(b), "Test exchange Setup must not error")
testexch.UpdatePairsOnce(t, b)
b.Requester = r
cp1 := currency.NewPair(currency.BTC, currency.PFC)
cp2 := currency.NewPair(currency.ETH, currency.PFC)
sharedtestvalues.SetupCurrencyPairsForExchangeAsset(t, b, asset.Futures, futuresPair, cp1, cp2)
@@ -769,3 +729,23 @@ func TestGetCurrencyTradeURL(t *testing.T) {
assert.NotEmpty(t, resp)
}
}
// TestStripExponent exercises StripExponent
func TestStripExponent(t *testing.T) {
t.Parallel()
s, err := (&MarketPair{Symbol: "BTC-ETH"}).StripExponent()
assert.NoError(t, err, "Should not error on a symbol without exponent")
assert.Empty(t, s, "Should return an empty symbol without exponent")
for _, p := range []string{"B", "M", "K"} {
s, err = (&MarketPair{Symbol: p + "_BTC-ETH"}).StripExponent()
assert.NoError(t, err, "Should not error on a symbol with exponent")
assert.Equal(t, "BTC-ETH", s, "Should return the symbol without the exponent")
}
_, err = (&MarketPair{Symbol: "Z_BTC-ETH"}).StripExponent()
assert.ErrorIs(t, err, errInvalidPairSymbol, "Should error on a symbol with unknown exponent")
_, err = (&MarketPair{Symbol: "M_BTC_ETH"}).StripExponent()
assert.ErrorIs(t, err, errInvalidPairSymbol, "Should error on a symbol with too many underscores")
}

View File

@@ -1,7 +1,6 @@
package btse
import (
"sync"
"time"
)
@@ -354,16 +353,6 @@ type ErrorResponse struct {
Status int `json:"status"`
}
// OrderSizeLimit holds accepted minimum, maximum, and size increment when submitting new orders
type OrderSizeLimit struct {
MinOrderSize float64
MaxOrderSize float64
MinSizeIncrement float64
}
// orderSizeLimitMap map of OrderSizeLimit per currency
var orderSizeLimitMap sync.Map
// WsSubscriptionAcknowledgement contains successful subscription messages
type WsSubscriptionAcknowledgement struct {
Channel []string `json:"channel"`

View File

@@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"math"
"sort"
"strconv"
"strings"
@@ -33,6 +32,11 @@ import (
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
// Private Errors
var (
errInvalidPairSymbol = errors.New("invalid currency pair symbol")
)
// SetDefaults sets the basic defaults for BTSE
func (b *BTSE) SetDefaults() {
b.Name = "BTSE"
@@ -193,11 +197,6 @@ func (b *BTSE) Setup(exch *config.Exchange) error {
return err
}
err = b.seedOrderSizeLimits(context.TODO())
if err != nil {
return err
}
return b.Websocket.SetupNewConnection(stream.ConnectionSetup{
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
@@ -210,41 +209,16 @@ func (b *BTSE) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.P
if err != nil {
return nil, err
}
var errs error
pairs := make(currency.Pairs, 0, len(m))
mPairs := m.MillionPairs()
for _, l := range m {
if !l.Active || !l.HasLiquidity() ||
(a == asset.Spot && !l.IsMarketOpenToSpot) { // Skip OTC assets only tradable on web UI
continue
}
if mPairs[l.Symbol] {
// BTSE lists M_ symbols for very small pairs, in millions. For those listings, we want to take the M_ listing in preference
// to the native listing, since they're often going to appear as locked markets due to size (bid == ask, e.g. 0.0000000003)
continue
}
baseCurr := l.Base
var quoteCurr string
if a == asset.Futures {
s := strings.Split(l.Symbol, l.Base) // e.g. RUNEPFC for RUNE-USD futures pair
if len(s) <= 1 {
continue
}
quoteCurr = s[1]
for _, marketInfo := range m {
if pair, err := marketInfo.Pair(); err != nil {
errs = common.AppendError(errs, fmt.Errorf("%s: %w", marketInfo.Symbol, err))
} else {
s := strings.Split(l.Symbol, currency.DashDelimiter)
if len(s) != 2 {
continue
}
baseCurr = s[0]
quoteCurr = s[1]
pairs = append(pairs, pair)
}
pair, err := currency.NewPairFromStrings(baseCurr, quoteCurr)
if err != nil {
return nil, err
}
pairs = append(pairs, pair)
}
return pairs, nil
return pairs, errs
}
// UpdateTradablePairs updates the exchanges available pairs and stores
@@ -305,7 +279,11 @@ func (b *BTSE) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item)
if !b.SupportsAsset(a) {
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
}
ticks, err := b.GetMarketSummary(ctx, p.String(), a == asset.Spot)
symbol, err := b.FormatSymbol(p, a)
if err != nil {
return nil, err
}
ticks, err := b.GetMarketSummary(ctx, symbol, a == asset.Spot)
if err != nil {
return nil, err
}
@@ -458,23 +436,6 @@ func (b *BTSE) GetAccountFundingHistory(_ context.Context) ([]exchange.FundingHi
return nil, common.ErrFunctionNotSupported
}
func (b *BTSE) withinLimits(pair currency.Pair, amount float64) error {
val, found := OrderSizeLimits(pair.String())
if !found {
return fmt.Errorf("%w for pair %v", order.ErrExchangeLimitNotLoaded, pair)
}
if math.Mod(amount, val.MinSizeIncrement) < 0 {
return fmt.Errorf("%w %v %v %v", order.ErrAmountBelowMin, pair, amount, val.MinSizeIncrement)
}
if amount < val.MinOrderSize {
return fmt.Errorf("%w %v %v %v", order.ErrAmountBelowMin, pair, amount, val.MinOrderSize)
}
if amount > val.MaxOrderSize {
return fmt.Errorf("%w %v %v %v", order.ErrAmountExceedsMax, pair, amount, val.MinSizeIncrement)
}
return nil
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *BTSE) GetWithdrawalsHistory(_ context.Context, _ currency.Code, _ asset.Item) ([]exchange.WithdrawalHistory, error) {
return nil, common.ErrFunctionNotSupported
@@ -543,10 +504,6 @@ func (b *BTSE) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR
if err != nil {
return nil, err
}
err = b.withinLimits(fPair, s.Amount)
if err != nil {
return nil, err
}
r, err := b.CreateOrder(ctx,
s.ClientID, 0.0,
@@ -1077,45 +1034,6 @@ func (b *BTSE) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pai
return req.ProcessResponse(timeSeries)
}
func (b *BTSE) seedOrderSizeLimits(ctx context.Context) error {
pairs, err := b.GetMarketSummary(ctx, "", true)
if err != nil {
return err
}
for x := range pairs {
tempValues := OrderSizeLimit{
MinOrderSize: pairs[x].MinOrderSize,
MaxOrderSize: pairs[x].MaxOrderSize,
MinSizeIncrement: pairs[x].MinSizeIncrement,
}
orderSizeLimitMap.Store(pairs[x].Symbol, tempValues)
}
pairs, err = b.GetMarketSummary(ctx, "", false)
if err != nil {
return err
}
for x := range pairs {
tempValues := OrderSizeLimit{
MinOrderSize: pairs[x].MinOrderSize,
MaxOrderSize: pairs[x].MaxOrderSize,
MinSizeIncrement: pairs[x].MinSizeIncrement,
}
orderSizeLimitMap.Store(pairs[x].Symbol, tempValues)
}
return nil
}
// OrderSizeLimits looks up currency pair in orderSizeLimitMap and returns OrderSizeLimit
func OrderSizeLimits(pair string) (limits OrderSizeLimit, found bool) {
resp, ok := orderSizeLimitMap.Load(pair)
if !ok {
return
}
val, ok := resp.(OrderSizeLimit)
return val, ok
}
// GetServerTime returns the current exchange server time.
func (b *BTSE) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, error) {
st, err := b.GetCurrentServerTime(ctx)
@@ -1125,6 +1043,95 @@ func (b *BTSE) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, erro
return st.ISO, nil
}
// ExponentPairs returns a map of symbol names which have a Exponent equivalent
// e.g. PIT-USD will be returned if M_PIT-USD exists, and SATS-USD if K_SATS-USD exists
func (m *MarketSummary) ExponentPairs() (map[string]bool, error) {
pairs := map[string]bool{}
var errs error
for _, s := range *m {
if s.Active && s.HasLiquidity() {
if symbol, err := s.StripExponent(); err != nil {
errs = common.AppendError(errs, err)
} else if symbol != "" {
pairs[symbol] = true
}
}
}
return pairs, errs
}
// StripExponent returns the symbol without a exponent prefix; e.g. B_, M_, K_
// Returns an empty string if no exponent prefix is found
// Errors if there's too many underscores, or if the exponent is not recognised
func (m *MarketPair) StripExponent() (string, error) {
parts := strings.Split(m.Symbol, "_")
switch len(parts) {
case 1:
return "", nil
case 2:
switch parts[0] {
case "B", "M", "K":
return parts[1], nil
}
}
return "", errInvalidPairSymbol
}
// Pair returns the currency Pair for a MarketPair
func (m *MarketPair) Pair() (currency.Pair, error) {
baseCurr := m.Base
var quoteCurr string
if m.Futures {
s := strings.Split(m.Symbol, m.Base) // e.g. RUNEPFC for RUNE-USD futures pair
if len(s) <= 1 {
return currency.EMPTYPAIR, errInvalidPairSymbol
}
quoteCurr = s[1]
} else {
s := strings.Split(m.Symbol, currency.DashDelimiter)
if len(s) != 2 {
return currency.EMPTYPAIR, errInvalidPairSymbol
}
baseCurr = s[0]
quoteCurr = s[1]
}
return currency.NewPairFromStrings(baseCurr, quoteCurr)
}
// GetMarketSummary returns filtered market pair details; Specifically:
// - Pairs which aren't active are removed
// - Pairs which don't have liquidity are removed
// - OTC pairs only traded on web UI are removed
// - Pairs with an exponent counterpart pair are removed
// BTSE lists M_ symbols for very small pairs, in millions. For those listings, we want to take the M_ listing in preference
// to the native listing, since they're often going to appear as locked markets due to size (bid == ask, e.g. 0.0000000003)
func (b *BTSE) GetMarketSummary(ctx context.Context, symbol string, spot bool) (MarketSummary, error) {
m, err := b.GetRawMarketSummary(ctx, symbol, spot)
if err != nil {
return m, err
}
ePairs, err := m.ExponentPairs()
if err != nil {
return m, err
}
filtered := make(MarketSummary, 0, len(m))
for _, l := range m {
if !l.Active || !l.HasLiquidity() || (spot && !l.IsMarketOpenToSpot) { // Skip OTC assets only tradable on web UI
continue
}
if ePairs[l.Symbol] { // Skip pair with an exponent sibling
continue
}
if !spot {
// BTSE API for futures does not return futures field at all, and the docs show it coming back as false
// Much easier for our data flow if we can trust this field
l.Futures = true
}
filtered = append(filtered, l)
}
return filtered, nil
}
// GetFuturesContractDetails returns details about futures contracts
func (b *BTSE) GetFuturesContractDetails(ctx context.Context, item asset.Item) ([]futures.Contract, error) {
if !item.IsFutures() {
@@ -1265,8 +1272,33 @@ func (b *BTSE) IsPerpetualFutureCurrency(a asset.Item, p currency.Pair) (bool, e
}
// UpdateOrderExecutionLimits updates order execution limits
func (b *BTSE) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
func (b *BTSE) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error {
summary, err := b.GetMarketSummary(ctx, "", a == asset.Spot)
if err != nil {
return err
}
var errs error
limits := make([]order.MinMaxLevel, 0, len(summary))
for _, marketInfo := range summary {
p, err := marketInfo.Pair() //nolint:govet // Deliberately shadow err
if err != nil {
errs = common.AppendError(err, fmt.Errorf("%s: %w", p, err))
continue
}
limits = append(limits, order.MinMaxLevel{
Pair: p,
Asset: a,
MinimumBaseAmount: marketInfo.MinOrderSize,
MaximumBaseAmount: marketInfo.MaxOrderSize,
AmountStepIncrementSize: marketInfo.MinSizeIncrement,
MinPrice: marketInfo.MinValidPrice,
PriceStepIncrementSize: marketInfo.MinPriceIncrement,
})
}
if err = b.LoadLimits(limits); err != nil {
errs = common.AppendError(errs, err)
}
return errs
}
// GetOpenInterest returns the open interest rate for a given asset pair

View File

@@ -10,45 +10,26 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
// Public errors for order limits
var (
// ErrExchangeLimitNotLoaded defines if an exchange does not have minmax
// values
ErrExchangeLimitNotLoaded = errors.New("exchange limits not loaded")
// ErrPriceBelowMin is when the price is lower than the minimum price
// limit accepted by the exchange
ErrPriceBelowMin = errors.New("price below minimum limit")
// ErrPriceExceedsMax is when the price is higher than the maximum price
// limit accepted by the exchange
ErrPriceExceedsMax = errors.New("price exceeds maximum limit")
// ErrPriceExceedsStep is when the price is not divisible by its step
ErrPriceExceedsStep = errors.New("price exceeds step limit")
// ErrAmountBelowMin is when the amount is lower than the minimum amount
// limit accepted by the exchange
ErrAmountBelowMin = errors.New("amount below minimum limit")
// ErrAmountExceedsMax is when the amount is higher than the maximum amount
// limit accepted by the exchange
ErrAmountExceedsMax = errors.New("amount exceeds maximum limit")
// ErrAmountExceedsStep is when the amount is not divisible by its step
ErrAmountExceedsStep = errors.New("amount exceeds step limit")
// ErrNotionalValue is when the notional value does not exceed currency pair
// requirements
ErrNotionalValue = errors.New("total notional value is under minimum limit")
// ErrMarketAmountBelowMin is when the amount is lower than the minimum
// amount limit accepted by the exchange for a market order
ErrMarketAmountBelowMin = errors.New("market order amount below minimum limit")
// ErrMarketAmountExceedsMax is when the amount is higher than the maximum
// amount limit accepted by the exchange for a market order
ErrMarketAmountExceedsMax = errors.New("market order amount exceeds maximum limit")
// ErrMarketAmountExceedsStep is when the amount is not divisible by its
// step for a market order
ErrMarketAmountExceedsStep = errors.New("market order amount exceeds step limit")
// ErrCannotValidateAsset is thrown when the asset is not loaded
ErrCannotValidateAsset = errors.New("cannot check limit, asset not loaded")
// ErrCannotValidateBaseCurrency is thrown when the base currency is not loaded
ErrCannotValidateBaseCurrency = errors.New("cannot check limit, base currency not loaded")
// ErrCannotValidateQuoteCurrency is thrown when the quote currency is not loaded
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")
@@ -105,9 +86,7 @@ func (e *ExecutionLimits) LoadLimits(levels []MinMaxLevel) error {
for x := range levels {
if !levels[x].Asset.IsValid() {
return fmt.Errorf("cannot load levels for '%s': %w",
levels[x].Asset,
asset.ErrNotSupported)
return fmt.Errorf("cannot load levels for '%s': %w", levels[x].Asset, asset.ErrNotSupported)
}
m1, ok := e.m[levels[x].Asset]
if !ok {