Skip to content

Commit

Permalink
Huobi: Add V2 websocket support
Browse files Browse the repository at this point in the history
  • Loading branch information
gbjk committed Oct 28, 2024
1 parent 5544c8d commit 58a8c92
Show file tree
Hide file tree
Showing 4 changed files with 507 additions and 848 deletions.
108 changes: 37 additions & 71 deletions exchanges/huobi/huobi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"testing"
"time"

"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/common"
Expand All @@ -24,7 +23,6 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"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"
Expand Down Expand Up @@ -69,33 +67,9 @@ func TestMain(m *testing.M) {
if err != nil {
log.Fatal("Huobi setup error", err)
}

os.Exit(m.Run())
}

func setupWsTests(t *testing.T) {
t.Helper()
if wsSetupRan {
return
}
if !h.Websocket.IsEnabled() && !h.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(h) {
t.Skip(stream.ErrWebsocketNotEnabled.Error())
}
comms = make(chan WsMessage, sharedtestvalues.WebsocketChannelOverrideCapacity)
go h.wsReadData()
var dialer websocket.Dialer
err := h.wsAuthenticatedDial(&dialer)
if err != nil {
t.Fatal(err)
}
err = h.wsLogin(context.Background())
if err != nil {
t.Fatal(err)
}

wsSetupRan = true
}

func TestGetCurrenciesIncludingChains(t *testing.T) {
t.Parallel()
r, err := h.GetCurrenciesIncludingChains(context.Background(), currency.EMPTYCODE)
Expand Down Expand Up @@ -1315,51 +1289,6 @@ func TestQueryWithdrawQuota(t *testing.T) {
}
}

// TestWsGetAccountsList connects to WS, logs in, gets account list
func TestWsGetAccountsList(t *testing.T) {
setupWsTests(t)
if _, err := h.wsGetAccountsList(context.Background()); err != nil {
t.Error(err)
}
}

// TestWsGetOrderList connects to WS, logs in, gets order list
func TestWsGetOrderList(t *testing.T) {
setupWsTests(t)
p, err := currency.NewPairFromString("ethbtc")
if err != nil {
t.Error(err)
}
_, err = h.wsGetOrdersList(context.Background(), 1, p)
if err != nil {
t.Error(err)
}
}

// TestWsGetOrderDetails connects to WS, logs in, gets order details
func TestWsGetOrderDetails(t *testing.T) {
setupWsTests(t)
orderID := "123"
_, err := h.wsGetOrderDetails(context.Background(), orderID)
if err != nil {
t.Error(err)
}
}

func TestWsSubResponse(t *testing.T) {
pressXToJSON := []byte(`{
"op": "sub",
"cid": "123",
"err-code": 0,
"ts": 1489474081631,
"topic": "accounts"
}`)
err := h.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
}

func TestWsKline(t *testing.T) {
err := h.Websocket.AddSubscriptions(h.Websocket.Conn, &subscription.Subscription{Key: "market.btcusdt.kline.1min", Asset: asset.Spot, Pairs: currency.Pairs{btcusdtPair}, Channel: subscription.CandlesChannel})
require.NoError(t, err, "AddSubscriptions must not error")
Expand Down Expand Up @@ -1988,16 +1917,53 @@ func TestGenerateSubscriptions(t *testing.T) {
testsubs.EqualLists(t, exp, subs)
}

// TestSubscribe exercises live subscriptions for public channels
func TestSubscribe(t *testing.T) {
t.Parallel()
h := new(HUOBI)
require.NoError(t, testexch.Setup(h), "Test instance Setup must not error")
h.Websocket.SetCanUseAuthenticatedEndpoints(true)
subs, err := h.Features.Subscriptions.ExpandTemplates(h)
require.NoError(t, err, "ExpandTemplates must not error")
h.Features.Subscriptions = subscription.List{}
testexch.SetupWs(t, h)
err = h.Subscribe(subs)
require.NoError(t, err, "Subscribe must not error")
got := h.Websocket.GetSubscriptions()
require.Len(t, got, len(subs))
for _, s := range got {
assert.Equal(t, subscription.SubscribedState, s.State())
}
}

// TestAuthSubscribe exercises mock subscriptions for private channels
func TestAuthSubscribe(t *testing.T) {
// TODO: currently using live - need to implement mock
t.Parallel()
h := new(HUOBI)
require.NoError(t, testexch.Setup(h), "Test instance Setup must not error")
h.Websocket.SetCanUseAuthenticatedEndpoints(true)
subs, err := h.Features.Subscriptions.ExpandTemplates(h)
require.NoError(t, err, "ExpandTemplates must not error")
h.Features.Subscriptions = subscription.List{}
testexch.SetupWs(t, h)
err = h.Subscribe(subs)
require.NoError(t, err, "Subscribe must not error")
got := h.Websocket.GetSubscriptions()
require.Len(t, got, len(subs))
for _, s := range got {
assert.Equal(t, subscription.SubscribedState, s.State())
}
}

func TestChannelName(t *testing.T) {
s := &subscription.Subscription{Channel: subscription.CandlesChannel}
assert.Equal(t, "x", channelName(s, btcusdPair))
t.Error("more to come")
}

func TestAuthLogin(t *testing.T) {
//{"action":"req","code":2002,"ch":"auth","message":"auth.fail"}
}

var updatePairsMutex sync.Mutex
Expand Down
95 changes: 37 additions & 58 deletions exchanges/huobi/huobi_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -807,30 +807,24 @@ type KlinesRequestParams struct {
Size int // Size; [1-2000]
}

// WsRequest defines a request data structure
type WsRequest struct {
Topic string `json:"req,omitempty"`
Subscribe string `json:"sub,omitempty"`
Unsubscribe string `json:"unsub,omitempty"`
ClientID int64 `json:"cid,string,omitempty"`
}

// WsResponse defines a response from the websocket connection when there
// is an error
type WsResponse struct {
Op string `json:"op"`
TS int64 `json:"ts"`
Status string `json:"status"`
// ErrorCode returns either an integer or a string
ErrorCode interface{} `json:"err-code"`
ErrorMessage string `json:"err-msg"`
Ping int64 `json:"ping"`
Channel string `json:"ch"`
Rep string `json:"rep"`
Topic string `json:"topic"`
Subscribed string `json:"subbed"`
UnSubscribed string `json:"unsubbed"`
ClientID int64 `json:"cid,string"`
// wsSubReq is a request to subscribe to or unubscribe from a topic for public channels (private channels use generic wsReq)
type wsSubReq struct {
Id string `json:"id,omitempty"`
Sub string `json:"sub,omitempty"`
Unsub string `json:"unsub,omitempty"`
}

// wsSubResp is a response to a subscribe/unsubcribe request

Check failure on line 817 in exchanges/huobi/huobi_types.go

View workflow job for this annotation

GitHub Actions / Spell checker

unsubcribe ==> unsubscribe
type wsSubResp struct {
Id int64 `json:"id"`
Op string `json:"op"`
Channel string `json:"ch"`
Timestamp int64 `json:"ts"`
Status string `json:"status"`
ErrorCode any `json:"err-code"` // ErrorCode returns either an integer or a string
ErrorMessage string `json:"err-msg"`
Subscribed string `json:"subbed"`
UnSubscribed string `json:"unsubbed"`
}

// WsHeartBeat defines a heartbeat request
Expand Down Expand Up @@ -901,21 +895,21 @@ type WsTrade struct {
}
}

// WsAuthenticationRequest data for login
type WsAuthenticationRequest struct {
Op string `json:"op"`
AccessKeyID string `json:"AccessKeyId"`
SignatureMethod string `json:"SignatureMethod"`
SignatureVersion string `json:"SignatureVersion"`
Timestamp string `json:"Timestamp"`
Signature string `json:"Signature"`
ClientID int64 `json:"cid,string,omitempty"`
// wsReq contains authentication login fields
type wsReq struct {
Action string `json:"action"`
Channel string `json:"ch"`
Params any `json:"params"`
}

// WsMessage defines read data from the websocket connection
type WsMessage struct {
Raw []byte
URL string
// wsAuthReq contains authentication login fields
type wsAuthReq struct {
AuthType string `json:"authType"`
AccessKey string `json:"accessKey"`
SignatureMethod string `json:"signatureMethod"`
SignatureVersion string `json:"signatureVersion"`
Timestamp string `json:"timestamp"`
Signature string `json:"signature"`
}

// WsAuthenticatedSubscriptionRequest request for subscription on authenticated connection
Expand All @@ -927,7 +921,6 @@ type WsAuthenticatedSubscriptionRequest struct {
Timestamp string `json:"Timestamp"`
Signature string `json:"Signature"`
Topic string `json:"topic"`
ClientID int64 `json:"cid,string,omitempty"`
}

// WsAuthenticatedAccountsListRequest request for account list authenticated connection
Expand All @@ -940,7 +933,6 @@ type WsAuthenticatedAccountsListRequest struct {
Signature string `json:"Signature"`
Topic string `json:"topic"`
Symbol string `json:"symbol"`
ClientID int64 `json:"cid,string,omitempty"`
}

// WsAuthenticatedOrderDetailsRequest request for order details authenticated connection
Expand All @@ -953,7 +945,6 @@ type WsAuthenticatedOrderDetailsRequest struct {
Signature string `json:"Signature"`
Topic string `json:"topic"`
OrderID string `json:"order-id"`
ClientID int64 `json:"cid,string,omitempty"`
}

// WsAuthenticatedOrdersListRequest request for orderslist authenticated connection
Expand All @@ -968,12 +959,11 @@ type WsAuthenticatedOrdersListRequest struct {
States string `json:"states"`
AccountID int64 `json:"account-id"`
Symbol string `json:"symbol"`
ClientID int64 `json:"cid,string,omitempty"`
}

// WsAuthenticatedAccountsResponse response from Accounts authenticated subscription
type WsAuthenticatedAccountsResponse struct {
WsResponse
wsSubResp
Data WsAuthenticatedAccountsResponseData `json:"data"`
}

Expand All @@ -993,7 +983,7 @@ type WsAuthenticatedAccountsResponseDataList struct {

// WsAuthenticatedOrdersUpdateResponse response from OrdersUpdate authenticated subscription
type WsAuthenticatedOrdersUpdateResponse struct {
WsResponse
wsSubResp
Data WsAuthenticatedOrdersUpdateResponseData `json:"data"`
}

Expand All @@ -1013,16 +1003,10 @@ type WsAuthenticatedOrdersUpdateResponseData struct {

// WsAuthenticatedOrdersResponse response from Orders authenticated subscription
type WsAuthenticatedOrdersResponse struct {
WsResponse
wsSubResp
Data []WsAuthenticatedOrdersResponseData `json:"data"`
}

// WsOldOrderUpdate response from Orders authenticated subscription
type WsOldOrderUpdate struct {
WsResponse
Data WsAuthenticatedOrdersResponseData `json:"data"`
}

// WsAuthenticatedOrdersResponseData order data
type WsAuthenticatedOrdersResponseData struct {
SeqID int64 `json:"seq-id"`
Expand All @@ -1045,7 +1029,7 @@ type WsAuthenticatedOrdersResponseData struct {

// WsAuthenticatedAccountsListResponse response from AccountsList authenticated endpoint
type WsAuthenticatedAccountsListResponse struct {
WsResponse
wsSubResp
Data []WsAuthenticatedAccountsListResponseData `json:"data"`
}

Expand All @@ -1066,21 +1050,16 @@ type WsAuthenticatedAccountsListResponseDataList struct {

// WsAuthenticatedOrdersListResponse response from OrdersList authenticated endpoint
type WsAuthenticatedOrdersListResponse struct {
WsResponse
wsSubResp
Data []OrderInfo `json:"data"`
}

// WsAuthenticatedOrderDetailResponse response from OrderDetail authenticated endpoint
type WsAuthenticatedOrderDetailResponse struct {
WsResponse
wsSubResp
Data OrderInfo `json:"data"`
}

// WsPong sent for pong messages
type WsPong struct {
Pong int64 `json:"pong"`
}

type authenticationPing struct {
OP string `json:"op"`
TS int64 `json:"ts"`
Expand Down
Loading

0 comments on commit 58a8c92

Please sign in to comment.