From ceef7a14e0e34026963033a54d09bdeeb3b08bb8 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Wed, 18 Oct 2023 11:57:27 +1100 Subject: [PATCH] currency: Adds matching lookup table built from available pairs (#1312) * currency: Add pair matching update (cherry-pick) * exchange/currency: Add tests and update func * linter fix, also if using json unmarshal functionality stop usage of string conversion without delimiter * gemini: fix test * currency/manager: potential optimisation * exchanges: purge derive from wrapper cases and add warning comment * glorious: nits * glorious: nits * linter: fix * glorious: nits * whoops * whoops * glorious: nits continued * glorious: diff THANKS! * hitbtc: fix update tradable pairs strings splitting. continue if not enabled tickers update pair. * glorious: nits * linter: fix * Update exchanges/exmo/exmo_wrapper.go Co-authored-by: Scott * bitstamp: fix test when 32 biterinos architecturinos * capture more strings for speed * swapsies because whos running 32bit \0/? --------- Co-authored-by: Ryan O'Hara-Reid Co-authored-by: Scott --- .../exchange_wrapper_standards_test.go | 3 + currency/currency_types.go | 8 -- currency/manager.go | 73 +++++++--- currency/manager_test.go | 87 +++++++++-- currency/manager_types.go | 21 ++- currency/pair.go | 25 ++-- currency/pair_methods.go | 18 ++- currency/pair_types.go | 2 + currency/pairs.go | 3 +- engine/rpcserver.go | 24 +++- engine/rpcserver_test.go | 21 ++- exchanges/bitfinex/bitfinex_wrapper.go | 14 +- exchanges/bitmex/bitmex_test.go | 1 + exchanges/bitmex/bitmex_wrapper.go | 18 +-- exchanges/bitstamp/bitstamp_type_convert.go | 11 +- exchanges/bybit/bybit_test.go | 6 +- exchanges/bybit/bybit_websocket.go | 36 ++++- exchanges/bybit/bybit_wrapper.go | 77 +++++----- exchanges/bybit/bybit_ws_cfutures.go | 44 ++++-- exchanges/bybit/bybit_ws_futures.go | 26 ++-- exchanges/bybit/bybit_ws_ufutures.go | 26 ++-- exchanges/coinut/coinut_types.go | 12 +- exchanges/exchange.go | 36 +++++ exchanges/exchange_test.go | 136 ++++++++++++++++++ exchanges/exmo/exmo_test.go | 23 +-- exchanges/exmo/exmo_wrapper.go | 19 +-- exchanges/gateio/gateio_wrapper.go | 7 +- exchanges/gemini/gemini_types.go | 22 +-- exchanges/hitbtc/hitbtc_test.go | 21 ++- exchanges/hitbtc/hitbtc_wrapper.go | 23 ++- exchanges/interfaces.go | 12 ++ exchanges/okx/okx.go | 30 +++- 32 files changed, 621 insertions(+), 264 deletions(-) diff --git a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go index 493a2927..995c40ec 100644 --- a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go +++ b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go @@ -300,6 +300,8 @@ func generateMethodArg(ctx context.Context, t *testing.T, argGenerator *MethodAr // Crypto Chain input = reflect.ValueOf(cryptoChainPerExchange[exchName]) } + case "MatchSymbolWithAvailablePairs", "MatchSymbolCheckEnabled": + input = reflect.ValueOf(argGenerator.AssetParams.Pair.Base.Lower().String() + argGenerator.AssetParams.Pair.Quote.Lower().String()) default: // OrderID input = reflect.ValueOf("1337") @@ -602,6 +604,7 @@ var acceptableErrors = []error{ order.ErrPairIsEmpty, // Is thrown when the empty pair and asset scenario for an order submission is sent in the Validate() function deposit.ErrAddressNotFound, // Is thrown when an address is not found due to the exchange requiring valid API keys futures.ErrNotFuturesAsset, // Is thrown when a futures function receives a non-futures asset + currency.ErrSymbolStringEmpty, // Is thrown when a symbol string is empty for blank MatchSymbol func checks } // warningErrors will t.Log(err) when thrown to diagnose things, but not necessarily suggest diff --git a/currency/currency_types.go b/currency/currency_types.go index f69e75c9..de9d1aa4 100644 --- a/currency/currency_types.go +++ b/currency/currency_types.go @@ -81,11 +81,3 @@ const ( ForwardSlashDelimiter = "/" ColonDelimiter = ":" ) - -// delimiters is a delimiter list -var delimiters = []string{ - DashDelimiter, - UnderscoreDelimiter, - ForwardSlashDelimiter, - ColonDelimiter, -} diff --git a/currency/manager.go b/currency/manager.go index c3d22769..22e68bd3 100644 --- a/currency/manager.go +++ b/currency/manager.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" @@ -29,9 +30,12 @@ var ( ErrPairManagerNotInitialised = errors.New("pair manager not initialised") // ErrAssetNotFound is returned when an asset does not exist in the pairstore ErrAssetNotFound = errors.New("asset type not found in pair store") + // ErrSymbolStringEmpty is an error when a symbol string is empty + ErrSymbolStringEmpty = errors.New("symbol string is empty") - errPairStoreIsNil = errors.New("pair store is nil") - errPairFormatIsNil = errors.New("pair format is nil") + errPairStoreIsNil = errors.New("pair store is nil") + errPairFormatIsNil = errors.New("pair format is nil") + errPairMatcherIsNil = errors.New("pair matcher is nil") ) // GetAssetTypes returns a list of stored asset types @@ -64,6 +68,24 @@ func (p *PairsManager) Get(a asset.Item) (*PairStore, error) { return c.copy() } +// Match returns a currency pair based on the supplied symbol and asset type +func (p *PairsManager) Match(symbol string, a asset.Item) (Pair, error) { + if symbol == "" { + return EMPTYPAIR, ErrSymbolStringEmpty + } + symbol = strings.ToLower(symbol) + p.mutex.RLock() + defer p.mutex.RUnlock() + if p.matcher == nil { + return EMPTYPAIR, errPairMatcherIsNil + } + pair, ok := p.matcher[key{symbol, a}] + if !ok { + return EMPTYPAIR, fmt.Errorf("%w for %v %v", ErrPairNotFound, symbol, a) + } + return *pair, nil +} + // Store stores a new currency pair config based on its asset type func (p *PairsManager) Store(a asset.Item, ps *PairStore) error { if !a.IsValid() { @@ -78,6 +100,14 @@ func (p *PairsManager) Store(a asset.Item, ps *PairStore) error { p.Pairs = make(map[asset.Item]*PairStore) } p.Pairs[a] = cpy + if p.matcher == nil { + p.matcher = make(map[key]*Pair) + } + for x := range cpy.Available { + p.matcher[key{ + Symbol: cpy.Available[x].Base.Lower().String() + cpy.Available[x].Quote.Lower().String(), + Asset: a}] = &cpy.Available[x] + } p.mutex.Unlock() return nil } @@ -85,6 +115,14 @@ func (p *PairsManager) Store(a asset.Item, ps *PairStore) error { // Delete deletes a map entry based on the supplied asset type func (p *PairsManager) Delete(a asset.Item) { p.mutex.Lock() + vals, ok := p.Pairs[a] + if !ok { + p.mutex.Unlock() + return + } + for x := range vals.Available { + delete(p.matcher, key{Symbol: vals.Available[x].Base.Lower().String() + vals.Available[x].Quote.Lower().String(), Asset: a}) + } delete(p.Pairs, a) p.mutex.Unlock() } @@ -187,6 +225,15 @@ func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) error pairStore.Enabled = cpy } else { pairStore.Available = cpy + + if p.matcher == nil { + p.matcher = make(map[key]*Pair) + } + for x := range pairStore.Available { + p.matcher[key{ + Symbol: pairStore.Available[x].Base.Lower().String() + pairStore.Available[x].Quote.Lower().String(), + Asset: a}] = &pairStore.Available[x] + } } return nil @@ -285,14 +332,14 @@ func (p *PairsManager) EnablePair(a asset.Item, pair Pair) error { return nil } -// IsAssetPairEnabled checks if a pair is enabled for an enabled asset type -func (p *PairsManager) IsAssetPairEnabled(a asset.Item, pair Pair) error { +// IsPairEnabled checks if a pair is enabled for an enabled asset type +func (p *PairsManager) IsPairEnabled(pair Pair, a asset.Item) (bool, error) { if !a.IsValid() { - return fmt.Errorf("%s %w", a, asset.ErrNotSupported) + return false, fmt.Errorf("%s %w", a, asset.ErrNotSupported) } if pair.IsEmpty() { - return ErrCurrencyPairEmpty + return false, ErrCurrencyPairEmpty } p.mutex.RLock() @@ -300,20 +347,12 @@ func (p *PairsManager) IsAssetPairEnabled(a asset.Item, pair Pair) error { pairStore, err := p.getPairStoreRequiresLock(a) if err != nil { - return err + return false, err } - if pairStore.AssetEnabled == nil { - return fmt.Errorf("%s %w", a, ErrAssetIsNil) + return false, fmt.Errorf("%s %w", a, ErrAssetIsNil) } - if !*pairStore.AssetEnabled { - return fmt.Errorf("%s %w", a, asset.ErrNotEnabled) - } - if !pairStore.Enabled.Contains(pair, true) { - return fmt.Errorf("%s %w", pair, ErrPairNotFound) - } - - return nil + return *pairStore.AssetEnabled && pairStore.Enabled.Contains(pair, true), nil } // IsAssetEnabled checks to see if an asset is enabled diff --git a/currency/manager_test.go b/currency/manager_test.go index 66a2dd29..dbd81f10 100644 --- a/currency/manager_test.go +++ b/currency/manager_test.go @@ -92,6 +92,48 @@ func TestGet(t *testing.T) { } } +func TestPairsManagerMatch(t *testing.T) { + t.Parallel() + + p := &PairsManager{} + + _, err := p.Match("", 1337) + if !errors.Is(err, ErrSymbolStringEmpty) { + t.Fatalf("received: '%v' but expected: '%v'", err, ErrSymbolStringEmpty) + } + + _, err = p.Match("sillyBilly", 1337) + if !errors.Is(err, errPairMatcherIsNil) { + t.Fatalf("received: '%v' but expected: '%v'", err, errPairMatcherIsNil) + } + + p = initTest(t) + + _, err = p.Match("sillyBilly", 1337) + if !errors.Is(err, ErrPairNotFound) { + t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairNotFound) + } + + _, err = p.Match("sillyBilly", asset.Spot) + if !errors.Is(err, ErrPairNotFound) { + t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairNotFound) + } + + whatIgot, err := p.Match("bTCuSD", asset.Spot) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + whatIwant, err := NewPairFromString("btc-usd") + if err != nil { + t.Fatal(err) + } + + if !whatIgot.Equal(whatIwant) { + t.Fatal("expected btc-usd") + } +} + func TestStore(t *testing.T) { t.Parallel() availPairs, err := NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}) @@ -531,43 +573,64 @@ func TestUnmarshalMarshal(t *testing.T) { } } -func TestIsAssetPairEnabled(t *testing.T) { +func TestIsPairEnabled(t *testing.T) { t.Parallel() pm := initTest(t) cp := NewPairWithDelimiter("BTC", "USD", "-") - err := pm.IsAssetPairEnabled(asset.Spot, cp) + enabled, err := pm.IsPairEnabled(cp, asset.Spot) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) } - err = pm.IsAssetPairEnabled(asset.Futures, cp) - if !errors.Is(err, asset.ErrNotEnabled) { - t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotEnabled) + if !enabled { + t.Fatal("expected pair to be enabled") + } + + enabled, err = pm.IsPairEnabled(NewPair(SAFE, MOONRISE), asset.Spot) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if enabled { + t.Fatal("expected pair to be disabled") + } + + enabled, err = pm.IsPairEnabled(cp, asset.Futures) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if enabled { + t.Fatal("expected pair to be disabled because asset type is not enabled") } cp = NewPairWithDelimiter("XRP", "DOGE", "-") - err = pm.IsAssetPairEnabled(asset.Spot, cp) - if !errors.Is(err, ErrPairNotFound) { - t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairNotFound) + enabled, err = pm.IsPairEnabled(cp, asset.Spot) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) } - err = pm.IsAssetPairEnabled(asset.PerpetualSwap, cp) + if enabled { + t.Fatal("expected pair to be disabled because pair not found in enabled list") + } + + _, err = pm.IsPairEnabled(cp, asset.PerpetualSwap) if !errors.Is(err, ErrAssetNotFound) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrAssetNotFound) } - err = pm.IsAssetPairEnabled(asset.Item(1337), cp) + _, err = pm.IsPairEnabled(cp, asset.Item(1337)) if !errors.Is(err, asset.ErrNotSupported) { t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported) } pm.Pairs[asset.PerpetualSwap] = &PairStore{} - err = pm.IsAssetPairEnabled(asset.PerpetualSwap, cp) + _, err = pm.IsPairEnabled(cp, asset.PerpetualSwap) if !errors.Is(err, ErrAssetIsNil) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrAssetIsNil) } - err = pm.IsAssetPairEnabled(asset.PerpetualSwap, EMPTYPAIR) + _, err = pm.IsPairEnabled(EMPTYPAIR, asset.PerpetualSwap) if !errors.Is(err, ErrCurrencyPairEmpty) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairEmpty) } diff --git a/currency/manager_types.go b/currency/manager_types.go index 2b94d7ca..3382cb1a 100644 --- a/currency/manager_types.go +++ b/currency/manager_types.go @@ -8,13 +8,14 @@ import ( // PairsManager manages asset pairs type PairsManager struct { - BypassConfigFormatUpgrades bool `json:"bypassConfigFormatUpgrades"` - RequestFormat *PairFormat `json:"requestFormat,omitempty"` - ConfigFormat *PairFormat `json:"configFormat,omitempty"` - UseGlobalFormat bool `json:"useGlobalFormat,omitempty"` - LastUpdated int64 `json:"lastUpdated,omitempty"` - Pairs FullStore `json:"pairs"` - mutex sync.RWMutex `json:"-"` + BypassConfigFormatUpgrades bool `json:"bypassConfigFormatUpgrades"` + RequestFormat *PairFormat `json:"requestFormat,omitempty"` + ConfigFormat *PairFormat `json:"configFormat,omitempty"` + UseGlobalFormat bool `json:"useGlobalFormat,omitempty"` + LastUpdated int64 `json:"lastUpdated,omitempty"` + Pairs FullStore `json:"pairs"` + matcher map[key]*Pair + mutex sync.RWMutex } // FullStore holds all supported asset types with the enabled and available @@ -37,3 +38,9 @@ type PairFormat struct { Separator string `json:"separator,omitempty"` Index string `json:"index,omitempty"` } + +// key is used to store the asset type and symbol in a map +type key struct { + Symbol string + Asset asset.Item +} diff --git a/currency/pair.go b/currency/pair.go index 975eeb97..fe6c1716 100644 --- a/currency/pair.go +++ b/currency/pair.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strings" + "unicode" ) var errCannotCreatePair = errors.New("cannot create currency pair") @@ -100,23 +101,17 @@ func NewPairFromString(currencyPair string) (Pair, error) { errCannotCreatePair, currencyPair) } - var delimiter string - pairStrings := []string{currencyPair} - for x := range delimiters { - if strings.Contains(pairStrings[0], delimiters[x]) { - values := strings.SplitN(pairStrings[0], delimiters[x], 2) - if delimiter != "" { - values[1] += delimiter + pairStrings[1] - pairStrings = values - } else { - pairStrings = values - } - delimiter = delimiters[x] + + for x := range currencyPair { + if unicode.IsPunct(rune(currencyPair[x])) { + return Pair{ + Base: NewCode(currencyPair[:x]), + Delimiter: string(currencyPair[x]), + Quote: NewCode(currencyPair[x+1:]), + }, nil } } - if delimiter != "" { - return Pair{Base: NewCode(pairStrings[0]), Delimiter: delimiter, Quote: NewCode(pairStrings[1])}, nil - } + return NewPairFromStrings(currencyPair[0:3], currencyPair[3:]) } diff --git a/currency/pair_methods.go b/currency/pair_methods.go index 7add2bec..90689e1c 100644 --- a/currency/pair_methods.go +++ b/currency/pair_methods.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "unicode" ) // EMPTYFORMAT defines an empty pair format @@ -45,8 +46,21 @@ func (p *Pair) UnmarshalJSON(d []byte) error { return nil } - *p, err = NewPairFromString(pair) - return err + // Check if pair is in the format of BTC-USD + for x := range pair { + if unicode.IsPunct(rune(pair[x])) { + p.Base = NewCode(pair[:x]) + p.Delimiter = string(pair[x]) + p.Quote = NewCode(pair[x+1:]) + return nil + } + } + + // NOTE: Pair string could be in format DUSKUSDT (Kucoin) which will be + // incorrectly converted to DUS-KUSDT, ELKRW (Bithumb) which will convert + // converted to ELK-RW and HTUSDT (Lbank) which will be incorrectly + // converted to HTU-SDT. + return fmt.Errorf("%w from %s cannot ensure pair is in correct format, please use exchange method MatchSymbolWithAvailablePairs", errCannotCreatePair, pair) } // MarshalJSON conforms type to the marshaler interface diff --git a/currency/pair_types.go b/currency/pair_types.go index 2db8832f..a1086d48 100644 --- a/currency/pair_types.go +++ b/currency/pair_types.go @@ -1,6 +1,8 @@ package currency // Pair holds currency pair information +// NOTE: UnmarshalJSON allows string conversion to Pair type but only if there +// is a delimiter present in the string, otherwise it will return an error. type Pair struct { Delimiter string `json:"delimiter,omitempty"` Base Code `json:"base,omitempty"` diff --git a/currency/pairs.go b/currency/pairs.go index abf8beee..887b46fc 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -303,7 +303,8 @@ func (p Pairs) GetRandomPair() (Pair, error) { } // DeriveFrom matches symbol string to the available pairs list when no -// delimiter is supplied. +// delimiter is supplied. WARNING: This is not optimised and should only be used +// for one off processes. func (p Pairs) DeriveFrom(symbol string) (Pair, error) { if len(p) == 0 { return EMPTYPAIR, ErrCurrencyPairsEmpty diff --git a/engine/rpcserver.go b/engine/rpcserver.go index e2637ca0..d7f27c68 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -78,6 +78,7 @@ var ( errGRPCShutdownSignalIsNil = errors.New("cannot shutdown, gRPC shutdown channel is nil") errInvalidStrategy = errors.New("invalid strategy") errSpecificPairNotEnabled = errors.New("specified pair is not enabled") + errPairNotEnabled = errors.New("pair is not enabled") ) // RPCServer struct @@ -4686,13 +4687,18 @@ func (s *RPCServer) GetFundingRates(ctx context.Context, r *gctrpc.GetFundingRat return nil, err } + cp, err := exch.MatchSymbolWithAvailablePairs(r.Pair.Base+r.Pair.Quote, a, false) + if err != nil { + return nil, err + } + pairs, err := exch.GetEnabledPairs(a) if err != nil { return nil, err } - cp, err := pairs.DeriveFrom(r.Pair.Base + r.Pair.Quote) - if err != nil { - return nil, err + + if !pairs.Contains(cp, true) { + return nil, fmt.Errorf("%w %v", errPairNotEnabled, cp) } funding, err := exch.GetFundingRates(ctx, &fundingrate.RatesRequest{ @@ -4779,14 +4785,20 @@ func (s *RPCServer) GetLatestFundingRate(ctx context.Context, r *gctrpc.GetLates return nil, fmt.Errorf("%s %w", a, futures.ErrNotFuturesAsset) } + cp, err := exch.MatchSymbolWithAvailablePairs(r.Pair.Base+r.Pair.Quote, a, false) + if err != nil { + return nil, err + } + pairs, err := exch.GetEnabledPairs(a) if err != nil { return nil, err } - cp, err := pairs.DeriveFrom(r.Pair.Base + r.Pair.Quote) - if err != nil { - return nil, err + + if !pairs.Contains(cp, true) { + return nil, fmt.Errorf("%w %v", errPairNotEnabled, cp) } + funding, err := exch.GetLatestFundingRate(ctx, &fundingrate.LatestRateRequest{ Asset: a, Pair: cp, diff --git a/engine/rpcserver_test.go b/engine/rpcserver_test.go index a1a9823c..522c66ee 100644 --- a/engine/rpcserver_test.go +++ b/engine/rpcserver_test.go @@ -2927,19 +2927,26 @@ func TestGetFundingRates(t *testing.T) { } b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) - b.CurrencyPairs.Pairs[asset.Futures] = ¤cy.PairStore{ + err = b.CurrencyPairs.Store(asset.Futures, ¤cy.PairStore{ AssetEnabled: convert.BoolPtr(true), RequestFormat: ¤cy.PairFormat{Delimiter: "-"}, ConfigFormat: ¤cy.PairFormat{Delimiter: "-"}, Available: currency.Pairs{cp}, Enabled: currency.Pairs{cp}, + }) + if err != nil { + t.Fatal(err) } - b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + + err = b.CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{ AssetEnabled: convert.BoolPtr(true), ConfigFormat: ¤cy.PairFormat{Delimiter: "/"}, RequestFormat: ¤cy.PairFormat{Delimiter: "/"}, Available: currency.Pairs{cp}, Enabled: currency.Pairs{cp}, + }) + if err != nil { + t.Fatal(err) } b.Features.Supports.FuturesCapabilities.FundingRates = true fakeExchange := fExchange{ @@ -3028,19 +3035,25 @@ func TestGetLatestFundingRate(t *testing.T) { } b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) - b.CurrencyPairs.Pairs[asset.Futures] = ¤cy.PairStore{ + err = b.CurrencyPairs.Store(asset.Futures, ¤cy.PairStore{ AssetEnabled: convert.BoolPtr(true), RequestFormat: ¤cy.PairFormat{Delimiter: "-"}, ConfigFormat: ¤cy.PairFormat{Delimiter: "-"}, Available: currency.Pairs{cp}, Enabled: currency.Pairs{cp}, + }) + if err != nil { + t.Fatal(err) } - b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + err = b.CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{ AssetEnabled: convert.BoolPtr(true), ConfigFormat: ¤cy.PairFormat{Delimiter: "/"}, RequestFormat: ¤cy.PairFormat{Delimiter: "/"}, Available: currency.Pairs{cp}, Enabled: currency.Pairs{cp}, + }) + if err != nil { + t.Fatal(err) } fakeExchange := fExchange{ IBotExchange: exch, diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 830d3c58..c7bd983b 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -356,20 +356,20 @@ func (b *Bitfinex) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) // UpdateTickers updates the ticker for all currency pairs of a given asset type func (b *Bitfinex) UpdateTickers(ctx context.Context, a asset.Item) error { - enabled, err := b.GetEnabledPairs(a) - if err != nil { - return err - } - tickerNew, err := b.GetTickerBatch(ctx) if err != nil { return err } for key, val := range tickerNew { - pair, err := enabled.DeriveFrom(strings.Replace(key, ":", "", 1)[1:]) + pair, enabled, err := b.MatchSymbolCheckEnabled(key[1:], a, true) if err != nil { - // GetTickerBatch returns all pairs in call across all asset types. + if !errors.Is(err, currency.ErrPairNotFound) { + return err + } + } + + if !enabled { continue } diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index 9c601681..fc4e7c63 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -1098,6 +1098,7 @@ func TestGetHistoricTrades(t *testing.T) { } func TestUpdateTicker(t *testing.T) { + t.Parallel() cp := currency.NewPair(currency.ETH, currency.USD) _, err := b.UpdateTicker(context.Background(), cp, asset.PerpetualContract) if err != nil { diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 2660fd6d..2e73a118 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -364,11 +364,7 @@ func (b *Bitmex) UpdateTickers(ctx context.Context, a asset.Item) error { return err } - enabled, err := b.GetEnabledPairs(a) - if err != nil { - return err - } - + var enabled bool instruments: for j := range tick { var pair currency.Pair @@ -377,7 +373,7 @@ instruments: if tick[j].Typ != futuresID { continue instruments } - pair, err = enabled.DeriveFrom(tick[j].Symbol) + pair, enabled, err = b.MatchSymbolCheckEnabled(tick[j].Symbol, a, false) case asset.Index: switch tick[j].Typ { case bitMEXBasketIndexID, @@ -392,23 +388,27 @@ instruments: // contain an underscore. Calling DeriveFrom will then error and // the instruments will be missed. tick[j].Symbol = strings.Replace(tick[j].Symbol, currency.UnderscoreDelimiter, "", 1) - pair, err = enabled.DeriveFrom(tick[j].Symbol) + pair, enabled, err = b.MatchSymbolCheckEnabled(tick[j].Symbol, a, false) case asset.PerpetualContract: if tick[j].Typ != perpetualContractID { continue instruments } - pair, err = enabled.DeriveFrom(tick[j].Symbol) + pair, enabled, err = b.MatchSymbolCheckEnabled(tick[j].Symbol, a, false) case asset.Spot: if tick[j].Typ != spotID { continue instruments } tick[j].Symbol = strings.Replace(tick[j].Symbol, currency.UnderscoreDelimiter, "", 1) - pair, err = enabled.DeriveFrom(tick[j].Symbol) + pair, enabled, err = b.MatchSymbolCheckEnabled(tick[j].Symbol, a, false) } + if err != nil { if !errors.Is(err, currency.ErrPairNotFound) { return err } + } + + if !enabled { continue } diff --git a/exchanges/bitstamp/bitstamp_type_convert.go b/exchanges/bitstamp/bitstamp_type_convert.go index e0af07f1..94af150f 100644 --- a/exchanges/bitstamp/bitstamp_type_convert.go +++ b/exchanges/bitstamp/bitstamp_type_convert.go @@ -44,13 +44,22 @@ func (t *microTimestamp) UnmarshalJSON(data []byte) error { return err } + if strconv.IntSize == 32 && len(s) >= 10 { + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return err + } + *t = microTimestamp(time.UnixMicro(i)) + return nil + } + + // Has Fast path optimisation when int == 64 i, err := strconv.Atoi(s) if err != nil { return err } *t = microTimestamp(time.UnixMicro(int64(i))) - return nil } diff --git a/exchanges/bybit/bybit_test.go b/exchanges/bybit/bybit_test.go index 8be27d7a..ed8e68fb 100644 --- a/exchanges/bybit/bybit_test.go +++ b/exchanges/bybit/bybit_test.go @@ -2042,7 +2042,7 @@ func TestUpdateTicker(t *testing.T) { var pairs currency.Pairs if mockTests { var pair2 currency.Pair - pair2, err = currency.NewPairFromString("BTCUSD-U23") + pair2, err = currency.NewPairFromString("BTCUSD-Z23") if err != nil { t.Fatal(err) } @@ -2230,7 +2230,7 @@ func TestGetHistoricCandles(t *testing.T) { } var pair2 currency.Pair if mockTests { - pair2, err = currency.NewPairFromString("BTCUSD-U23") + pair2, err = currency.NewPairFromString("BTCUSD-Z23") if err != nil { t.Fatal(err) } @@ -2292,7 +2292,7 @@ func TestGetHistoricCandlesExtended(t *testing.T) { } var pair2 currency.Pair if mockTests { - pair2, err = currency.NewPairFromString("BTCUSD-U23") + pair2, err = currency.NewPairFromString("BTCUSD-Z23") if err != nil { t.Fatal(err) } diff --git a/exchanges/bybit/bybit_websocket.go b/exchanges/bybit/bybit_websocket.go index 182910cf..4f7ee986 100644 --- a/exchanges/bybit/bybit_websocket.go +++ b/exchanges/bybit/bybit_websocket.go @@ -266,11 +266,15 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { if err != nil { return err } - p, err := by.extractCurrencyPair(data.OBData.Symbol, asset.Spot) + p, enabled, err := by.MatchSymbolCheckEnabled(data.OBData.Symbol, asset.Spot, false) if err != nil { return err } + if !enabled { + return nil + } + err = by.wsUpdateOrderbook(&data.OBData, p, asset.Spot) if err != nil { return err @@ -286,11 +290,15 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { return err } - p, err := by.extractCurrencyPair(data.Parameters.Symbol, asset.Spot) + p, enabled, err := by.MatchSymbolCheckEnabled(data.Parameters.Symbol, asset.Spot, false) if err != nil { return err } + if !enabled { + return nil + } + side := order.Sell if data.TradeData.Side { side = order.Buy @@ -313,11 +321,15 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { return err } - p, err := by.extractCurrencyPair(data.Ticker.Symbol, asset.Spot) + p, enabled, err := by.MatchSymbolCheckEnabled(data.Ticker.Symbol, asset.Spot, false) if err != nil { return err } + if !enabled { + return nil + } + by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, Bid: data.Ticker.Bid.Float64(), @@ -334,11 +346,15 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { return err } - p, err := by.extractCurrencyPair(data.Kline.Symbol, asset.Spot) + p, enabled, err := by.MatchSymbolCheckEnabled(data.Kline.Symbol, asset.Spot, false) if err != nil { return err } + if !enabled { + return nil + } + by.Websocket.DataHandler <- stream.KlineData{ Pair: p, AssetType: asset.Spot, @@ -425,11 +441,15 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { } } - p, err := by.extractCurrencyPair(data[j].Symbol, asset.Spot) + p, enabled, err := by.MatchSymbolCheckEnabled(data[j].Symbol, asset.Spot, false) if err != nil { return err } + if !enabled { + continue + } + by.Websocket.DataHandler <- order.Detail{ Price: data[j].Price.Float64(), Amount: data[j].Quantity.Float64(), @@ -475,11 +495,15 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { } } - p, err := by.extractCurrencyPair(data[j].Symbol, asset.Spot) + p, enabled, err := by.MatchSymbolCheckEnabled(data[j].Symbol, asset.Spot, false) if err != nil { return err } + if !enabled { + continue + } + by.Websocket.DataHandler <- &order.Detail{ Exchange: by.Name, OrderID: data[j].OrderID, diff --git a/exchanges/bybit/bybit_wrapper.go b/exchanges/bybit/bybit_wrapper.go index 56afbfd9..bfff780c 100644 --- a/exchanges/bybit/bybit_wrapper.go +++ b/exchanges/bybit/bybit_wrapper.go @@ -2,6 +2,7 @@ package bybit import ( "context" + "errors" "fmt" "sort" "strconv" @@ -416,16 +417,6 @@ func (by *Bybit) UpdateTradablePairs(ctx context.Context, forceUpdate bool) erro // UpdateTickers updates the ticker for all currency pairs of a given asset type func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error { - avail, err := by.GetAvailablePairs(assetType) - if err != nil { - return err - } - - enabled, err := by.GetEnabledPairs(assetType) - if err != nil { - return err - } - switch assetType { case asset.Spot: ticks, err := by.GetTickersV5(ctx, "spot", "", "") @@ -434,17 +425,19 @@ func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error } for x := range ticks.List { - pair, err := avail.DeriveFrom(ticks.List[x].Symbol) + pair, enabled, err := by.MatchSymbolCheckEnabled(ticks.List[x].Symbol, assetType, false) if err != nil { // These symbols below do not have a spot market but are in fact // perpetuals. if ticks.List[x].Symbol == "ZECUSDT" || ticks.List[x].Symbol == "DASHUSDT" { continue } - return err + if !errors.Is(err, currency.ErrPairNotFound) { + return err + } } - if !enabled.Contains(pair, true) { + if !enabled { continue } @@ -465,6 +458,11 @@ func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error } } case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.Futures: + enabled, err := by.GetEnabledPairs(assetType) + if err != nil { + return err + } + tick, err := by.GetFuturesSymbolPriceTicker(ctx, currency.EMPTYPAIR) if err != nil { return err @@ -480,7 +478,8 @@ func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error if tick[y].Symbol != formattedPair.String() { continue } - cp, err := by.extractCurrencyPair(tick[y].Symbol, assetType) + // Don't need to check if this pair is enabled due to call above. + cp, err := by.MatchSymbolWithAvailablePairs(tick[y].Symbol, assetType, false) if err != nil { return err } @@ -501,6 +500,11 @@ func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error } } case asset.USDCMarginedFutures: + enabled, err := by.GetEnabledPairs(assetType) + if err != nil { + return err + } + for x := range enabled { formattedPair, err := by.FormatExchangeCurrency(enabled[x], assetType) if err != nil { @@ -512,7 +516,8 @@ func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error return err } - cp, err := by.extractCurrencyPair(tick.Symbol, assetType) + // Don't need to check if this pair is enabled due to call above. + cp, err := by.MatchSymbolWithAvailablePairs(tick.Symbol, assetType, false) if err != nil { return err } @@ -551,10 +556,13 @@ func (by *Bybit) UpdateTicker(ctx context.Context, p currency.Pair, assetType as } for y := range tick { - cp, err := by.extractCurrencyPair(tick[y].Symbol, assetType) + cp, enabled, err := by.MatchSymbolCheckEnabled(tick[y].Symbol, assetType, false) if err != nil { return nil, err } + if !enabled { + continue + } err = ticker.ProcessTicker(&ticker.Price{ Last: tick[y].LastPrice.Float64(), High: tick[y].HighPrice.Float64(), @@ -572,7 +580,6 @@ func (by *Bybit) UpdateTicker(ctx context.Context, p currency.Pair, assetType as return nil, err } } - case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.Futures: tick, err := by.GetFuturesSymbolPriceTicker(ctx, formattedPair) if err != nil { @@ -580,10 +587,13 @@ func (by *Bybit) UpdateTicker(ctx context.Context, p currency.Pair, assetType as } for y := range tick { - cp, err := by.extractCurrencyPair(tick[y].Symbol, assetType) + cp, enabled, err := by.MatchSymbolCheckEnabled(tick[y].Symbol, assetType, false) if err != nil { return nil, err } + if !enabled { + continue + } err = ticker.ProcessTicker(&ticker.Price{ Last: tick[y].LastPrice.Float64(), High: tick[y].HighPrice24h.Float64(), @@ -606,10 +616,13 @@ func (by *Bybit) UpdateTicker(ctx context.Context, p currency.Pair, assetType as return nil, err } - cp, err := by.extractCurrencyPair(tick.Symbol, assetType) + cp, enabled, err := by.MatchSymbolCheckEnabled(tick.Symbol, assetType, false) if err != nil { return nil, err } + if !enabled { + return nil, fmt.Errorf("%v %v not enabled", formattedPair, assetType) + } err = ticker.ProcessTicker(&ticker.Price{ Last: tick.LastPrice.Float64(), High: tick.High24h.Float64(), @@ -2097,30 +2110,12 @@ func (by *Bybit) GetServerTime(ctx context.Context, a asset.Item) (time.Time, er return time.Time{}, fmt.Errorf("%s %w", a, asset.ErrNotSupported) } -func (by *Bybit) extractCurrencyPair(symbol string, item asset.Item) (currency.Pair, error) { - pairs, err := by.CurrencyPairs.GetPairs(item, true) - if err != nil { - return currency.EMPTYPAIR, err - } - pair, err := pairs.DeriveFrom(symbol) - if err != nil { - return currency.EMPTYPAIR, err - } - return pair, nil -} - // UpdateOrderExecutionLimits sets exchange executions for a required asset type func (by *Bybit) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { - avail, err := by.GetAvailablePairs(a) - if err != nil { - return err - } - var limits []order.MinMaxLevel switch a { case asset.Spot: - var pairsData []PairData - pairsData, err = by.GetAllSpotPairs(ctx) + pairsData, err := by.GetAllSpotPairs(ctx) if err != nil { return err } @@ -2128,11 +2123,15 @@ func (by *Bybit) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) e limits = make([]order.MinMaxLevel, 0, len(pairsData)) for x := range pairsData { var pair currency.Pair - pair, err = avail.DeriveFrom(pairsData[x].Name) + var enabled bool + pair, enabled, err = by.MatchSymbolCheckEnabled(pairsData[x].Name, a, false) if err != nil { log.Warnf(log.ExchangeSys, "%s unable to load limits for %v, pair data missing", by.Name, pairsData[x].Name) continue } + if !enabled { + continue + } limits = append(limits, order.MinMaxLevel{ Asset: a, diff --git a/exchanges/bybit/bybit_ws_cfutures.go b/exchanges/bybit/bybit_ws_cfutures.go index ec63ada3..39ca9e29 100644 --- a/exchanges/bybit/bybit_ws_cfutures.go +++ b/exchanges/bybit/bybit_ws_cfutures.go @@ -215,6 +215,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { if wsType, ok := multiStreamData["type"].(string); ok { switch topics[0] { case wsOrder25, wsOrder200: + var enabled bool switch wsType { case wsOperationSnapshot: var response WsFuturesOrderbook @@ -224,11 +225,15 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { } var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData[0].Symbol, asset.CoinMarginedFutures) + p, enabled, err = by.MatchSymbolCheckEnabled(response.OBData[0].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } + if !enabled { + return nil + } + err = by.processOrderbook(response.OBData, response.Type, p, @@ -236,7 +241,6 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { if err != nil { return err } - case wsOperationDelta: var response WsCoinDeltaOrderbook err = json.Unmarshal(respRaw, &response) @@ -246,11 +250,15 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { if len(response.OBData.Delete) > 0 { var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData.Delete[0].Symbol, asset.CoinMarginedFutures) + p, enabled, err = by.MatchSymbolCheckEnabled(response.OBData.Delete[0].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } + if !enabled { + return nil + } + err = by.processOrderbook(response.OBData.Delete, wsOrderbookActionDelete, p, @@ -262,11 +270,15 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { if len(response.OBData.Update) > 0 { var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData.Update[0].Symbol, asset.CoinMarginedFutures) + p, enabled, err = by.MatchSymbolCheckEnabled(response.OBData.Update[0].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } + if !enabled { + return nil + } + err = by.processOrderbook(response.OBData.Update, wsOrderbookActionUpdate, p, @@ -278,11 +290,15 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { if len(response.OBData.Insert) > 0 { var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData.Insert[0].Symbol, asset.CoinMarginedFutures) + p, enabled, err = by.MatchSymbolCheckEnabled(response.OBData.Insert[0].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } + if !enabled { + return nil + } + err = by.processOrderbook(response.OBData.Insert, wsOrderbookActionInsert, p, @@ -308,7 +324,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { trades := make([]trade.Data, len(response.TradeData)) for i := range response.TradeData { var p currency.Pair - p, err = by.extractCurrencyPair(response.TradeData[0].Symbol, asset.CoinMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.TradeData[0].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } @@ -344,7 +360,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { } var p currency.Pair - p, err = by.extractCurrencyPair(topics[len(topics)-1], asset.CoinMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(topics[len(topics)-1], asset.CoinMarginedFutures, false) if err != nil { return err } @@ -382,7 +398,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { } var p currency.Pair - p, err = by.extractCurrencyPair(response.Ticker.Symbol, asset.CoinMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Ticker.Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } @@ -411,7 +427,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { if len(response.Data.Delete) > 0 { for x := range response.Data.Delete { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data.Delete[x].Symbol, asset.CoinMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data.Delete[x].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } @@ -435,7 +451,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { if len(response.Data.Update) > 0 { for x := range response.Data.Update { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data.Update[x].Symbol, asset.CoinMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data.Update[x].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } @@ -459,7 +475,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { if len(response.Data.Insert) > 0 { for x := range response.Data.Insert { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data.Insert[x].Symbol, asset.CoinMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data.Insert[x].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } @@ -510,7 +526,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { for i := range response.Data { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data[i].Symbol, asset.CoinMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data[i].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } @@ -566,7 +582,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { } for x := range response.Data { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.CoinMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data[x].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } @@ -628,7 +644,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { } for x := range response.Data { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.CoinMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data[x].Symbol, asset.CoinMarginedFutures, false) if err != nil { return err } diff --git a/exchanges/bybit/bybit_ws_futures.go b/exchanges/bybit/bybit_ws_futures.go index 8eb6fe51..d5f2ef84 100644 --- a/exchanges/bybit/bybit_ws_futures.go +++ b/exchanges/bybit/bybit_ws_futures.go @@ -176,7 +176,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { } var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData[0].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.OBData[0].Symbol, asset.Futures, false) if err != nil { return err } @@ -198,7 +198,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { if len(response.OBData.Delete) > 0 { var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData.Delete[0].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.OBData.Delete[0].Symbol, asset.Futures, false) if err != nil { return err } @@ -213,7 +213,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { if len(response.OBData.Update) > 0 { var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData.Update[0].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.OBData.Update[0].Symbol, asset.Futures, false) if err != nil { return err } @@ -229,7 +229,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { if len(response.OBData.Insert) > 0 { var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData.Insert[0].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.OBData.Insert[0].Symbol, asset.Futures, false) if err != nil { return err } @@ -261,7 +261,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { trades := make([]trade.Data, len(response.TradeData)) for i := range response.TradeData { var p currency.Pair - p, err = by.extractCurrencyPair(response.TradeData[0].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.TradeData[0].Symbol, asset.Futures, false) if err != nil { return err } @@ -297,7 +297,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { } var p currency.Pair - p, err = by.extractCurrencyPair(topics[len(topics)-1], asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(topics[len(topics)-1], asset.Futures, false) if err != nil { return err } @@ -327,7 +327,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { } var p currency.Pair - p, err = by.extractCurrencyPair(response.Ticker.Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.Ticker.Symbol, asset.Futures, false) if err != nil { return err } @@ -356,7 +356,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { if len(response.Data.Delete) > 0 { for x := range response.Data.Delete { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data.Delete[x].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data.Delete[x].Symbol, asset.Futures, false) if err != nil { return err } @@ -380,7 +380,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { if len(response.Data.Update) > 0 { for x := range response.Data.Update { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data.Update[x].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data.Update[x].Symbol, asset.Futures, false) if err != nil { return err } @@ -404,7 +404,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { if len(response.Data.Insert) > 0 { for x := range response.Data.Insert { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data.Insert[x].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data.Insert[x].Symbol, asset.Futures, false) if err != nil { return err } @@ -455,7 +455,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { for i := range response.Data { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data[i].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data[i].Symbol, asset.Futures, false) if err != nil { return err } @@ -509,7 +509,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { } for x := range response.Data { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data[x].Symbol, asset.Futures, false) if err != nil { return err } @@ -571,7 +571,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { } for x := range response.Data { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.Futures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data[x].Symbol, asset.Futures, false) if err != nil { return err } diff --git a/exchanges/bybit/bybit_ws_ufutures.go b/exchanges/bybit/bybit_ws_ufutures.go index 2a42ec18..73f7fd0e 100644 --- a/exchanges/bybit/bybit_ws_ufutures.go +++ b/exchanges/bybit/bybit_ws_ufutures.go @@ -184,7 +184,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { } var p currency.Pair - p, err = by.extractCurrencyPair(response.Data.OBData[0].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data.OBData[0].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -206,7 +206,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { if len(response.OBData.Delete) > 0 { var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData.Delete[0].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.OBData.Delete[0].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -222,7 +222,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { if len(response.OBData.Update) > 0 { var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData.Update[0].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.OBData.Update[0].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -238,7 +238,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { if len(response.OBData.Insert) > 0 { var p currency.Pair - p, err = by.extractCurrencyPair(response.OBData.Insert[0].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.OBData.Insert[0].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -268,7 +268,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { trades := make([]trade.Data, len(response.TradeData)) for i := range response.TradeData { var p currency.Pair - p, err = by.extractCurrencyPair(response.TradeData[0].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.TradeData[0].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -303,7 +303,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { } var p currency.Pair - p, err = by.extractCurrencyPair(topics[len(topics)-1], asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(topics[len(topics)-1], asset.USDTMarginedFutures, false) if err != nil { return err } @@ -333,7 +333,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { } var p currency.Pair - p, err = by.extractCurrencyPair(response.Ticker.Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Ticker.Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -362,7 +362,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { if len(response.Data.Delete) > 0 { for x := range response.Data.Delete { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data.Delete[x].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data.Delete[x].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -386,7 +386,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { if len(response.Data.Update) > 0 { for x := range response.Data.Update { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data.Update[x].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data.Update[x].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -410,7 +410,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { if len(response.Data.Insert) > 0 { for x := range response.Data.Insert { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data.Insert[x].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data.Insert[x].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -461,7 +461,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { for i := range response.Data { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data[i].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data[i].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -515,7 +515,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { } for x := range response.Data { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data[x].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } @@ -577,7 +577,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { } for x := range response.Data { var p currency.Pair - p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.USDTMarginedFutures) + p, err = by.MatchSymbolWithAvailablePairs(response.Data[x].Symbol, asset.USDTMarginedFutures, false) if err != nil { return err } diff --git a/exchanges/coinut/coinut_types.go b/exchanges/coinut/coinut_types.go index abd7400e..f935aa17 100644 --- a/exchanges/coinut/coinut_types.go +++ b/exchanges/coinut/coinut_types.go @@ -125,8 +125,8 @@ type OrderResponse struct { // Commission holds trade commission structure type Commission struct { - Currency currency.Pair `json:"currency"` - Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + Amount float64 `json:"amount,string"` } // OrderFilledResponse contains order filled response @@ -545,8 +545,8 @@ type WsOrderData struct { // WsOrderFilledCommissionData ws response data type WsOrderFilledCommissionData struct { - Amount float64 `json:"amount,string"` - Currency currency.Pair `json:"currency"` + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` } // WsOrderRejectedResponse ws response @@ -594,8 +594,8 @@ type WsTradeHistoryResponse struct { // WsTradeHistoryCommissionData ws response data type WsTradeHistoryCommissionData struct { - Amount float64 `json:"amount,string"` - Currency currency.Pair `json:"currency"` + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` } // WsTradeHistoryTradeData ws response data diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 94ccfc0b..a2bf0f32 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" "time" + "unicode" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" @@ -1731,3 +1732,38 @@ func (b *Base) SetLeverage(_ context.Context, _ asset.Item, _ currency.Pair, _ m func (b *Base) GetLeverage(_ context.Context, _ asset.Item, _ currency.Pair, _ margin.Type, _ order.Side) (float64, error) { return -1, common.ErrNotYetImplemented } + +// MatchSymbolWithAvailablePairs returns a currency pair based on the supplied +// symbol and asset type. If the string is expected to have a delimiter this +// will attempt to screen it out. +func (b *Base) MatchSymbolWithAvailablePairs(symbol string, a asset.Item, hasDelimiter bool) (currency.Pair, error) { + if hasDelimiter { + for x := range symbol { + if unicode.IsPunct(rune(symbol[x])) { + symbol = symbol[:x] + symbol[x+1:] + break + } + } + } + return b.CurrencyPairs.Match(symbol, a) +} + +// MatchSymbolCheckEnabled returns a currency pair based on the supplied symbol +// and asset type against the available pairs list. If the string is expected to +// have a delimiter this will attempt to screen it out. It will also check if +// the pair is enabled. +func (b *Base) MatchSymbolCheckEnabled(symbol string, a asset.Item, hasDelimiter bool) (pair currency.Pair, enabled bool, err error) { + pair, err = b.MatchSymbolWithAvailablePairs(symbol, a, hasDelimiter) + if err != nil { + return pair, false, err + } + + enabled, err = b.IsPairEnabled(pair, a) + return +} + +// IsPairEnabled checks if a pair is enabled for an enabled asset type. +// TODO: Optimisation map for enabled pair matching, instead of linear traversal. +func (b *Base) IsPairEnabled(pair currency.Pair, a asset.Item) (bool, error) { + return b.CurrencyPairs.IsPairEnabled(pair, a) +} diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index fed98ae7..6c772da0 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -3089,3 +3089,139 @@ func TestGetStandardConfig(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", cfg.WebsocketTrafficTimeout, config.DefaultWebsocketTrafficTimeout) } } + +func TestMatchSymbolWithAvailablePairs(t *testing.T) { + t.Parallel() + b := Base{Name: "test"} + whatIWant := currency.NewPair(currency.BTC, currency.USDT) + err := b.CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{ + AssetEnabled: convert.BoolPtr(true), + Available: []currency.Pair{whatIWant}}) + if err != nil { + t.Fatal(err) + } + + _, err = b.MatchSymbolWithAvailablePairs("sillBillies", asset.Futures, false) + if !errors.Is(err, currency.ErrPairNotFound) { + t.Fatalf("received: '%v' but expected: '%v'", err, currency.ErrPairNotFound) + } + + whatIGot, err := b.MatchSymbolWithAvailablePairs("btcusdT", asset.Spot, false) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if !whatIGot.Equal(whatIWant) { + t.Fatalf("received: '%v' but expected: '%v'", whatIGot, whatIWant) + } + + whatIGot, err = b.MatchSymbolWithAvailablePairs("btc-usdT", asset.Spot, true) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if !whatIGot.Equal(whatIWant) { + t.Fatalf("received: '%v' but expected: '%v'", whatIGot, whatIWant) + } +} + +func TestMatchSymbolCheckEnabled(t *testing.T) { + t.Parallel() + b := Base{Name: "test"} + whatIWant := currency.NewPair(currency.BTC, currency.USDT) + availButNoEnabled := currency.NewPair(currency.BTC, currency.AUD) + err := b.CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{ + AssetEnabled: convert.BoolPtr(true), + Available: []currency.Pair{whatIWant, availButNoEnabled}, + Enabled: []currency.Pair{whatIWant}, + }) + if err != nil { + t.Fatal(err) + } + + _, _, err = b.MatchSymbolCheckEnabled("sillBillies", asset.Futures, false) + if !errors.Is(err, currency.ErrPairNotFound) { + t.Fatalf("received: '%v' but expected: '%v'", err, currency.ErrPairNotFound) + } + + whatIGot, enabled, err := b.MatchSymbolCheckEnabled("btcusdT", asset.Spot, false) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if !enabled { + t.Fatal("expected true") + } + + if !whatIGot.Equal(whatIWant) { + t.Fatalf("received: '%v' but expected: '%v'", whatIGot, whatIWant) + } + + whatIGot, enabled, err = b.MatchSymbolCheckEnabled("btc-usdT", asset.Spot, true) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if !whatIGot.Equal(whatIWant) { + t.Fatalf("received: '%v' but expected: '%v'", whatIGot, whatIWant) + } + + if !enabled { + t.Fatal("expected true") + } + + whatIGot, enabled, err = b.MatchSymbolCheckEnabled("btc-AUD", asset.Spot, true) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if !whatIGot.Equal(availButNoEnabled) { + t.Fatalf("received: '%v' but expected: '%v'", whatIGot, whatIWant) + } + + if enabled { + t.Fatal("expected false") + } +} + +func TestIsPairEnabled(t *testing.T) { + t.Parallel() + b := Base{Name: "test"} + whatIWant := currency.NewPair(currency.BTC, currency.USDT) + availButNoEnabled := currency.NewPair(currency.BTC, currency.AUD) + err := b.CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{ + AssetEnabled: convert.BoolPtr(true), + Available: []currency.Pair{whatIWant, availButNoEnabled}, + Enabled: []currency.Pair{whatIWant}, + }) + if err != nil { + t.Fatal(err) + } + + enabled, err := b.IsPairEnabled(currency.NewPair(currency.AAA, currency.CYC), asset.Spot) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if enabled { + t.Fatal("expected false") + } + + enabled, err = b.IsPairEnabled(availButNoEnabled, asset.Spot) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if enabled { + t.Fatal("expected false") + } + + enabled, err = b.IsPairEnabled(whatIWant, asset.Spot) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if !enabled { + t.Fatal("expected true") + } +} diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index d373f202..d7e02d2a 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -48,22 +48,6 @@ func TestMain(m *testing.M) { e.API.AuthenticatedSupport = true e.SetCredentials(APIKey, APISecret, "", "", "", "") - - err = e.UpdateTradablePairs(context.Background(), false) - if err != nil { - log.Fatal("Exmo UpdateTradablePairs error", err) - } - - avail, err := e.GetAvailablePairs(asset.Spot) - if err != nil { - log.Fatal("Exmo GetAvailablePairs error", err) - } - - err = e.CurrencyPairs.StorePairs(asset.Spot, avail, true) - if err != nil { - log.Fatal("Exmo StorePairs error", err) - } - os.Exit(m.Run()) } @@ -513,18 +497,19 @@ func TestUpdateTicker(t *testing.T) { func TestUpdateTickers(t *testing.T) { t.Parallel() + err := e.UpdateTickers(context.Background(), asset.Spot) if err != nil { t.Error(err) } - avail, err := e.GetAvailablePairs(asset.Spot) + enabled, err := e.GetEnabledPairs(asset.Spot) if err != nil { t.Fatal(err) } - for x := range avail { - _, err := ticker.GetTicker(e.Name, avail[x], asset.Spot) + for x := range enabled { + _, err := ticker.GetTicker(e.Name, enabled[x], asset.Spot) if err != nil { t.Error(err) } diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index a56cff47..dbed8c21 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -206,28 +206,21 @@ func (e *EXMO) UpdateTradablePairs(ctx context.Context, forceUpdate bool) error // UpdateTickers updates the ticker for all currency pairs of a given asset type func (e *EXMO) UpdateTickers(ctx context.Context, a asset.Item) error { - avail, err := e.GetAvailablePairs(a) - if err != nil { - return err - } - - enabled, err := e.GetEnabledPairs(a) - if err != nil { - return err - } - result, err := e.GetTicker(ctx) if err != nil { return err } + var enabled bool for symbol, tick := range result { var pair currency.Pair - pair, err = avail.DeriveFrom(strings.Replace(symbol, "_", "", 1)) + pair, enabled, err = e.MatchSymbolCheckEnabled(symbol, asset.Spot, true) if err != nil { - return err + if !errors.Is(err, currency.ErrPairNotFound) { + return err + } } - if !enabled.Contains(pair, true) { + if !enabled { continue } err = ticker.ProcessTicker(&ticker.Price{ diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 1076e203..973e5568 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -2112,11 +2112,6 @@ func (g *Gateio) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) e return fmt.Errorf("%s %w", a, asset.ErrNotSupported) } - avail, err := g.GetAvailablePairs(a) - if err != nil { - return err - } - var limits []order.MinMaxLevel switch a { case asset.Spot: @@ -2132,7 +2127,7 @@ func (g *Gateio) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) e continue } var pair currency.Pair - pair, err = avail.DeriveFrom(strings.ReplaceAll(pairsData[x].ID, "_", "")) + pair, err = g.MatchSymbolWithAvailablePairs(pairsData[x].ID, a, true) if err != nil { return err } diff --git a/exchanges/gemini/gemini_types.go b/exchanges/gemini/gemini_types.go index 7736e5dc..b12c7c09 100644 --- a/exchanges/gemini/gemini_types.go +++ b/exchanges/gemini/gemini_types.go @@ -31,17 +31,17 @@ type Ticker struct { // TickerV2 holds returned ticker data from the exchange type TickerV2 struct { - Ask float64 `json:"ask,string"` - Bid float64 `json:"bid,string"` - Changes []string `json:"changes"` - Close float64 `json:"close,string"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - Open float64 `json:"open,string"` - Message string `json:"message,omitempty"` - Reason string `json:"reason,omitempty"` - Result string `json:"result,omitempty"` - Symbol currency.Pair `json:"symbol"` + Ask float64 `json:"ask,string"` + Bid float64 `json:"bid,string"` + Changes []string `json:"changes"` + Close float64 `json:"close,string"` + High float64 `json:"high,string"` + Low float64 `json:"low,string"` + Open float64 `json:"open,string"` + Message string `json:"message,omitempty"` + Reason string `json:"reason,omitempty"` + Result string `json:"result,omitempty"` + Symbol string `json:"symbol"` } // Orderbook contains orderbook information for both bid and ask side diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index 46f989fa..2c6b044d 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -186,23 +186,18 @@ func TestUpdateTicker(t *testing.T) { } func TestUpdateTickers(t *testing.T) { - avail, err := h.GetAvailablePairs(asset.Spot) - if err != nil { - t.Fatal(err) - } - - err = h.CurrencyPairs.StorePairs(asset.Spot, avail, true) - if err != nil { - t.Fatal(err) - } - - err = h.UpdateTickers(context.Background(), asset.Spot) + err := h.UpdateTickers(context.Background(), asset.Spot) if err != nil { t.Error(err) } - for j := range avail { - _, err = h.FetchTicker(context.Background(), avail[j], asset.Spot) + enabled, err := h.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + + for j := range enabled { + _, err = h.FetchTicker(context.Background(), enabled[j], asset.Spot) if err != nil { t.Error(err) } diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 1e12f44c..300ed1b1 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -291,9 +291,9 @@ func (h *HitBTC) FetchTradablePairs(ctx context.Context, _ asset.Item) (currency pairs := make([]currency.Pair, len(symbols)) for x := range symbols { - quote := strings.Replace(symbols[x].ID, symbols[x].BaseCurrency, "", 1) + index := strings.Index(symbols[x].ID, symbols[x].QuoteCurrency) var pair currency.Pair - pair, err = currency.NewPairFromStrings(symbols[x].BaseCurrency, quote) + pair, err = currency.NewPairFromStrings(symbols[x].ID[:index], symbols[x].ID[index:]) if err != nil { return nil, err } @@ -322,23 +322,18 @@ func (h *HitBTC) UpdateTickers(ctx context.Context, a asset.Item) error { if err != nil { return err } - avail, err := h.GetAvailablePairs(a) - if err != nil { - return err - } - - enabled, err := h.GetEnabledPairs(a) - if err != nil { - return err - } for x := range tick { - pair, err := avail.DeriveFrom(tick[x].Symbol) + var pair currency.Pair + var enabled bool + pair, enabled, err = h.MatchSymbolCheckEnabled(tick[x].Symbol, a, false) if err != nil { - return err + if !errors.Is(err, currency.ErrPairNotFound) { + return err + } } - if !enabled.Contains(pair, true) { + if !enabled { continue } diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go index 658d096a..bbaf8241 100644 --- a/exchanges/interfaces.go +++ b/exchanges/interfaces.go @@ -97,6 +97,18 @@ type IBotExchange interface { CurrencyStateManagement FuturesManagement MarginManagement + + // MatchSymbolWithAvailablePairs returns a currency pair based on the supplied + // symbol and asset type. If the string is expected to have a delimiter this + // will attempt to screen it out. + MatchSymbolWithAvailablePairs(symbol string, a asset.Item, hasDelimiter bool) (currency.Pair, error) + // MatchSymbolCheckEnabled returns a currency pair based on the supplied symbol + // and asset type against the available pairs list. If the string is expected to + // have a delimiter this will attempt to screen it out. It will also check if + // the pair is enabled. + MatchSymbolCheckEnabled(symbol string, a asset.Item, hasDelimiter bool) (pair currency.Pair, enabled bool, err error) + // IsPairEnabled checks if a pair is enabled for an enabled asset type + IsPairEnabled(pair currency.Pair, a asset.Item) (bool, error) } // OrderManagement defines functionality for order management diff --git a/exchanges/okx/okx.go b/exchanges/okx/okx.go index bb5fc88c..7d7fafd7 100644 --- a/exchanges/okx/okx.go +++ b/exchanges/okx/okx.go @@ -4328,10 +4328,18 @@ func (ok *Okx) GetAssetsFromInstrumentTypeOrID(instType, instrumentID string) ([ switch { case len(splitSymbol) == 2: resp := make([]asset.Item, 0, 2) - if err := ok.CurrencyPairs.IsAssetPairEnabled(asset.Spot, pair); err == nil { + enabled, err := ok.IsPairEnabled(pair, asset.Spot) + if err != nil { + return nil, err + } + if enabled { resp = append(resp, asset.Spot) } - if err := ok.CurrencyPairs.IsAssetPairEnabled(asset.Margin, pair); err == nil { + enabled, err = ok.IsPairEnabled(pair, asset.Margin) + if err != nil { + return nil, err + } + if enabled { resp = append(resp, asset.Margin) } if len(resp) > 0 { @@ -4340,15 +4348,27 @@ func (ok *Okx) GetAssetsFromInstrumentTypeOrID(instType, instrumentID string) ([ case len(splitSymbol) > 2: switch splitSymbol[len(splitSymbol)-1] { case "SWAP", "swap": - if err := ok.CurrencyPairs.IsAssetPairEnabled(asset.PerpetualSwap, pair); err == nil { + enabled, err := ok.IsPairEnabled(pair, asset.PerpetualSwap) + if err != nil { + return nil, err + } + if enabled { return []asset.Item{asset.PerpetualSwap}, nil } case "C", "P", "c", "p": - if err := ok.CurrencyPairs.IsAssetPairEnabled(asset.Options, pair); err == nil { + enabled, err := ok.IsPairEnabled(pair, asset.Options) + if err != nil { + return nil, err + } + if enabled { return []asset.Item{asset.Options}, nil } default: - if err := ok.CurrencyPairs.IsAssetPairEnabled(asset.Futures, pair); err == nil { + enabled, err := ok.IsPairEnabled(pair, asset.Futures) + if err != nil { + return nil, err + } + if enabled { return []asset.Item{asset.Futures}, nil } }