From 29ee9902d47aa23f572086c366572b2dad5f602d Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Thu, 29 Aug 2024 09:50:14 +0700 Subject: [PATCH 1/6] Kline: Fix Raw Short, Marshal and Unmarshal --- exchanges/kline/kline.go | 7 +++++++ exchanges/kline/kline_test.go | 27 +++++++++++++-------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/exchanges/kline/kline.go b/exchanges/kline/kline.go index 5a8ea21ebb1..a972af3af18 100644 --- a/exchanges/kline/kline.go +++ b/exchanges/kline/kline.go @@ -135,6 +135,9 @@ func (i Interval) Duration() time.Duration { // Short returns short string version of interval func (i Interval) Short() string { + if i == Raw { + return "raw" + } s := i.String() if strings.HasSuffix(s, "m0s") { s = s[:len(s)-2] @@ -149,6 +152,10 @@ func (i Interval) Short() string { // It does not validate the duration is aligned, only that it is a parsable duration func (i *Interval) UnmarshalJSON(text []byte) error { text = bytes.Trim(text, `"`) + if string(text) == "raw" { + *i = Raw + return nil + } if len(bytes.TrimLeft(text, `0123456789`)) > 0 { // contains non-numerics, ParseDuration can handle errors d, err := time.ParseDuration(string(text)) if err != nil { diff --git a/exchanges/kline/kline_test.go b/exchanges/kline/kline_test.go index a17e21637e1..cf29e9bffb1 100644 --- a/exchanges/kline/kline_test.go +++ b/exchanges/kline/kline_test.go @@ -143,9 +143,9 @@ func TestKlineDuration(t *testing.T) { func TestKlineShort(t *testing.T) { t.Parallel() - if OneDay.Short() != "24h" { - t.Fatalf("unexpected result: %v", OneDay.Short()) - } + assert.Equal(t, "24h", OneDay.Short(), "One day should show as 24h") + assert.Equal(t, "1h", OneHour.Short(), "One hour should truncate 0m0s suffix") + assert.Equal(t, "raw", Raw.Short(), "Raw should return raw") } func TestDurationToWord(t *testing.T) { @@ -1403,16 +1403,15 @@ func TestGetIntervalResultLimit(t *testing.T) { } func TestUnmarshalJSON(t *testing.T) { - i := new(Interval) - err := i.UnmarshalJSON([]byte(`"3m"`)) - assert.NoError(t, err, "UnmarshalJSON should not error") - assert.Equal(t, time.Minute*3, i.Duration(), "Interval should have correct value") - err = i.UnmarshalJSON([]byte(`"15s"`)) - assert.NoError(t, err, "UnmarshalJSON should not error") - assert.Equal(t, time.Second*15, i.Duration(), "Interval should have correct value") - err = i.UnmarshalJSON([]byte(`720000000000`)) - assert.NoError(t, err, "UnmarshalJSON should not error") - assert.Equal(t, time.Minute*12, i.Duration(), "Interval should have correct value") - err = i.UnmarshalJSON([]byte(`"6hedgehogs"`)) + t.Parallel() + var i Interval + for _, tt := range []struct { + in string + exp Interval + }{{`"3m"`, ThreeMin}, {`"15s"`, FifteenSecond}, {`720000000000`, OneMin * 12}, {`"-1ns"`, Raw}, {`"raw"`, Raw}} { + err := i.UnmarshalJSON([]byte(tt.in)) + assert.NoErrorf(t, err, "UnmarshalJSON should not error on %q", tt.in) + } + err := i.UnmarshalJSON([]byte(`"6hedgehogs"`)) assert.ErrorContains(t, err, "unknown unit", "UnmarshalJSON should error") } From 8a7123d7c9a170fb332a6106371e7d651e0482d5 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Thu, 30 May 2024 16:33:38 +0700 Subject: [PATCH 2/6] Deribit: Rename GenerateDefaultSubs --- exchanges/deribit/deribit_test.go | 4 ++-- exchanges/deribit/deribit_websocket.go | 4 ++-- exchanges/deribit/deribit_wrapper.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exchanges/deribit/deribit_test.go b/exchanges/deribit/deribit_test.go index 84b92c7b2d2..cd33f742124 100644 --- a/exchanges/deribit/deribit_test.go +++ b/exchanges/deribit/deribit_test.go @@ -3266,9 +3266,9 @@ func setupWs() { } } -func TestGenerateDefaultSubscriptions(t *testing.T) { +func TestGenerateSubscriptions(t *testing.T) { t.Parallel() - result, err := d.GenerateDefaultSubscriptions() + result, err := d.generateSubscriptions() require.NoError(t, err) assert.NotNil(t, result) } diff --git a/exchanges/deribit/deribit_websocket.go b/exchanges/deribit/deribit_websocket.go index d99dc86293d..b34f7f52d1d 100644 --- a/exchanges/deribit/deribit_websocket.go +++ b/exchanges/deribit/deribit_websocket.go @@ -740,8 +740,8 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error { return nil } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) { +// generateSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() +func (d *Deribit) generateSubscriptions() (subscription.List, error) { var subscriptions subscription.List assets := d.GetAssetTypes(true) subscriptionChannels := defaultSubscriptions diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index 38023875caa..ce448e4251e 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -197,7 +197,7 @@ func (d *Deribit) Setup(exch *config.Exchange) error { Connector: d.WsConnect, Subscriber: d.Subscribe, Unsubscriber: d.Unsubscribe, - GenerateSubscriptions: d.GenerateDefaultSubscriptions, + GenerateSubscriptions: d.generateSubscriptions, Features: &d.Features.Supports.WebsocketCapabilities, OrderbookBufferConfig: buffer.Config{ SortBuffer: true, From 7b9a6ec711f15ba6a290a74c60fe48bb3609d4c4 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Tue, 27 Aug 2024 11:54:28 +0700 Subject: [PATCH 3/6] Deribit: Remove custom GetDefaultConfig Moved to exchange base by #1472 --- exchanges/deribit/deribit_wrapper.go | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index ce448e4251e..e7e47507418 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -34,27 +34,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) -// GetDefaultConfig returns a default exchange config -func (d *Deribit) GetDefaultConfig(ctx context.Context) (*config.Exchange, error) { - d.SetDefaults() - exchCfg, err := d.GetStandardConfig() - if err != nil { - return nil, err - } - - err = d.SetupDefaults(exchCfg) - if err != nil { - return nil, err - } - if d.Features.Supports.RESTCapabilities.AutoPairUpdates { - err := d.UpdateTradablePairs(ctx, true) - if err != nil { - return nil, err - } - } - return exchCfg, nil -} - // SetDefaults sets the basic defaults for Deribit func (d *Deribit) SetDefaults() { d.Name = "Deribit" From 2d742c794a8909a8b139d6d60443182d03fe9fc6 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Thu, 29 Aug 2024 11:40:30 +0700 Subject: [PATCH 4/6] Deribit: Straight Rename of eps to endpoints Since I had to ask what this abbreviation meant, I think we should abandon it --- .../deribit/{deribit_websocket_eps.go => deribit_ws_endpoints.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename exchanges/deribit/{deribit_websocket_eps.go => deribit_ws_endpoints.go} (100%) diff --git a/exchanges/deribit/deribit_websocket_eps.go b/exchanges/deribit/deribit_ws_endpoints.go similarity index 100% rename from exchanges/deribit/deribit_websocket_eps.go rename to exchanges/deribit/deribit_ws_endpoints.go From 2387d8bfaa84a9807689c4f32e3bfe4b7e0b43d4 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Sun, 2 Jun 2024 17:07:18 +0700 Subject: [PATCH 5/6] Deribit: Add Subscription configuration --- .../exchanges_templates/deribit.tmpl | 21 +- exchanges/deribit/README.md | 21 +- exchanges/deribit/deribit_test.go | 77 ++- exchanges/deribit/deribit_websocket.go | 605 +++++------------- exchanges/deribit/deribit_wrapper.go | 1 + 5 files changed, 268 insertions(+), 457 deletions(-) diff --git a/cmd/documentation/exchanges_templates/deribit.tmpl b/cmd/documentation/exchanges_templates/deribit.tmpl index 9e04bfa9c36..1277ed1a3e7 100644 --- a/cmd/documentation/exchanges_templates/deribit.tmpl +++ b/cmd/documentation/exchanges_templates/deribit.tmpl @@ -93,12 +93,23 @@ if err != nil { } ``` -### How to do Websocket public/private calls +### Subscriptions -```go - // Exchanges will be abstracted out in further updates and examples will be - // supplied then -``` +All default subscriptions are for all enabled assets. + +Default Public Subscriptions: +- Candles ( Timeframe: 1 day ) +- Orderbook ( Full depth @ Interval: 100ms ) +- Ticker ( Interval: 100ms ) +- All Trades ( Interval: 100ms ) + +Default Authenticated Subscriptions: +- My Account Orders +- My Account Trades + +kline.Raw Interval configurable for a raw orderbook subscription when authenticated + +Subscriptions are subject to enabled assets and pairs. ### Please click GoDocs chevron above to view current GoDoc information for this package {{template "contributions"}} diff --git a/exchanges/deribit/README.md b/exchanges/deribit/README.md index 57e04fdcaee..3e6595258f7 100644 --- a/exchanges/deribit/README.md +++ b/exchanges/deribit/README.md @@ -111,12 +111,23 @@ if err != nil { } ``` -### How to do Websocket public/private calls +### Subscriptions -```go - // Exchanges will be abstracted out in further updates and examples will be - // supplied then -``` +All default subscriptions are for all enabled assets. + +Default Public Subscriptions: +- Candles ( Timeframe: 1 day ) +- Orderbook ( Full depth @ Interval: 100ms ) +- Ticker ( Interval: 100ms ) +- All Trades ( Interval: 100ms ) + +Default Authenticated Subscriptions: +- My Account Orders +- My Account Trades + +kline.Raw Interval configurable for a raw orderbook subscription when authenticated + +Subscriptions are subject to enabled assets and pairs. ### Please click GoDocs chevron above to view current GoDoc information for this package diff --git a/exchanges/deribit/deribit_test.go b/exchanges/deribit/deribit_test.go index cd33f742124..693ae836e0a 100644 --- a/exchanges/deribit/deribit_test.go +++ b/exchanges/deribit/deribit_test.go @@ -25,9 +25,11 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" + "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/trade" testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange" + testsubs "github.com/thrasher-corp/gocryptotrader/internal/testing/subscriptions" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -3268,9 +3270,80 @@ func setupWs() { func TestGenerateSubscriptions(t *testing.T) { t.Parallel() - result, err := d.generateSubscriptions() + + d := new(Deribit) //nolint:govet // Intentional lexical scope shadow + require.NoError(t, testexch.Setup(d), "Test instance Setup must not error") + + d.Websocket.SetCanUseAuthenticatedEndpoints(true) + subs, err := d.generateSubscriptions() require.NoError(t, err) - assert.NotNil(t, result) + exp := subscription.List{} + for _, s := range d.Features.Subscriptions { + for _, a := range d.GetAssetTypes(true) { + if !d.IsAssetWebsocketSupported(a) { + continue + } + pairs, err := d.GetEnabledPairs(a) + require.NoErrorf(t, err, "GetEnabledPairs %s must not error", a) + s := s.Clone() //nolint:govet // Intentional lexical scope shadow + s.Asset = a + if isSymbolChannel(s) { + for i, p := range pairs { + s := s.Clone() //nolint:govet // Intentional lexical scope shadow + s.QualifiedChannel = channelName(s) + "." + p.String() + if s.Interval != 0 { + s.QualifiedChannel += "." + channelInterval(s) + } + s.Pairs = pairs[i : i+1] + exp = append(exp, s) + } + } else { + s.Pairs = pairs + s.QualifiedChannel = channelName(s) + exp = append(exp, s) + } + } + } + testsubs.EqualLists(t, exp, subs) +} + +func TestChannelInterval(t *testing.T) { + t.Parallel() + + for _, i := range []int{1, 3, 5, 10, 15, 30, 60, 120, 180, 360, 720} { + a := channelInterval(&subscription.Subscription{Channel: subscription.CandlesChannel, Interval: kline.Interval(i * int(time.Minute))}) + assert.Equal(t, strconv.Itoa(i), a) + } + + a := channelInterval(&subscription.Subscription{Channel: subscription.CandlesChannel, Interval: kline.OneDay}) + assert.Equal(t, "1D", a) + + assert.Panics(t, func() { + channelInterval(&subscription.Subscription{Channel: subscription.CandlesChannel, Interval: kline.OneMonth}) + }) + + a = channelInterval(&subscription.Subscription{Channel: subscription.OrderbookChannel, Interval: kline.ThousandMilliseconds}) + assert.Equal(t, "agg2", a, "1 second should expand to agg2") + + a = channelInterval(&subscription.Subscription{Channel: subscription.OrderbookChannel, Interval: kline.HundredMilliseconds}) + assert.Equal(t, "100ms", a, "100ms should expand correctly") + + a = channelInterval(&subscription.Subscription{Channel: subscription.OrderbookChannel, Interval: kline.Raw}) + assert.Equal(t, "raw", a, "raw should expand correctly") + + assert.Panics(t, func() { + channelInterval(&subscription.Subscription{Channel: subscription.OrderbookChannel, Interval: kline.OneMonth}) + }) + + a = channelInterval(&subscription.Subscription{Channel: userAccessLogChannel}) + assert.Empty(t, a, "Anything else should return empty") +} + +func TestChannelName(t *testing.T) { + t.Parallel() + assert.Equal(t, tickerChannel, channelName(&subscription.Subscription{Channel: subscription.TickerChannel})) + assert.Equal(t, userLockChannel, channelName(&subscription.Subscription{Channel: userLockChannel})) + assert.Panics(t, func() { channelName(&subscription.Subscription{Channel: "wibble"}) }, "Unknown channels should panic") } func TestFetchTicker(t *testing.T) { diff --git a/exchanges/deribit/deribit_websocket.go b/exchanges/deribit/deribit_websocket.go index b34f7f52d1d..21e7088bb7b 100644 --- a/exchanges/deribit/deribit_websocket.go +++ b/exchanges/deribit/deribit_websocket.go @@ -3,10 +3,12 @@ package deribit import ( "context" "encoding/json" + "errors" "fmt" "net/http" "strconv" "strings" + "text/template" "time" "github.com/gorilla/websocket" @@ -50,33 +52,59 @@ const ( platformStatePublicMethodsStateChannel = "platform_state.public_methods_state" quoteChannel = "quote" requestForQuoteChannel = "rfq" - tickerChannel = "ticker." - tradesChannel = "trades." - tradesWithKindChannel = "trades" + tickerChannel = "ticker" + tradesChannel = "trades" // private websocket channels - userAccessLogChannel = "user.access_log" - userChangesInstrumentsChannel = "user.changes." - userChangesCurrencyChannel = "user.changes" - userLockChannel = "user.lock" - userMMPTriggerChannel = "user.mmp_trigger" - rawUserOrdersChannel = "user.orders.%s.raw" - userOrdersWithIntervalChannel = "user.orders." - rawUsersOrdersKindCurrencyChannel = "user.orders.%s.%s.raw" - rawUsersOrdersWithKindCurrencyAndIntervalChannel = "user.orders" - userPortfolioChannel = "user.portfolio" - userTradesChannelByInstrument = "user.trades." - userTradesByKindCurrencyAndIntervalChannel = "user.trades" + userAccessLogChannel = "user.access_log" + userChangesInstrumentsChannel = "user.changes." + userChangesCurrencyChannel = "user.changes" + userLockChannel = "user.lock" + userMMPTriggerChannel = "user.mmp_trigger" + userOrdersChannel = "user.orders" + userTradesChannel = "user.trades" + userPortfolioChannel = "user.portfolio" ) -var ( - defaultSubscriptions = []string{ - chartTradesChannel, // chart trades channel to fetch candlestick data. - orderbookChannel, - tickerChannel, - tradesWithKindChannel, - } +var subscriptionNames = map[string]string{ + subscription.TickerChannel: tickerChannel, + subscription.OrderbookChannel: orderbookChannel, + subscription.CandlesChannel: chartTradesChannel, + subscription.AllTradesChannel: tradesChannel, + subscription.MyTradesChannel: userTradesChannel, + subscription.MyOrdersChannel: userOrdersChannel, + announcementsChannel: announcementsChannel, + priceIndexChannel: priceIndexChannel, + priceRankingChannel: priceRankingChannel, + priceStatisticsChannel: priceStatisticsChannel, + volatilityIndexChannel: volatilityIndexChannel, + estimatedExpirationPriceChannel: estimatedExpirationPriceChannel, + incrementalTickerChannel: incrementalTickerChannel, + instrumentStateChannel: instrumentStateChannel, + markPriceOptionsChannel: markPriceOptionsChannel, + perpetualChannel: perpetualChannel, + platformStateChannel: platformStateChannel, + platformStatePublicMethodsStateChannel: platformStatePublicMethodsStateChannel, + quoteChannel: quoteChannel, + requestForQuoteChannel: requestForQuoteChannel, + userAccessLogChannel: userAccessLogChannel, + userChangesInstrumentsChannel: userChangesInstrumentsChannel, + userChangesCurrencyChannel: userChangesCurrencyChannel, + userLockChannel: userLockChannel, + userMMPTriggerChannel: userMMPTriggerChannel, + userPortfolioChannel: userPortfolioChannel, +} +var defaultSubscriptions = subscription.List{ + {Enabled: true, Asset: asset.All, Channel: subscription.CandlesChannel, Interval: kline.OneDay}, + {Enabled: true, Asset: asset.All, Channel: subscription.OrderbookChannel, Interval: kline.HundredMilliseconds}, // Raw is available for authenticated users + {Enabled: true, Asset: asset.All, Channel: subscription.TickerChannel, Interval: kline.HundredMilliseconds}, + {Enabled: true, Asset: asset.All, Channel: subscription.AllTradesChannel, Interval: kline.HundredMilliseconds}, + {Enabled: true, Asset: asset.All, Channel: subscription.MyOrdersChannel, Interval: kline.HundredMilliseconds, Authenticated: true}, + {Enabled: true, Asset: asset.All, Channel: subscription.MyTradesChannel, Interval: kline.HundredMilliseconds, Authenticated: true}, +} + +var ( indexENUMS = []string{"ada_usd", "algo_usd", "avax_usd", "bch_usd", "bnb_usd", "btc_usd", "doge_usd", "dot_usd", "eth_usd", "link_usd", "ltc_usd", "luna_usd", "matic_usd", "near_usd", "shib_usd", "sol_usd", "trx_usd", "uni_usd", "usdc_usd", "xrp_usd", "ada_usdc", "bch_usdc", "algo_usdc", "avax_usdc", "btc_usdc", "doge_usdc", "dot_usdc", "bch_usdc", "bnb_usdc", "eth_usdc", "link_usdc", "ltc_usdc", "luna_usdc", "matic_usdc", "near_usdc", "shib_usdc", "sol_usdc", "trx_usdc", "uni_usdc", "xrp_usdc", "btcdvol_usdc", "ethdvol_usdc"} pingMessage = WsSubscriptionInput{ @@ -740,446 +768,75 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error { return nil } -// generateSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() +// generateSubscriptions returns a list of configured subscriptions func (d *Deribit) generateSubscriptions() (subscription.List, error) { - var subscriptions subscription.List - assets := d.GetAssetTypes(true) - subscriptionChannels := defaultSubscriptions - if d.Websocket.CanUseAuthenticatedEndpoints() { - subscriptionChannels = append( - subscriptionChannels, - - // authenticated subscriptions - rawUsersOrdersKindCurrencyChannel, - rawUsersOrdersWithKindCurrencyAndIntervalChannel, - userTradesByKindCurrencyAndIntervalChannel, - ) - } - var err error - assetPairs := make(map[asset.Item][]currency.Pair, len(assets)) - for _, a := range assets { - assetPairs[a], err = d.GetEnabledPairs(a) - if err != nil { - return nil, err - } - if len(assetPairs[a]) > 5 { - assetPairs[a] = assetPairs[a][:5] - } - } - for x := range subscriptionChannels { - switch subscriptionChannels[x] { - case chartTradesChannel: - for _, a := range assets { - for z := range assetPairs[a] { - if ((assetPairs[a][z].Quote.Upper().String() == perpString || - !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) && - a == asset.Futures) || (a != asset.Spot && a != asset.Futures) { - continue - } - subscriptions = append(subscriptions, - &subscription.Subscription{ - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{assetPairs[a][z]}, - Params: map[string]interface{}{ - "resolution": "1D", - }, - Asset: a, - }) - } - } - case incrementalTickerChannel, - quoteChannel, - rawUserOrdersChannel: - for _, a := range assets { - for z := range assetPairs[a] { - if ((assetPairs[a][z].Quote.Upper().String() == perpString || - !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) && - a == asset.Futures) || (a != asset.Spot && a != asset.Futures) { - continue - } - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{assetPairs[a][z]}, - Asset: a, - }) - } - } - case orderbookChannel: - for _, a := range assets { - for z := range assetPairs[a] { - if ((assetPairs[a][z].Quote.Upper().String() == perpString || - !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) && - a == asset.Futures) || (a != asset.Spot && a != asset.Futures) { - continue - } - subscriptions = append(subscriptions, - &subscription.Subscription{ - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{assetPairs[a][z]}, - // if needed, group and depth of orderbook can be passed as follow "group": "250", "depth": "20", - Interval: kline.HundredMilliseconds, - Asset: a, - Params: map[string]interface{}{ - "group": "none", - "depth": "10", - }, - }, - ) - if d.Websocket.CanUseAuthenticatedEndpoints() { - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: orderbookChannel, - Pairs: currency.Pairs{assetPairs[a][z]}, - Asset: a, - Interval: kline.Interval(0), - Params: map[string]interface{}{ - "group": "none", - "depth": "10", - }, - }) - } - } - } - case tickerChannel, - tradesChannel: - for _, a := range assets { - for z := range assetPairs[a] { - if ((assetPairs[a][z].Quote.Upper().String() != perpString && - !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) && - a == asset.Futures) || (a != asset.Spot && a != asset.Futures) { - continue - } - subscriptions = append(subscriptions, - &subscription.Subscription{ - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{assetPairs[a][z]}, - Interval: kline.HundredMilliseconds, - Asset: a, - }) - } - } - case perpetualChannel, - userChangesInstrumentsChannel, - userTradesChannelByInstrument: - for _, a := range assets { - for z := range assetPairs[a] { - if subscriptionChannels[x] == perpetualChannel && !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString) { - continue - } - subscriptions = append(subscriptions, - &subscription.Subscription{ - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{assetPairs[a][z]}, - Interval: kline.HundredMilliseconds, - Asset: a, - }) - } - } - case instrumentStateChannel, - rawUsersOrdersKindCurrencyChannel: - var okay bool - for _, a := range assets { - currencyPairsName := make(map[currency.Code]bool, 2*len(assetPairs[a])) - for z := range assetPairs[a] { - if okay = currencyPairsName[assetPairs[a][z].Base]; !okay { - subscriptions = append(subscriptions, &subscription.Subscription{ - Asset: a, - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Base}}, - }) - currencyPairsName[assetPairs[a][z].Base] = true - } - if okay = currencyPairsName[assetPairs[a][z].Quote]; !okay { - subscriptions = append(subscriptions, &subscription.Subscription{ - Asset: a, - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Quote}}, - }) - currencyPairsName[assetPairs[a][z].Quote] = true - } - } - } - case userChangesCurrencyChannel, - userOrdersWithIntervalChannel, - rawUsersOrdersWithKindCurrencyAndIntervalChannel, - userTradesByKindCurrencyAndIntervalChannel, - tradesWithKindChannel: - for _, a := range assets { - currencyPairsName := make(map[currency.Code]bool, 2*len(assetPairs[a])) - var okay bool - for z := range assetPairs[a] { - if okay = currencyPairsName[assetPairs[a][z].Base]; !okay { - subscriptions = append(subscriptions, &subscription.Subscription{ - Asset: a, - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Base}}, - Interval: kline.HundredMilliseconds, - }) - currencyPairsName[assetPairs[a][z].Base] = true - } - if okay = currencyPairsName[assetPairs[a][z].Quote]; !okay { - subscriptions = append(subscriptions, &subscription.Subscription{ - Asset: a, - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Quote}}, - Interval: kline.HundredMilliseconds, - }) - currencyPairsName[assetPairs[a][z].Quote] = true - } - } - } - case requestForQuoteChannel, - userMMPTriggerChannel, - userPortfolioChannel: - for _, a := range assets { - currencyPairsName := make(map[currency.Code]bool, 2*len(assetPairs[a])) - var okay bool - for z := range assetPairs[a] { - if okay = currencyPairsName[assetPairs[a][z].Base]; !okay { - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Base}}, - Asset: a, - }) - currencyPairsName[assetPairs[a][z].Base] = true - } - if okay = currencyPairsName[assetPairs[a][z].Quote]; !okay { - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: subscriptionChannels[x], - Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Quote}}, - Asset: a, - }) - currencyPairsName[assetPairs[a][z].Quote] = true - } - } - } - case announcementsChannel, - userAccessLogChannel, - platformStateChannel, - userLockChannel, - platformStatePublicMethodsStateChannel: - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: subscriptionChannels[x], - }) - case priceIndexChannel, - priceRankingChannel, - priceStatisticsChannel, - volatilityIndexChannel, - markPriceOptionsChannel, - estimatedExpirationPriceChannel: - for i := range indexENUMS { - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: subscriptionChannels[x], - Params: map[string]interface{}{ - "index_name": indexENUMS[i], - }, - }) - } - } - } - return subscriptions, nil + return d.Features.Subscriptions.ExpandTemplates(d) } -func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs subscription.List) ([]WsSubscriptionInput, error) { - subscriptionPayloads := make([]WsSubscriptionInput, len(subscs)) - for x := range subscs { - if len(subscs[x].Pairs) > 1 { - return nil, subscription.ErrNotSinglePair - } - sub := WsSubscriptionInput{ - JSONRPCVersion: rpcVersion, - ID: d.Websocket.Conn.GenerateMessageID(false), - Method: "public/" + operation, - Params: map[string][]string{}, - } - switch subscs[x].Channel { - case userAccessLogChannel, userChangesInstrumentsChannel, userChangesCurrencyChannel, userLockChannel, userMMPTriggerChannel, rawUserOrdersChannel, - userOrdersWithIntervalChannel, rawUsersOrdersKindCurrencyChannel, userPortfolioChannel, userTradesChannelByInstrument, userTradesByKindCurrencyAndIntervalChannel: - if !d.Websocket.CanUseAuthenticatedEndpoints() { - continue - } - sub.Method = "private/" + operation - } - var instrumentID string - if len(subscs[x].Pairs) == 1 { - pairFormat, err := d.GetPairFormat(subscs[x].Asset, true) - if err != nil { - return nil, err - } - subscs[x].Pairs = subscs[x].Pairs.Format(pairFormat) - if subscs[x].Asset == asset.Futures { - instrumentID = d.formatFuturesTradablePair(subscs[x].Pairs[0]) - } else { - instrumentID = subscs[x].Pairs.Join() - } - } - switch subscs[x].Channel { - case announcementsChannel, - userAccessLogChannel, - platformStateChannel, - platformStatePublicMethodsStateChannel, - userLockChannel: - sub.Params["channels"] = []string{subscs[x].Channel} - case orderbookChannel: - if len(subscs[x].Pairs) != 1 { - return nil, currency.ErrCurrencyPairEmpty - } - intervalString, err := d.GetResolutionFromInterval(subscs[x].Interval) - if err != nil { - return nil, err - } - group, okay := subscs[x].Params["group"].(string) - if !okay { - sub.Params["channels"] = []string{orderbookChannel + "." + instrumentID + "." + intervalString} - break - } - depth, okay := subscs[x].Params["depth"].(string) - if !okay { - sub.Params["channels"] = []string{orderbookChannel + "." + instrumentID + "." + intervalString} - break - } - sub.Params["channels"] = []string{orderbookChannel + "." + instrumentID + "." + group + "." + depth + "." + intervalString} - case chartTradesChannel: - if len(subscs[x].Pairs) != 1 { - return nil, currency.ErrCurrencyPairEmpty - } - resolution, okay := subscs[x].Params["resolution"].(string) - if !okay { - resolution = "1D" - } - sub.Params["channels"] = []string{chartTradesChannel + "." + d.formatFuturesTradablePair(subscs[x].Pairs[0]) + "." + resolution} - case priceIndexChannel, - priceRankingChannel, - priceStatisticsChannel, - volatilityIndexChannel, - markPriceOptionsChannel, - estimatedExpirationPriceChannel: - indexName, okay := subscs[x].Params["index_name"].(string) - if !okay { - return nil, errUnsupportedIndexName - } - sub.Params["channels"] = []string{subscs[x].Channel + "." + indexName} - case instrumentStateChannel: - if len(subscs[x].Pairs) != 1 { - return nil, currency.ErrCurrencyPairEmpty - } - kind := d.GetAssetKind(subscs[x].Asset) - currencyCode := getValidatedCurrencyCode(subscs[x].Pairs[0]) - sub.Params["channels"] = []string{"instrument.state." + kind + "." + currencyCode} - case rawUsersOrdersKindCurrencyChannel: - if len(subscs[x].Pairs) != 1 { - return nil, currency.ErrCurrencyPairEmpty - } - kind := d.GetAssetKind(subscs[x].Asset) - currencyCode := getValidatedCurrencyCode(subscs[x].Pairs[0]) - sub.Params["channels"] = []string{"user.orders." + kind + "." + currencyCode + ".raw"} - case quoteChannel, - incrementalTickerChannel: - if len(subscs[x].Pairs) != 1 { - return nil, currency.ErrCurrencyPairEmpty - } - sub.Params["channels"] = []string{subscs[x].Channel + "." + instrumentID} - case rawUserOrdersChannel: - if len(subscs[x].Pairs) != 1 { - return nil, currency.ErrCurrencyPairEmpty - } - sub.Params["channels"] = []string{"user.orders." + instrumentID + ".raw"} - case requestForQuoteChannel, - userMMPTriggerChannel, - userPortfolioChannel: - if len(subscs[x].Pairs) != 1 { - return nil, currency.ErrCurrencyPairEmpty - } - currencyCode := getValidatedCurrencyCode(subscs[x].Pairs[0]) - sub.Params["channels"] = []string{subscs[x].Channel + "." + currencyCode} - case tradesChannel, - userChangesInstrumentsChannel, - userOrdersWithIntervalChannel, - tickerChannel, - perpetualChannel, - userTradesChannelByInstrument: - if len(subscs[x].Pairs) != 1 { - return nil, currency.ErrCurrencyPairEmpty - } - if subscs[x].Interval.Duration() == 0 { - sub.Params["channels"] = []string{subscs[x].Channel + instrumentID} - continue - } - intervalString, err := d.GetResolutionFromInterval(subscs[x].Interval) - if err != nil { - return nil, err - } - sub.Params["channels"] = []string{subscs[x].Channel + instrumentID + "." + intervalString} - case userChangesCurrencyChannel, - tradesWithKindChannel, - rawUsersOrdersWithKindCurrencyAndIntervalChannel, - userTradesByKindCurrencyAndIntervalChannel: - kind := d.GetAssetKind(subscs[x].Asset) - if len(subscs[x].Pairs) != 1 { - return nil, currency.ErrCurrencyPairEmpty - } - currencyCode := getValidatedCurrencyCode(subscs[x].Pairs[0]) - if subscs[x].Interval.Duration() == 0 { - sub.Params["channels"] = []string{subscs[x].Channel + "." + kind + "." + currencyCode} - continue - } - intervalString, err := d.GetResolutionFromInterval(subscs[x].Interval) - if err != nil { - return nil, err - } - sub.Params["channels"] = []string{subscs[x].Channel + "." + kind + "." + currencyCode + "." + intervalString} - default: - return nil, errUnsupportedChannel - } - subscriptionPayloads[x] = sub - } - return filterSubscriptionPayloads(subscriptionPayloads), nil +// GetSubscriptionTemplate returns a subscription channel template +func (d *Deribit) GetSubscriptionTemplate(_ *subscription.Subscription) (*template.Template, error) { + return template.New("master.tmpl").Funcs(template.FuncMap{ + "channelName": channelName, + "interval": channelInterval, + "isSymbolChannel": isSymbolChannel, + }). + Parse(subTplText) } // Subscribe sends a websocket message to receive data from the channel -func (d *Deribit) Subscribe(channelsToSubscribe subscription.List) error { - return d.handleSubscription("subscribe", channelsToSubscribe) +func (d *Deribit) Subscribe(subs subscription.List) error { + errs := d.handleSubscription("public/subscribe", subs.Public()) + return common.AppendError(errs, + d.handleSubscription("private/subscribe", subs.Private()), + ) } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (d *Deribit) Unsubscribe(channelsToUnsubscribe subscription.List) error { - return d.handleSubscription("unsubscribe", channelsToUnsubscribe) +func (d *Deribit) Unsubscribe(subs subscription.List) error { + errs := d.handleSubscription("public/unsubscribe", subs.Public()) + return common.AppendError(errs, + d.handleSubscription("private/unsubscribe", subs.Private()), + ) } -func filterSubscriptionPayloads(subscription []WsSubscriptionInput) []WsSubscriptionInput { - newSubscriptionsMap := map[string]bool{} - newSubscs := make([]WsSubscriptionInput, 0, len(subscription)) - for x := range subscription { - if len(subscription[x].Params["channels"]) == 0 { - continue - } - if !newSubscriptionsMap[subscription[x].Params["channels"][0]] { - newSubscriptionsMap[subscription[x].Params["channels"][0]] = true - newSubscs = append(newSubscs, subscription[x]) - } +func (d *Deribit) handleSubscription(method string, subs subscription.List) error { + var err error + subs, err = subs.ExpandTemplates(d) + if err != nil || len(subs) == 0 { + return err } - return newSubscs -} - -func (d *Deribit) handleSubscription(operation string, channels subscription.List) error { - payloads, err := d.generatePayloadFromSubscriptionInfos(operation, channels) + r := WsSubscriptionInput{ + JSONRPCVersion: rpcVersion, + ID: d.Websocket.Conn.GenerateMessageID(false), + Method: method, + Params: map[string][]string{ + "channels": subs.QualifiedChannels(), + }, + } + data, err := d.Websocket.Conn.SendMessageReturnResponse(context.TODO(), request.Unset, r.ID, r) if err != nil { return err } - for x := range payloads { - data, err := d.Websocket.Conn.SendMessageReturnResponse(context.TODO(), request.Unset, payloads[x].ID, payloads[x]) - if err != nil { - return err - } - var response wsSubscriptionResponse - err = json.Unmarshal(data, &response) - if err != nil { - return fmt.Errorf("%v %v", d.Name, err) - } - if payloads[x].ID == response.ID && len(response.Result) == 0 { - log.Errorf(log.ExchangeSys, "subscription to channel %s was not successful", payloads[x].Params["channels"][0]) + var response wsSubscriptionResponse + err = json.Unmarshal(data, &response) + if err != nil { + return fmt.Errorf("%v %v", d.Name, err) + } + subAck := map[string]bool{} + for _, c := range response.Result { + subAck[c] = true + } + if len(subAck) != len(subs) { + err = common.ErrUnknownError + } + for _, s := range subs { + if _, ok := subAck[s.QualifiedChannel]; ok { + err = common.AppendError(err, d.Websocket.AddSuccessfulSubscriptions(d.Websocket.Conn, s)) + } else { + err = common.AppendError(err, errors.New(s.String())) } } - return nil + return err } func getValidatedCurrencyCode(pair currency.Pair) string { @@ -1199,3 +856,61 @@ func getValidatedCurrencyCode(pair currency.Pair) string { return "any" } } + +func channelName(s *subscription.Subscription) string { + if name, ok := subscriptionNames[s.Channel]; ok { + return name + } + panic(fmt.Errorf("%w: %s", subscription.ErrNotSupported, s.Channel)) +} + +// channelInterval converts an interval to an exchange specific interval +// We convert 1s to agg2; Docs do not explain agg2 but support explained that it may vary under load but is currently 1 second +func channelInterval(s *subscription.Subscription) string { + if s.Interval != 0 { + if channelName(s) == chartTradesChannel { + if s.Interval == kline.OneDay { + return "1D" + } + m := s.Interval.Duration().Minutes() + switch m { + case 1, 3, 5, 10, 15, 30, 60, 120, 180, 360, 720: // Valid Minute intervals + return strconv.Itoa(int(m)) + } + panic(fmt.Errorf("%w: %s", kline.ErrUnsupportedInterval, s.Interval)) + } + switch s.Interval { + case kline.ThousandMilliseconds: + return "agg2" + case kline.HundredMilliseconds, kline.Raw: + return s.Interval.Short() + } + panic(fmt.Errorf("%w: %s", kline.ErrUnsupportedInterval, s.Interval)) + } + return "" +} + +func isSymbolChannel(s *subscription.Subscription) bool { + switch channelName(s) { + case orderbookChannel, chartTradesChannel, tickerChannel, tradesChannel, perpetualChannel, quoteChannel, + userChangesInstrumentsChannel, incrementalTickerChannel, userOrdersChannel, userTradesChannel: + return true + } + return false +} + +const subTplText = ` +{{- if isSymbolChannel $.S -}} + {{- range $asset, $pairs := $.AssetPairs }} + {{- range $p := $pairs }} + {{- channelName $.S -}} . {{- $p }} + {{- with $i := interval $.S -}} . {{- $i }}{{ end }} + {{- $.PairSeparator }} + {{- end }} + {{- $.AssetSeparator }} + {{- end }} +{{- else }} + {{- channelName $.S -}} + {{- with $i := interval $.S -}} . {{- $i }}{{ end }} +{{- end }} +` diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index e7e47507418..53cbbea426e 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -127,6 +127,7 @@ func (d *Deribit) SetDefaults() { GlobalResultLimit: 500, }, }, + Subscriptions: defaultSubscriptions.Clone(), } d.Requester, err = request.New(d.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), From a427d9a4904be1239b6f67e03e22bf546e38232c Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Fri, 30 Aug 2024 10:44:43 +0700 Subject: [PATCH 6/6] Deribit: Fix race on Setup with optionsRegex Calling Setup twice would race on the assignment to this package var. There was an option to just move the assignment to the package var declaration, but this change improves the performance and allocations: ``` BenchmarkOptionPairToString-8 1000000 1239 ns/op 485 B/op 10 allocs/op BenchmarkOptionPairToString2-8 3473804 656.2 ns/op 348 B/op 7 allocs/op ``` I've also removed the t.Run because even success the -v output from tests would be very noisy, and I don't think we were getting any benefit from it at all: ``` === RUN TestOptionPairToString === PAUSE TestOptionPairToString === CONT TestOptionPairToString === RUN TestOptionPairToString/BTC-30MAY24-61000-C === PAUSE TestOptionPairToString/BTC-30MAY24-61000-C === RUN TestOptionPairToString/ETH-1JUN24-3200-P === PAUSE TestOptionPairToString/ETH-1JUN24-3200-P === RUN TestOptionPairToString/SOL_USDC-31MAY24-162-P === PAUSE TestOptionPairToString/SOL_USDC-31MAY24-162-P === RUN TestOptionPairToString/MATIC_USDC-6APR24-0d98-P === PAUSE TestOptionPairToString/MATIC_USDC-6APR24-0d98-P === CONT TestOptionPairToString/BTC-30MAY24-61000-C === CONT TestOptionPairToString/SOL_USDC-31MAY24-162-P === CONT TestOptionPairToString/ETH-1JUN24-3200-P === CONT TestOptionPairToString/MATIC_USDC-6APR24-0d98-P --- PASS: TestOptionPairToString (0.00s) --- PASS: TestOptionPairToString/BTC-30MAY24-61000-C (0.00s) --- PASS: TestOptionPairToString/ETH-1JUN24-3200-P (0.00s) --- PASS: TestOptionPairToString/SOL_USDC-31MAY24-162-P (0.00s) --- PASS: TestOptionPairToString/MATIC_USDC-6APR24-0d98-P (0.00s) ``` ( And that got worse with me adding more tests ) --- exchanges/deribit/deribit.go | 33 ++++++++++------------------ exchanges/deribit/deribit_test.go | 19 +++++++--------- exchanges/deribit/deribit_wrapper.go | 4 ---- 3 files changed, 20 insertions(+), 36 deletions(-) diff --git a/exchanges/deribit/deribit.go b/exchanges/deribit/deribit.go index f4787df6be4..c13c24fcef1 100644 --- a/exchanges/deribit/deribit.go +++ b/exchanges/deribit/deribit.go @@ -7,7 +7,6 @@ import ( "fmt" "net/http" "net/url" - "regexp" "strconv" "strings" "time" @@ -28,12 +27,6 @@ type Deribit struct { exchange.Base } -var ( - // optionRegex compiles optionDecimalRegex at startup and is used to help set - // option currency lower-case d eg MATIC-USDC-3JUN24-0d64-P - optionRegex *regexp.Regexp -) - const ( deribitAPIVersion = "/api/v2" tradeBaseURL = "https://www.deribit.com/" @@ -43,8 +36,7 @@ const ( tradeFuturesCombo = "futures-spreads/" tradeOptionsCombo = "combos/" - perpString = "PERPETUAL" - optionDecimalRegex = `\d+(D)\d+` + perpString = "PERPETUAL" // Public endpoints // Market Data @@ -2837,20 +2829,19 @@ func (d *Deribit) formatFuturesTradablePair(pair currency.Pair) string { return instrumentID } -// optionPairToString to format and return an Options currency pairs with the following format: MATIC_USDC-6APR24-0d98-P -// it has both uppercase or lowercase characters, which we can not achieve with the Upper=true or Upper=false +// optionPairToString formats an options pair as: SYMBOL-EXPIRE-STRIKE-TYPE +// SYMBOL may be a currency (BTC) or a pair (XRP_USDC) +// EXPIRE is DDMMMYY +// STRIKE may include a d for decimal point in linear options +// TYPE is Call or Put func (d *Deribit) optionPairToString(pair currency.Pair) string { - subCodes := strings.Split(pair.Quote.String(), currency.DashDelimiter) initialDelimiter := currency.DashDelimiter - if subCodes[0] == "USDC" { + q := pair.Quote.String() + if strings.HasPrefix(q, "USDC") && len(q) > 11 { // Linear option initialDelimiter = currency.UnderscoreDelimiter + // Replace a capital D with d for decimal place in Strike price + // Char 11 is either the hyphen before Strike price or first digit + q = q[:11] + strings.Replace(q[11:], "D", "d", -1) } - for i := range subCodes { - if match := optionRegex.MatchString(subCodes[i]); match { - subCodes[i] = strings.ToLower(subCodes[i]) - break - } - } - - return pair.Base.String() + initialDelimiter + strings.Join(subCodes, currency.DashDelimiter) + return pair.Base.String() + initialDelimiter + q } diff --git a/exchanges/deribit/deribit_test.go b/exchanges/deribit/deribit_test.go index 693ae836e0a..51a505b337f 100644 --- a/exchanges/deribit/deribit_test.go +++ b/exchanges/deribit/deribit_test.go @@ -3310,9 +3310,9 @@ func TestGenerateSubscriptions(t *testing.T) { func TestChannelInterval(t *testing.T) { t.Parallel() - for _, i := range []int{1, 3, 5, 10, 15, 30, 60, 120, 180, 360, 720} { - a := channelInterval(&subscription.Subscription{Channel: subscription.CandlesChannel, Interval: kline.Interval(i * int(time.Minute))}) - assert.Equal(t, strconv.Itoa(i), a) + for _, i := range []int64{1, 3, 5, 10, 15, 30, 60, 120, 180, 360, 720} { + a := channelInterval(&subscription.Subscription{Channel: subscription.CandlesChannel, Interval: kline.Interval(i * int64(time.Minute))}) + assert.Equal(t, strconv.Itoa(int(i)), a) } a := channelInterval(&subscription.Subscription{Channel: subscription.CandlesChannel, Interval: kline.OneDay}) @@ -3754,18 +3754,15 @@ func TestFormatFuturesTradablePair(t *testing.T) { func TestOptionPairToString(t *testing.T) { t.Parallel() - optionsList := map[currency.Pair]string{ + for pair, exp := range map[currency.Pair]string{ {Delimiter: currency.DashDelimiter, Base: currency.BTC, Quote: currency.NewCode("30MAY24-61000-C")}: "BTC-30MAY24-61000-C", {Delimiter: currency.DashDelimiter, Base: currency.ETH, Quote: currency.NewCode("1JUN24-3200-P")}: "ETH-1JUN24-3200-P", {Delimiter: currency.DashDelimiter, Base: currency.SOL, Quote: currency.NewCode("USDC-31MAY24-162-P")}: "SOL_USDC-31MAY24-162-P", {Delimiter: currency.DashDelimiter, Base: currency.MATIC, Quote: currency.NewCode("USDC-6APR24-0d98-P")}: "MATIC_USDC-6APR24-0d98-P", - } - for pair, instrumentID := range optionsList { - t.Run(instrumentID, func(t *testing.T) { - t.Parallel() - instrument := d.optionPairToString(pair) - require.Equal(t, instrumentID, instrument) - }) + {Delimiter: currency.DashDelimiter, Base: currency.MATIC, Quote: currency.NewCode("USDC-8JUN24-0D99-P")}: "MATIC_USDC-8JUN24-0d99-P", + {Delimiter: currency.DashDelimiter, Base: currency.MATIC, Quote: currency.NewCode("USDC-6DEC29-0D87-C")}: "MATIC_USDC-6DEC29-0d87-C", + } { + assert.Equal(t, exp, d.optionPairToString(pair), "optionPairToString should return correctly") } } diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index 53cbbea426e..c3bce491083 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "regexp" "sort" "strconv" "strings" @@ -188,9 +187,6 @@ func (d *Deribit) Setup(exch *config.Exchange) error { return err } - // setup option decimal regex at startup to make constant checks more efficient - optionRegex = regexp.MustCompile(optionDecimalRegex) - return d.Websocket.SetupNewConnection(&stream.ConnectionSetup{ URL: d.Websocket.GetWebsocketURL(), ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,