From 9917af96bac4711295e8f89a42561bd131533915 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Sat, 31 Aug 2024 14:50:27 +0700 Subject: [PATCH 1/2] HitBTC: Upgrade test config --- testdata/configtest.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/testdata/configtest.json b/testdata/configtest.json index 51de85a84c9..5551b4e56e9 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -1739,11 +1739,9 @@ "delimiter": "-" }, "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], "pairs": { "spot": { + "assetEnabled": true, "enabled": "BTC-USD", "available": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAXP-BTC,WAXP-ETH,WAXP-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,NAVI-BTC,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,BTS-BTC,BNK-BTC,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD,NJBC-BTC,NJBC-ETH,XRC-BTC,EOS-BCH,LTC-BCH,XRP-BCH,TRX-BCH,XLM-BCH,ETC-BCH,DASH-BCH,ZEC-BCH,BKX-USD,LAMB-BTC,NPXS-BTC,HBAR-BTC,HBAR-USD,ONE-BTC,RFR-BTC,RFR-USD,BUSD-USD,PAXG-BTC,PAXG-USD,REN-BTC,IGNIS-BTC,CEL-BTC,CEL-ETH,WIN-USD,ADK-BTC,PART-BTC,SOZ-BTC,SOZ-ETH,SOZ-USD,WAVES-USD,ADA-BCH,ONT-BCH,XMR-BCH,ATOM-BCH,LINK-BCH,OMG-BCH,WAVES-BCH,IOTX-BTC,HOT-BTC,SLV-BTC,HEDG-BTC,CHZ-BTC,CHZ-USD,COCOS-BTC,COCOS-USD,SEELE-BTC,SEELE-USD,MDA-BTC,LEO-USD,REM-BTC,REM-ETH,REM-USD,SCD-DAI,BTC-BUSD,RVN-BTC,BST-BTC,ERD-BTC,KRL-BTC,FTT-BTC,FTT-USD,RAISE-BTC,RAISE-ETH" } @@ -1795,7 +1793,13 @@ "iban": "", "supportedCurrencies": "" } - ] + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 + } }, { "name": "Huobi", From f835c800b82dfa841de21be6ae5e8c4f0d3626b9 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Sat, 31 Aug 2024 14:49:10 +0700 Subject: [PATCH 2/2] HitBTC: Add subscription configuration --- .../exchanges_templates/hitbtc.tmpl | 22 ++- exchanges/hitbtc/README.md | 22 ++- exchanges/hitbtc/hitbtc_test.go | 68 ++++++- exchanges/hitbtc/hitbtc_types.go | 17 +- exchanges/hitbtc/hitbtc_websocket.go | 184 ++++++++++-------- exchanges/hitbtc/hitbtc_wrapper.go | 11 +- 6 files changed, 212 insertions(+), 112 deletions(-) diff --git a/cmd/documentation/exchanges_templates/hitbtc.tmpl b/cmd/documentation/exchanges_templates/hitbtc.tmpl index 02d26c0647a..e8d9f488e80 100644 --- a/cmd/documentation/exchanges_templates/hitbtc.tmpl +++ b/cmd/documentation/exchanges_templates/hitbtc.tmpl @@ -93,12 +93,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 -``` +Subscriptions are for [v2 api](https://hitbtc-com.github.io/hitbtc-api/#socket-api-reference) + +All subscriptions are for spot. + +Default Public Subscriptions: +- Ticker +- Orderbook +- Candles ( Interval: 30 minutes, History: 100 ) +- All Trades ( History: 100 ) + +Default Authenticated Subscriptions: +- My Account events + +Subscriptions are subject to enabled assets and pairs. + +Configure Levels for number of history entries to return for applicable APIs. ### Please click GoDocs chevron above to view current GoDoc information for this package {{template "contributions"}} diff --git a/exchanges/hitbtc/README.md b/exchanges/hitbtc/README.md index ba363a8aeec..3c9a5fac708 100644 --- a/exchanges/hitbtc/README.md +++ b/exchanges/hitbtc/README.md @@ -111,12 +111,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 -``` +Subscriptions are for [v2 api](https://hitbtc-com.github.io/hitbtc-api/#socket-api-reference) + +All subscriptions are for spot. + +Default Public Subscriptions: +- Ticker +- Orderbook +- Candles ( Interval: 30 minutes, History: 100 ) +- All Trades ( History: 100 ) + +Default Authenticated Subscriptions: +- My Account events + +Subscriptions are subject to enabled assets and pairs. + +Configure Levels for number of history entries to return for applicable APIs. ### Please click GoDocs chevron above to view current GoDoc information for this package diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index df6f8ac4683..e3a1c544a70 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -21,7 +21,9 @@ 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" 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" ) @@ -1007,7 +1009,7 @@ func Test_FormatExchangeKlineInterval(t *testing.T) { test := testCases[x] t.Run(test.name, func(t *testing.T) { t.Parallel() - ret, err := h.FormatExchangeKlineInterval(test.interval) + ret, err := formatExchangeKlineInterval(test.interval) if err != nil { t.Fatal(err) } @@ -1090,3 +1092,67 @@ func TestGetCurrencyTradeURL(t *testing.T) { assert.NotEmpty(t, resp) } } + +func TestGenerateSubscriptions(t *testing.T) { + t.Parallel() + + h := new(HitBTC) + require.NoError(t, testexch.Setup(h), "Test instance Setup must not error") + + h.Websocket.SetCanUseAuthenticatedEndpoints(true) + require.True(t, h.Websocket.CanUseAuthenticatedEndpoints(), "CanUseAuthenticatedEndpoints must return true") + subs, err := h.generateSubscriptions() + require.NoError(t, err, "generateSubscriptions must not error") + exp := subscription.List{} + pairs, err := h.GetEnabledPairs(asset.Spot) + require.NoErrorf(t, err, "GetEnabledPairs must not error") + for _, s := range h.Features.Subscriptions { + for _, p := range pairs.Format(currency.PairFormat{Uppercase: true}) { + s = s.Clone() + s.Pairs = currency.Pairs{p} + n := subscriptionNames[s.Channel] + switch s.Channel { + case subscription.MyAccountChannel: + s.QualifiedChannel = `{"method":"` + n + `"}` + case subscription.CandlesChannel: + s.QualifiedChannel = `{"method":"` + n + `","params":{"symbol":"` + p.String() + `","period":"M30","limit":100}}` + case subscription.AllTradesChannel: + s.QualifiedChannel = `{"method":"` + n + `","params":{"symbol":"` + p.String() + `","limit":100}}` + default: + s.QualifiedChannel = `{"method":"` + n + `","params":{"symbol":"` + p.String() + `"}}` + } + exp = append(exp, s) + } + } + testsubs.EqualLists(t, exp, subs) +} + +func TestIsSymbolChannel(t *testing.T) { + t.Parallel() + assert.True(t, isSymbolChannel(&subscription.Subscription{Channel: subscription.TickerChannel})) + assert.False(t, isSymbolChannel(&subscription.Subscription{Channel: subscription.MyAccountChannel})) +} + +func TestSubToReq(t *testing.T) { + t.Parallel() + p := currency.NewPairWithDelimiter("BTC", "USD", "-") + r := subToReq(&subscription.Subscription{Channel: subscription.TickerChannel}, p) + assert.Equal(t, "Ticker", r.Method) + assert.Equal(t, "BTC-USD", (r.Params.Symbol)) + + r = subToReq(&subscription.Subscription{Channel: subscription.CandlesChannel, Levels: 4, Interval: kline.OneHour}, p) + assert.Equal(t, "Candles", r.Method) + assert.Equal(t, "H1", r.Params.Period) + assert.Equal(t, 4, r.Params.Limit) + assert.Equal(t, "BTC-USD", (r.Params.Symbol)) + + r = subToReq(&subscription.Subscription{Channel: subscription.AllTradesChannel, Levels: 150}) + assert.Equal(t, "Trades", r.Method) + assert.Equal(t, 150, r.Params.Limit) + + assert.PanicsWithError(t, + "subscription channel not supported: myTrades", + func() { subToReq(&subscription.Subscription{Channel: subscription.MyTradesChannel}, p) }, + "should panic on invalid channel", + ) +} diff --git a/exchanges/hitbtc/hitbtc_types.go b/exchanges/hitbtc/hitbtc_types.go index 6ed9f5aaa47..59ddfc9c064 100644 --- a/exchanges/hitbtc/hitbtc_types.go +++ b/exchanges/hitbtc/hitbtc_types.go @@ -294,24 +294,17 @@ type ResponseError struct { // WsRequest defines a request obj for the JSON-RPC and gets a websocket response type WsRequest struct { - Method string `json:"method"` - Params WsParams `json:"params,omitempty"` - ID int64 `json:"id"` -} - -// WsNotification defines a notification obj for the JSON-RPC this does not get -// a websocket response -type WsNotification struct { - JSONRPCVersion string `json:"jsonrpc,omitempty"` - Method string `json:"method"` - Params WsParams `json:"params"` + JSONRPCVersion string `json:"jsonrpc,omitempty"` + Method string `json:"method"` + Params *WsParams `json:"params,omitempty"` + ID int64 `json:"id,omitempty"` } // WsParams are websocket params for a request type WsParams struct { Symbol string `json:"symbol,omitempty"` Period string `json:"period,omitempty"` - Limit int64 `json:"limit,omitempty"` + Limit int `json:"limit,omitempty"` Symbols []string `json:"symbols,omitempty"` } diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index 2957b800d1e..7b384d3be97 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -8,13 +8,16 @@ import ( "net/http" "strconv" "strings" + "text/template" "time" + "github.com/Masterminds/sprig/v3" "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" "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,6 +34,22 @@ const ( errAuthFailed = 1002 ) +var subscriptionNames = map[string]string{ + subscription.TickerChannel: "Ticker", + subscription.OrderbookChannel: "Orderbook", + subscription.CandlesChannel: "Candles", + subscription.AllTradesChannel: "Trades", + subscription.MyAccountChannel: "Reports", +} + +var defaultSubscriptions = subscription.List{ + {Enabled: true, Asset: asset.Spot, Channel: subscription.TickerChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.OrderbookChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.CandlesChannel, Interval: kline.ThirtyMin, Levels: 100}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.AllTradesChannel, Levels: 100}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.MyAccountChannel, Authenticated: true}, +} + // WsConnect starts a new connection with the websocket API func (h *HitBTC) WsConnect() error { if !h.Websocket.IsEnabled() || !h.IsEnabled() { @@ -465,104 +484,54 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update *WsOrderbook) error { }) } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (h *HitBTC) GenerateDefaultSubscriptions() (subscription.List, error) { - var channels = []string{ - "Ticker", - "Orderbook", - "Trades", - "Candles", - } - - var subscriptions subscription.List - if h.Websocket.CanUseAuthenticatedEndpoints() { - subscriptions = append(subscriptions, &subscription.Subscription{Channel: "Reports"}) - } - pairs, err := h.GetEnabledPairs(asset.Spot) - if err != nil { - return nil, err - } - pairFmt, err := h.GetPairFormat(asset.Spot, true) - if err != nil { - return nil, err - } - pairFmt.Delimiter = "" - pairs = pairs.Format(pairFmt) - for i := range channels { - for j := range pairs { - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: channels[i], - Pairs: currency.Pairs{pairs[j]}, - Asset: asset.Spot, - }) - } - } - return subscriptions, nil +// generateSubscriptions returns a list of subscriptions from the configured subscriptions feature +func (h *HitBTC) generateSubscriptions() (subscription.List, error) { + return h.Features.Subscriptions.ExpandTemplates(h) } -// Subscribe sends a websocket message to receive data from the channel -func (h *HitBTC) Subscribe(channelsToSubscribe subscription.List) error { - var errs error - for _, s := range channelsToSubscribe { - if len(s.Pairs) != 1 { - return subscription.ErrNotSinglePair - } - pair := s.Pairs[0] +// GetSubscriptionTemplate returns a subscription channel template +func (h *HitBTC) GetSubscriptionTemplate(_ *subscription.Subscription) (*template.Template, error) { + return template.New("master.tmpl").Funcs(sprig.FuncMap()).Funcs(template.FuncMap{ + "subToReq": subToReq, + "isSymbolChannel": isSymbolChannel, + }).Parse(subTplText) +} - r := WsRequest{ - Method: "subscribe" + s.Channel, - ID: h.Websocket.Conn.GenerateMessageID(false), - Params: WsParams{ - Symbol: pair.String(), - }, - } - switch s.Channel { - case "Trades": - r.Params.Limit = 100 - case "Candles": - r.Params.Period = "M30" - r.Params.Limit = 100 - } +const ( + subscribeOp = "subscribe" + unsubscribeOp = "unsubscribe" +) - err := h.Websocket.Conn.SendJSONMessage(context.TODO(), request.Unset, r) - if err == nil { - err = h.Websocket.AddSuccessfulSubscriptions(h.Websocket.Conn, s) - } - if err != nil { - errs = common.AppendError(errs, err) - } - } - return errs +// Subscribe sends a websocket message to receive data from the channel +func (h *HitBTC) Subscribe(subs subscription.List) error { + return h.ParallelChanOp(subs, func(subs subscription.List) error { return h.manageSubs(subscribeOp, subs) }, 1) } // Unsubscribe sends a websocket message to stop receiving data from the channel func (h *HitBTC) Unsubscribe(subs subscription.List) error { + return h.ParallelChanOp(subs, func(subs subscription.List) error { return h.manageSubs(unsubscribeOp, subs) }, 1) +} + +func (h *HitBTC) manageSubs(op string, subs subscription.List) error { var errs error + subs, errs = subs.ExpandTemplates(h) for _, s := range subs { - if len(s.Pairs) != 1 { - return subscription.ErrNotSinglePair - } - pair := s.Pairs[0] - - r := WsNotification{ + r := WsRequest{ JSONRPCVersion: rpcVersion, - Method: "unsubscribe" + s.Channel, - Params: WsParams{ - Symbol: pair.String(), - }, + ID: h.Websocket.Conn.GenerateMessageID(false), } - - switch s.Channel { - case "Trades": - r.Params.Limit = 100 - case "Candles": - r.Params.Period = "M30" - r.Params.Limit = 100 + if err := json.Unmarshal([]byte(s.QualifiedChannel), &r); err != nil { + errs = common.AppendError(errs, err) + continue } - - err := h.Websocket.Conn.SendJSONMessage(context.TODO(), request.Unset, r) + r.Method = op + r.Method + err := h.Websocket.Conn.SendJSONMessage(context.TODO(), request.Unset, r) // v2 api does not return an ID with errors, so we don't use ReturnResponse if err == nil { - err = h.Websocket.RemoveSubscriptions(h.Websocket.Conn, s) + if op == subscribeOp { + err = h.Websocket.AddSuccessfulSubscriptions(h.Websocket.Conn, s) + } else { + err = h.Websocket.RemoveSubscriptions(h.Websocket.Conn, s) + } } if err != nil { errs = common.AppendError(errs, err) @@ -838,3 +807,50 @@ func (h *HitBTC) wsGetTrades(c currency.Pair, limit int64, sort, by string) (*Ws } return &response, nil } + +// subToReq returns the subscription as a map to populate WsRequest +func subToReq(s *subscription.Subscription, maybePair ...currency.Pair) *WsRequest { + name, ok := subscriptionNames[s.Channel] + if !ok { + panic(fmt.Errorf("%w: %s", subscription.ErrNotSupported, s.Channel)) + } + r := &WsRequest{ + Method: name, + } + if len(maybePair) != 0 { + r.Params = &WsParams{ + Symbol: maybePair[0].String(), + Limit: s.Levels, + } + if s.Interval != 0 { + var err error + if r.Params.Period, err = formatExchangeKlineInterval(s.Interval); err != nil { + panic(err) + } + } + } else if s.Levels != 0 { + r.Params = &WsParams{ + Limit: s.Levels, + } + } + return r +} + +// isSymbolChannel returns if the channel expects receive a symbol +func isSymbolChannel(s *subscription.Subscription) bool { + return s.Channel != subscription.MyAccountChannel +} + +const subTplText = ` +{{- if isSymbolChannel $.S }} + {{ range $asset, $pairs := $.AssetPairs }} + {{- range $p := $pairs -}} + {{- subToReq $.S $p | mustToJson }} + {{ $.PairSeparator }} + {{- end }} + {{ $.AssetSeparator }} + {{- end }} +{{- else }} + {{- subToReq $.S | mustToJson }} +{{- end }} +` diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 8de08796c42..8a57bc7ee78 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -108,6 +108,7 @@ func (h *HitBTC) SetDefaults() { GlobalResultLimit: 1000, }, }, + Subscriptions: defaultSubscriptions.Clone(), } h.Requester, err = request.New(h.Name, @@ -157,7 +158,7 @@ func (h *HitBTC) 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, OrderbookBufferConfig: buffer.Config{ SortBuffer: true, @@ -780,8 +781,8 @@ func (h *HitBTC) ValidateAPICredentials(ctx context.Context, assetType asset.Ite return h.CheckTransientError(err) } -// FormatExchangeKlineInterval returns Interval to exchange formatted string -func (h *HitBTC) FormatExchangeKlineInterval(in kline.Interval) (string, error) { +// formatExchangeKlineInterval returns Interval to exchange formatted string +func formatExchangeKlineInterval(in kline.Interval) (string, error) { switch in { case kline.OneMin: return "M1", nil @@ -814,7 +815,7 @@ func (h *HitBTC) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a return nil, err } - formattedInterval, err := h.FormatExchangeKlineInterval(req.ExchangeInterval) + formattedInterval, err := formatExchangeKlineInterval(req.ExchangeInterval) if err != nil { return nil, err } @@ -850,7 +851,7 @@ func (h *HitBTC) GetHistoricCandlesExtended(ctx context.Context, pair currency.P return nil, err } - formattedInterval, err := h.FormatExchangeKlineInterval(req.ExchangeInterval) + formattedInterval, err := formatExchangeKlineInterval(req.ExchangeInterval) if err != nil { return nil, err }