From a2a7fed273686ec660a4d835bb2222451f8a7883 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Mon, 11 Nov 2024 04:19:25 +0100 Subject: [PATCH] Huobi: Add subscription configuration (#1604) * Huobi: Update test config format * Huobi: Add subscription configuration * Huobi: Add subscription documentation * Huobi: Clarify OB sub Levels usage * Huobi: Enable websocket for tests * Subscriptions: Rename ErrPrivateChannelName Rename ErrPrivateChannelName to ErrUseConstChannelName --- .../exchanges_templates/huobi.tmpl | 20 +- exchanges/huobi/README.md | 20 +- exchanges/huobi/huobi_test.go | 64 +++++++ exchanges/huobi/huobi_websocket.go | 175 ++++++++++-------- exchanges/huobi/huobi_wrapper.go | 3 +- exchanges/kraken/kraken_test.go | 4 +- exchanges/kraken/kraken_websocket.go | 2 +- exchanges/subscription/subscription.go | 3 +- testdata/configtest.json | 16 +- 9 files changed, 209 insertions(+), 98 deletions(-) diff --git a/cmd/documentation/exchanges_templates/huobi.tmpl b/cmd/documentation/exchanges_templates/huobi.tmpl index 844129ec014..d4966962f6a 100644 --- a/cmd/documentation/exchanges_templates/huobi.tmpl +++ b/cmd/documentation/exchanges_templates/huobi.tmpl @@ -92,12 +92,24 @@ 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 subscriptions are for spot only. + +Default Public Subscriptions: +- Ticker +- Candles ( Interval: 1min ) +- Orderbook ( Level: 0 - No aggregation ) + - Configure Level: 1-5 for depth aggregation, for example: +```json + {"enabled": true, "channel": "orderbook", "asset": "spot", "levels": 1} ``` +- Trades + +Default Authenticated Subscriptions: +- Account Trades +- Account Orders +- Account Updates ### Please click GoDocs chevron above to view current GoDoc information for this package {{template "contributions"}} diff --git a/exchanges/huobi/README.md b/exchanges/huobi/README.md index d9286604326..7c3a465596b 100644 --- a/exchanges/huobi/README.md +++ b/exchanges/huobi/README.md @@ -110,12 +110,24 @@ 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 subscriptions are for spot only. + +Default Public Subscriptions: +- Ticker +- Candles ( Interval: 1min ) +- Orderbook ( Level: 0 - No aggregation ) + - Configure Level: 1-5 for depth aggregation, for example: +```json + {"enabled": true, "channel": "orderbook", "asset": "spot", "levels": 1} ``` +- Trades + +Default Authenticated Subscriptions: +- Account Trades +- Account Orders +- Account Updates ### Please click GoDocs chevron above to view current GoDoc information for this package diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 09fec32114b..bb0c17da0c0 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -25,8 +25,10 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" 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" ) @@ -2927,3 +2929,65 @@ func TestGetCurrencyTradeURL(t *testing.T) { } } } + +func TestGenerateSubscriptions(t *testing.T) { + t.Parallel() + + h := new(HUOBI) + require.NoError(t, testexch.Setup(h), "Test instance Setup must not error") + subs, err := h.generateSubscriptions() + require.NoError(t, err, "generateSubscriptions must not error") + exp := subscription.List{} + for _, s := range h.Features.Subscriptions { + if s.Authenticated && !h.Websocket.CanUseAuthenticatedEndpoints() { + continue + } + for _, a := range h.GetAssetTypes(true) { + if s.Asset != asset.All && s.Asset != a { + continue + } + pairs, err := h.GetEnabledPairs(a) + require.NoErrorf(t, err, "GetEnabledPairs %s must not error", a) + pairs = common.SortStrings(pairs).Format(currency.PairFormat{Uppercase: false, Delimiter: ""}) + s := s.Clone() //nolint:govet // Intentional lexical scope shadow + s.Asset = a + for i, p := range pairs { + s := s.Clone() //nolint:govet // Intentional lexical scope shadow + s.QualifiedChannel = channelName(s, p) + switch s.Channel { + case subscription.OrderbookChannel: + s.QualifiedChannel += ".step0" + case subscription.CandlesChannel: + s.QualifiedChannel += ".1min" + } + s.Pairs = pairs[i : i+1] + exp = append(exp, s) + } + } + } + testsubs.EqualLists(t, exp, subs) +} + +// TestSubscribe exercises live public subscriptions +func TestSubscribe(t *testing.T) { + t.Parallel() + h := new(HUOBI) + require.NoError(t, testexch.Setup(h), "Test instance Setup must not error") + subs, err := h.Features.Subscriptions.ExpandTemplates(h) + require.NoError(t, err, "ExpandTemplates must not error") + testexch.SetupWs(t, h) + err = h.Subscribe(subs) + require.NoError(t, err, "Subscribe must not error") + got := h.Websocket.GetSubscriptions() + require.Equal(t, 4, len(got), "Must get correct number of subscriptions") + for _, s := range got { + assert.Equal(t, subscription.SubscribedState, s.State()) + } +} + +func TestChannelName(t *testing.T) { + p := currency.NewPair(currency.BTC, currency.USD) + assert.Equal(t, "market.BTCUSD.kline", channelName(&subscription.Subscription{Channel: subscription.CandlesChannel}, p)) + assert.Panics(t, func() { channelName(&subscription.Subscription{Channel: wsOrderbookChannel}, p) }) + assert.Panics(t, func() { channelName(&subscription.Subscription{Channel: subscription.MyAccountChannel}, p) }, "Should panic on V2 endpoints until implemented") +} diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index 999f025137c..0ff45913cce 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -9,6 +9,7 @@ import ( "net/url" "strconv" "strings" + "text/template" "time" "github.com/gorilla/websocket" @@ -17,6 +18,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -31,11 +33,13 @@ const ( baseWSURL = "wss://api.huobi.pro" futuresWSURL = "wss://api.hbdm.com/" - wsMarketURL = baseWSURL + "/ws" - wsMarketKline = "market.%s.kline.1min" - wsMarketDepth = "market.%s.depth.step0" - wsMarketTrade = "market.%s.trade.detail" - wsMarketTicker = "market.%s.detail" + wsMarketURL = baseWSURL + "/ws" + wsCandlesChannel = "market.%s.kline" + wsOrderbookChannel = "market.%s.depth" + wsTradesChannel = "market.%s.trade.detail" + wsMarketDetailChannel = "market.%s.detail" + wsMyOrdersChannel = "orders.%s" + wsMyTradesChannel = "orders.%s.update" wsAccountsOrdersEndPoint = "/ws/v1" wsAccountsList = "accounts.list" @@ -56,6 +60,28 @@ const ( loginDelay = 50 * time.Millisecond ) +var defaultSubscriptions = subscription.List{ + {Enabled: true, Asset: asset.Spot, Channel: subscription.TickerChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.CandlesChannel, Interval: kline.OneMin}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.OrderbookChannel, Levels: 0}, // Aggregation Levels; 0 is no depth aggregation + {Enabled: true, Asset: asset.Spot, Channel: subscription.AllTradesChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.MyOrdersChannel, Authenticated: true}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.MyTradesChannel, Authenticated: true}, + {Enabled: true, Channel: subscription.MyAccountChannel, Authenticated: true}, +} + +var subscriptionNames = map[string]string{ + subscription.TickerChannel: wsMarketDetailChannel, + subscription.CandlesChannel: wsCandlesChannel, + subscription.OrderbookChannel: wsOrderbookChannel, + subscription.AllTradesChannel: wsTradesChannel, + /* TODO: Pending upcoming V2 support, these are dropped from the translation table so that the sub conf will be correct and not need upgrading, but will error on usage + subscription.MyTradesChannel: wsMyOrdersChannel, + subscription.MyOrdersChannel: wsMyTradesChannel, + subscription.MyAccountChannel: wsMyAccountChannel, + */ +} + // Instantiates a communications channel between websocket connections var comms = make(chan WsMessage) @@ -514,101 +540,66 @@ func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { return h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (h *HUOBI) GenerateDefaultSubscriptions() (subscription.List, error) { - var channels = []string{wsMarketKline, - wsMarketDepth, - wsMarketTrade, - wsMarketTicker} - var subscriptions subscription.List - if h.Websocket.CanUseAuthenticatedEndpoints() { - channels = append(channels, "orders.%v", "orders.%v.update") - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: "accounts", - }) - } - enabledCurrencies, err := h.GetEnabledPairs(asset.Spot) - if err != nil { - return nil, err - } - for i := range channels { - for j := range enabledCurrencies { - enabledCurrencies[j].Delimiter = "" - channel := fmt.Sprintf(channels[i], - enabledCurrencies[j].Lower().String()) - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: channel, - Pairs: currency.Pairs{enabledCurrencies[j]}, - }) - } - } - return subscriptions, nil +// generateSubscriptions returns a list of subscriptions from the configured subscriptions feature +func (h *HUOBI) generateSubscriptions() (subscription.List, error) { + return h.Features.Subscriptions.ExpandTemplates(h) +} + +// GetSubscriptionTemplate returns a subscription channel template +func (h *HUOBI) GetSubscriptionTemplate(_ *subscription.Subscription) (*template.Template, error) { + return template.New("master.tmpl").Funcs(template.FuncMap{ + "channelName": channelName, + "interval": h.FormatExchangeKlineInterval, + }).Parse(subTplText) } // Subscribe sends a websocket message to receive data from the channel -func (h *HUOBI) Subscribe(channelsToSubscribe subscription.List) error { +func (h *HUOBI) Subscribe(subs subscription.List) error { + ctx := context.Background() + var errs error var creds *account.Credentials - if h.Websocket.CanUseAuthenticatedEndpoints() { - var err error - creds, err = h.GetCredentials(context.TODO()) - if err != nil { - return err + if len(subs.Private()) > 0 { + if creds, errs = h.GetCredentials(ctx); errs != nil { + return errs } } - var errs error - for i := range channelsToSubscribe { + for _, s := range subs { var err error - if (strings.Contains(channelsToSubscribe[i].Channel, "orders.") || - strings.Contains(channelsToSubscribe[i].Channel, "accounts")) && creds != nil { - err = h.wsAuthenticatedSubscribe(creds, - "sub", - wsAccountsOrdersEndPoint+channelsToSubscribe[i].Channel, - channelsToSubscribe[i].Channel) + if s.Authenticated { + if err = h.wsAuthenticatedSubscribe(creds, "sub", wsAccountsOrdersEndPoint+"/"+s.QualifiedChannel, s.QualifiedChannel); err == nil { + err = h.Websocket.AddSuccessfulSubscriptions(h.Websocket.Conn, s) + } } else { - err = h.Websocket.Conn.SendJSONMessage(context.TODO(), request.Unset, WsRequest{ - Subscribe: channelsToSubscribe[i].Channel, - }) - } - if err == nil { - err = h.Websocket.AddSuccessfulSubscriptions(h.Websocket.Conn, channelsToSubscribe[i]) - } - if err != nil { - errs = common.AppendError(errs, err) + if err = h.Websocket.Conn.SendJSONMessage(ctx, request.Unset, WsRequest{Subscribe: s.QualifiedChannel}); err == nil { + err = h.Websocket.AddSuccessfulSubscriptions(h.Websocket.AuthConn, s) + } } + errs = common.AppendError(errs, err) } return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (h *HUOBI) Unsubscribe(channelsToUnsubscribe subscription.List) error { +func (h *HUOBI) Unsubscribe(subs subscription.List) error { + ctx := context.Background() + var errs error var creds *account.Credentials - if h.Websocket.CanUseAuthenticatedEndpoints() { - var err error - creds, err = h.GetCredentials(context.TODO()) - if err != nil { - return err + if len(subs.Private()) > 0 { + if creds, errs = h.GetCredentials(ctx); errs != nil { + return errs } } - var errs error - for i := range channelsToUnsubscribe { + for _, s := range subs { var err error - if (strings.Contains(channelsToUnsubscribe[i].Channel, "orders.") || - strings.Contains(channelsToUnsubscribe[i].Channel, "accounts")) && creds != nil { - err = h.wsAuthenticatedSubscribe(creds, - "unsub", - wsAccountsOrdersEndPoint+channelsToUnsubscribe[i].Channel, - channelsToUnsubscribe[i].Channel) + if s.Authenticated { + err = h.wsAuthenticatedSubscribe(creds, "unsub", wsAccountsOrdersEndPoint+"/"+s.QualifiedChannel, s.QualifiedChannel) } else { - err = h.Websocket.Conn.SendJSONMessage(context.TODO(), request.Unset, WsRequest{ - Unsubscribe: channelsToUnsubscribe[i].Channel, - }) + err = h.Websocket.Conn.SendJSONMessage(ctx, request.Unset, WsRequest{Unsubscribe: s.QualifiedChannel}) } if err == nil { - err = h.Websocket.RemoveSubscriptions(h.Websocket.Conn, channelsToUnsubscribe[i]) - } - if err != nil { - errs = common.AppendError(errs, err) + err = h.Websocket.RemoveSubscriptions(h.Websocket.Conn, s) } + errs = common.AppendError(errs, err) } return errs } @@ -810,3 +801,31 @@ func (h *HUOBI) wsGetOrderDetails(ctx context.Context, orderID string) (*WsAuthe } return &response, nil } + +// channelName converts global channel Names used in config of channel input into exchange channel names +// returns the name unchanged if no match is found +func channelName(s *subscription.Subscription, p currency.Pair) string { + if n, ok := subscriptionNames[s.Channel]; ok { + return fmt.Sprintf(n, p) + } + if s.Authenticated { + panic(fmt.Errorf("%w: Private endpoints not currently supported", common.ErrNotYetImplemented)) + } + panic(subscription.ErrUseConstChannelName) +} + +const subTplText = ` +{{- if $.S.Asset }} + {{ range $asset, $pairs := $.AssetPairs }} + {{- range $p := $pairs }} + {{- channelName $.S $p -}} + {{- if eq $.S.Channel "candles" -}} . {{- interval $.S.Interval }}{{ end }} + {{- if eq $.S.Channel "orderbook" -}} .step {{- $.S.Levels }}{{ end }} + {{ $.PairSeparator }} + {{- end }} + {{ $.AssetSeparator }} + {{- end }} +{{- else -}} + {{ channelName $.S nil }} +{{- end }} +` diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index f0deb80ad87..9a62d91a0e2 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -162,6 +162,7 @@ func (h *HUOBI) SetDefaults() { GlobalResultLimit: 2000, }, }, + Subscriptions: defaultSubscriptions.Clone(), } h.Requester, err = request.New(h.Name, @@ -213,7 +214,7 @@ func (h *HUOBI) Setup(exch *config.Exchange) error { Connector: h.WsConnect, Subscriber: h.Subscribe, Unsubscriber: h.Unsubscribe, - GenerateSubscriptions: h.GenerateDefaultSubscriptions, + GenerateSubscriptions: h.generateSubscriptions, Features: &h.Features.Supports.WebsocketCapabilities, }) if err != nil { diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 7f8f8e05628..2babae23b81 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -1001,7 +1001,7 @@ func TestWsSubscribe(t *testing.T) { Channel: c, Pairs: currency.Pairs{spotTestPair}, }}) - assert.ErrorIs(t, err, subscription.ErrPrivateChannelName, "Must error when trying to use a private channel name") + assert.ErrorIs(t, err, subscription.ErrUseConstChannelName, "Must error when trying to use a private channel name") assert.ErrorContains(t, err, c+" => subscription.CandlesChannel", "Must error when trying to use a private channel name") } } @@ -1703,6 +1703,6 @@ func TestEnforceStandardChannelNames(t *testing.T) { } for _, n := range []string{krakenWsOrderbook, krakenWsOHLC, krakenWsTrade, krakenWsOwnTrades, krakenWsOpenOrders, krakenWsOrderbook + "-5"} { err := enforceStandardChannelNames(&subscription.Subscription{Channel: n}) - assert.ErrorIsf(t, err, subscription.ErrPrivateChannelName, "Private channel names should not be allowed for %s", n) + assert.ErrorIsf(t, err, subscription.ErrUseConstChannelName, "Private channel names should not be allowed for %s", n) } } diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index b572af6a335..22088590463 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -1237,7 +1237,7 @@ func channelName(s *subscription.Subscription) string { func enforceStandardChannelNames(s *subscription.Subscription) error { name := strings.Split(s.Channel, "-") // Protect against attempted usage of book-N as a channel name if n, ok := reverseChannelNames[name[0]]; ok && n != s.Channel { - return fmt.Errorf("%w: %s => subscription.%s%sChannel", subscription.ErrPrivateChannelName, s.Channel, bytes.ToUpper([]byte{n[0]}), n[1:]) + return fmt.Errorf("%w: %s => subscription.%s%sChannel", subscription.ErrUseConstChannelName, s.Channel, bytes.ToUpper([]byte{n[0]}), n[1:]) } return nil } diff --git a/exchanges/subscription/subscription.go b/exchanges/subscription/subscription.go index a2a47055542..db76b370521 100644 --- a/exchanges/subscription/subscription.go +++ b/exchanges/subscription/subscription.go @@ -32,6 +32,7 @@ const ( MyTradesChannel = "myTrades" MyOrdersChannel = "myOrders" MyWalletChannel = "myWallet" + MyAccountChannel = "myAccount" ) // Public errors @@ -42,7 +43,7 @@ var ( ErrInStateAlready = errors.New("subscription already in state") ErrInvalidState = errors.New("invalid subscription state") ErrDuplicate = errors.New("duplicate subscription") - ErrPrivateChannelName = errors.New("must use standard channel name constants") + ErrUseConstChannelName = errors.New("must use standard channel name constants") ) // State tracks the status of a subscription channel diff --git a/testdata/configtest.json b/testdata/configtest.json index d9c80bf88ae..8846ee0e4a9 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -1794,11 +1794,6 @@ "delimiter": "-" }, "useGlobalFormat": true, - "assetTypes": [ - "spot", - "coinmarginedfutures", - "futures" - ], "pairs": { "coinmarginedfutures": { "assetEnabled": true, @@ -1826,6 +1821,7 @@ } }, "spot": { + "assetEnabled": true, "enabled": "BTC-USDT", "available": "PROPY-ETH,IOTA-BTC,UGAS-ETH,PAI-USDT,BSV-HUSD,MTX-ETH,BCH-BTC,LTC-HT,SOC-USDT,WXT-BTC,SALT-BTC,RCN-ETH,PNT-ETH,TT-USDT,AIDOC-ETH,BIX-BTC,OCN-USDT,QTUM-ETH,KCASH-ETH,SNT-USDT,LUN-BTC,QASH-BTC,ITC-BTC,NAS-BTC,XMR-BTC,TNT-ETH,UC-ETH,FAIR-BTC,PC-ETH,YEE-BTC,PAY-ETH,XMX-BTC,CRE-USDT,BAT-ETH,BHT-USDT,CKB-HT,LAMB-HT,AE-USDT,QUN-ETH,LYM-BTC,BCH-HT,BHT-BTC,RUFF-ETH,CNN-BTC,FOR-USDT,GTC-ETH,TRX-ETH,ELA-USDT,ACT-ETH,SMT-ETH,BUT-ETH,BCH-USDT,ICX-BTC,MEET-BTC,NCC-BTC,APPC-BTC,GVE-ETH,TNB-BTC,STEEM-ETH,18C-ETH,LBA-BTC,EKO-BTC,REQ-BTC,SOC-BTC,BOX-ETH,ELF-BTC,ZRX-ETH,LET-USDT,HT-BTC,TUSD-HUSD,EGCC-BTC,WTC-BTC,ATP-USDT,DOCK-USDT,PAI-BTC,ONT-ETH,IRIS-BTC,BTT-ETH,SC-BTC,XZC-BTC,LBA-USDT,HT-USDT,VET-ETH,KMD-ETH,SHE-ETH,PORTAL-BTC,ONE-BTC,BIX-USDT,RCCC-BTC,SKM-USDT,XTZ-ETH,SWFTC-BTC,RSR-BTC,LINK-ETH,DATX-BTC,HPT-HT,GET-ETH,BLZ-ETH,CTXC-USDT,CNNS-USDT,PVT-HT,ITC-USDT,LTC-BTC,NCASH-BTC,HOT-ETH,ADA-USDT,ADX-BTC,NODE-USDT,TRIO-BTC,GXC-ETH,SNT-BTC,FOR-BTC,DBC-BTC,UUU-USDT,CVCOIN-ETH,RSR-USDT,CRO-USDT,OCN-BTC,NEW-USDT,EGT-USDT,MANA-BTC,CMT-USDT,WXT-HT,XRP-BTC,MT-ETH,PAX-HUSD,LSK-ETH,IOTA-USDT,SRN-ETH,ZIL-ETH,ELF-USDT,LXT-ETH,LAMB-BTC,CRE-HT,CKB-BTC,XVG-BTC,BSV-BTC,BFT-BTC,WPR-ETH,HT-HUSD,POWR-BTC,MANA-ETH,ENG-ETH,ZJLT-ETH,SNC-ETH,ATOM-ETH,WICC-USDT,KAN-ETH,DGD-BTC,VSYS-HT,BCD-BTC,BTM-ETH,DOGE-USDT,MEX-BTC,BTG-BTC,DAC-ETH,DAT-BTC,GRS-ETH,ADX-ETH,EM-HT,GXC-USDT,CVC-BTC,OMG-ETH,SSP-ETH,OGO-HT,CMT-ETH,POLY-ETH,XZC-USDT,THETA-USDT,XEM-USDT,LOL-USDT,BCH-HUSD,GSC-BTC,DOGE-ETH,MDS-BTC,BTS-ETH,CTXC-BTC,MCO-BTC,BCX-BTC,ZLA-ETH,EKT-USDT,MAN-BTC,BLZ-BTC,ATOM-USDT,LOL-BTC,HPT-USDT,EM-BTC,EOS-USDT,WAN-BTC,GNT-BTC,CRO-BTC,MANA-USDT,SEELE-USDT,FSN-BTC,VIDY-HT,USDC-HUSD,LTC-HUSD,XRP-USDT,VSYS-BTC,STORJ-BTC,LOOM-ETH,SKM-BTC,LINK-USDT,TT-HT,QSP-ETH,ETN-BTC,FSN-HT,NODE-BTC,HC-USDT,PHX-BTC,XLM-BTC,RCCC-ETH,LTC-USDT,UUU-BTC,SEELE-ETH,PVT-BTC,HC-ETH,REN-ETH,KAN-USDT,EOS-ETH,BSV-USDT,BTS-USDT,KMD-BTC,OGO-USDT,THETA-ETH,MUSK-BTC,CNNS-HT,ETC-BTC,COVA-BTC,BTT-TRX,XMR-USDT,MTN-ETH,QUN-BTC,NAS-USDT,ELA-ETH,HIT-ETH,BTT-USDT,EKT-ETH,TOS-BTC,GAS-ETH,DCR-USDT,ONT-BTC,NEW-HT,NEXO-BTC,ETH-USDT,WXT-USDT,FOR-HT,ADA-BTC,EVX-ETH,VET-BTC,ZEC-USDT,NANO-ETH,IOST-HT,BCV-ETH,REN-USDT,NULS-ETH,ACT-USDT,LET-ETH,BTM-USDT,MEET-ETH,AKRO-HT,ARDR-BTC,DCR-ETH,NANO-USDT,BTC-HUSD,ALGO-BTC,IIC-ETH,BHD-BTC,KNC-ETH,ATP-BTC,ZRX-BTC,ABT-BTC,18C-BTC,XMR-ETH,WAXP-BTC,CVNT-BTC,MX-USDT,OST-ETH,NKN-BTC,TOPC-BTC,GNX-BTC,FTT-USDT,ONE-HT,DGB-ETH,NULS-USDT,DASH-BTC,UIP-BTC,KCASH-HT,WICC-ETH,EKO-ETH,EGT-HT,IRIS-USDT,STK-ETH,MXC-BTC,NAS-ETH,OMG-USDT,SMT-BTC,BUT-BTC,HIT-USDT,BAT-BTC,IRIS-ETH,NKN-HT,PC-BTC,TOP-USDT,GTC-BTC,LSK-BTC,ITC-ETH,DTA-BTC,HOT-BTC,BTT-BTC,FAIR-ETH,DOCK-ETH,QTUM-BTC,ZEN-BTC,ZIL-BTC,RCN-BTC,FTI-BTC,BHD-USDT,VIDY-USDT,LUN-ETH,DBC-ETH,TOPC-ETH,IIC-BTC,STEEM-USDT,IOTA-ETH,KCASH-BTC,RUFF-BTC,APPC-ETH,MT-BTC,SOC-ETH,GT-HT,PROPY-BTC,AIDOC-BTC,ACT-BTC,LYM-ETH,CHAT-BTC,SWFTC-ETH,ETH-BTC,UIP-USDT,UGAS-BTC,XRP-HUSD,ALGO-USDT,TNT-BTC,ONT-USDT,YEE-ETH,AKRO-BTC,TRX-USDT,OCN-ETH,SRN-BTC,DASH-USDT,XMX-ETH,NANO-BTC,QASH-ETH,EOS-HT,GT-BTC,XTZ-USDT,ARPA-USDT,SALT-ETH,BKBT-ETH,MTX-BTC,SMT-USDT,GXC-BTC,VIDY-BTC,FTT-HT,LAMB-ETH,TRX-BTC,TRIO-ETH,BFT-ETH,LINK-BTC,AE-ETH,NULS-BTC,BHD-HT,AST-ETH,NEO-USDT,EDU-BTC,CVCOIN-BTC,GVE-BTC,GET-BTC,ZRX-USDT,ELF-ETH,DATX-ETH,ADA-ETH,TOP-HT,NCASH-ETH,QTUM-USDT,ETC-HT,ZIL-USDT,TNB-ETH,BIX-ETH,SHE-BTC,PNT-BTC,BTC-USDT,PORTAL-ETH,WAVES-USDT,XZC-ETH,HT-ETH,POLY-BTC,MCO-ETH,MUSK-ETH,PAI-ETH,LXT-USDT,UTK-BTC,RTE-BTC,NCC-ETH,HB10-USDT,BOX-BTC,RDN-ETH,ARPA-BTC,LBA-ETH,CNN-ETH,AAC-ETH,XTZ-BTC,IDT-BTC,AKRO-USDT,IOST-BTC,GT-USDT,WAN-ETH,ETN-ETH,PVT-USDT,NEO-BTC,WAVES-ETH,ONE-USDT,ZEC-BTC,SKM-HT,IOST-ETH,NPXS-ETH,CVC-ETH,CMT-BTC,COVA-ETH,ARDR-ETH,RDN-BTC,DCR-BTC,REN-BTC,YCC-ETH,MX-HT,NEXO-ETH,XLM-ETH,YCC-BTC,ENG-BTC,CNNS-BTC,ZLA-BTC,QSP-BTC,MAN-ETH,UUU-ETH,ETH-HUSD,RTE-ETH,ATP-HT,BTM-BTC,DAC-BTC,TOS-ETH,LAMB-USDT,DASH-HT,NPXS-BTC,NEW-BTC,FTT-BTC,EOS-HUSD,GRS-BTC,POWR-ETH,VET-USDT,AAC-BTC,MX-BTC,MTN-BTC,XVG-ETH,GNX-ETH,SSP-BTC,WAVES-BTC,EGT-BTC,CTXC-ETH,IDT-ETH,STK-BTC,WICC-BTC,UTK-ETH,CRO-HT,LXT-BTC,GSC-ETH,OMG-BTC,XRP-HT,DGB-BTC,IOST-USDT,CVNT-ETH,GAS-BTC,HIT-BTC,CKB-USDT,ARPA-HT,RUFF-USDT,HC-BTC,WTC-ETH,MDS-USDT,ABT-ETH,ALGO-ETH,BIFI-BTC,KNC-BTC,TT-BTC,LET-BTC,NKN-USDT,PAY-BTC,DTA-USDT,AE-BTC,UC-BTC,VSYS-USDT,USDT-HUSD,EOS-BTC,STEEM-BTC,DOGE-BTC,NODE-HT,MDS-ETH,CRE-BTC,GNT-USDT,UIP-ETH,AST-BTC,XEM-BTC,ZEN-ETH,EDU-ETH,MEX-ETH,EKT-BTC,CVC-USDT,WAXP-ETH,REQ-ETH,OST-BTC,STORJ-USDT,SBTC-BTC,DGD-ETH,SC-ETH,WTC-USDT,THETA-BTC,DTA-ETH,BCV-BTC,SNC-BTC,RSR-HT,KAN-BTC,ELA-BTC,ATOM-BTC,BKBT-BTC,FSN-USDT,EM-USDT,WPR-BTC,TOP-BTC,BTS-BTC,EGCC-ETH,MTL-BTC,GNT-ETH,SEELE-BTC,EVX-BTC,FTI-ETH,BAT-USDT,MT-HT,LOL-HT,ICX-ETH,LOOM-BTC,ZJLT-BTC,XLM-USDT,OGO-BTC,DOCK-BTC,CHAT-ETH,DAT-ETH,ETC-USDT,HPT-BTC,BHT-HT" } @@ -1860,7 +1856,7 @@ }, "enabled": { "autoPairUpdates": true, - "websocketAPI": false + "websocketAPI": true } }, "bankAccounts": [ @@ -1877,7 +1873,13 @@ "iban": "", "supportedCurrencies": "" } - ] + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 + } }, { "name": "Kraken",