Websocket request-response correlation (#328)

* Establishes new websocket functionality. Begins the creation of the websocket request

* Adding a wrapper over gorilla websocket connect,send,receive to handle ID messages. Doesn't work

* Successfully moved exchange_websocket into its own wshandler namespace. oof

* Sets up ZB to use a round trip WS request system

* Adds Kraken ID support to subscriptions. Renames duplicate func name UnsubscribeToChannels to RemoveSubscribedChannels. Adds some helper methods in the WebsocketConn to reduce duplicate code. Cleans up ZB implementation

* Fixes double locking which caused no websocket data to be read. Fixes requestid for kraken subscriptions

* Completes Huobi and Hadax implementation. Extends ZB error handling. Adds GZip support for reading messages

* Adds HitBTC support. Adds GetCurrencies, GetSymbols, GetTrades WS funcs. Adds super fun new parameter to GenerateMessageID for Unix and UnixNano

* Adds GateIO id support

* Adds Coinut support. Prevents nil reference error in constatus when there isnt one

* Standardises all Exchange websockets to use the wshandler websocket. Removes the wsRequestMtx as wshandler handles that now. Makes the Dialer a dialer, its not externally referenced that I can see.

* Fixes issue with coinut implementation. Updates bitmex currencies. Removes redundant log messages which are used to log messages

* Starts testing. Renames files

* Adds tests for websocket connection

* Reverts request.go change

* Linting everything

* Fixes rebase issue

* Final changes. Fixes variable names, removes log.Debug, removes lines, rearranges test types, removes order correlation websocket type

* Final final commit, fixing ZB issues.

* Adds traffic alerts where missed. Changes empty struct pointer addresses to nil instead. Removes empty lines

* Fixed string conversion

* Fixes issue with ZB not sending success codes

* Fixes issue with coinut processing due to nonce handling with subscriptions

* Fixes issue where ZB test failure was not caught. Removes unnecessary error handling from other ZB tests

* Removes unused interface

* Renames wshandler.Init() to wshandler.Run()

* Updates template file

* Capitalises cryptocurrencies in struct. Moves websocketResponseCheckTimeout and websocketResponseMaxLimit into config options. Moves connection configuration to main exchange Setup (where appropriate). Reverts currencylastupdated ticks. Improves reader close error checking

* Fixes two inconsistent websocket delay times

* Creates a default variable for websocket ResponseMaxLimit and ResponseCheckTimeout, then applies it to setdefaults and all tests

* Updates exchange template to set and use default websocket response limits
This commit is contained in:
Scott
2019-08-07 15:15:01 +10:00
committed by Adrian Gallagher
parent 6e70f0642a
commit e209d85d0d
113 changed files with 3269 additions and 2594 deletions

View File

@@ -7,16 +7,15 @@ import (
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
log "github.com/thrasher-/gocryptotrader/logger"
)
@@ -48,9 +47,8 @@ const (
// Gateio is the overarching type across this package
type Gateio struct {
WebsocketConn *websocket.Conn
WebsocketConn *wshandler.WebsocketConnection
exchange.Base
wsRequestMtx sync.Mutex
}
// SetDefaults sets default values for the exchange
@@ -76,14 +74,17 @@ func (g *Gateio) SetDefaults() {
g.APIUrl = g.APIUrlDefault
g.APIUrlSecondaryDefault = gateioMarketURL
g.APIUrlSecondary = g.APIUrlSecondaryDefault
g.WebsocketInit()
g.Websocket.Functionality = exchange.WebsocketTickerSupported |
exchange.WebsocketTradeDataSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketKlineSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported
g.Websocket = wshandler.New()
g.Websocket.Functionality = wshandler.WebsocketTickerSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketKlineSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported |
wshandler.WebsocketMessageCorrelationSupported
g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup sets user configuration
@@ -125,17 +126,27 @@ func (g *Gateio) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = g.WebsocketSetup(g.WsConnect,
err = g.Websocket.Setup(g.WsConnect,
g.Subscribe,
g.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
gateioWebsocketEndpoint,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
g.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: g.Name,
URL: g.Websocket.GetWebsocketURL(),
ProxyURL: g.Websocket.GetProxyAddress(),
Verbose: g.Verbose,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
RateLimit: gateioWebsocketRateLimit,
}
}
}

View File

@@ -3,7 +3,6 @@ package gateio
import (
"net/http"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
@@ -11,6 +10,7 @@ import (
"github.com/thrasher-/gocryptotrader/currency"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
)
// Please supply your own APIKEYS here for due diligence testing
@@ -22,6 +22,7 @@ const (
)
var g Gateio
var wsSetupRan bool
func TestSetDefaults(t *testing.T) {
g.SetDefaults()
@@ -496,47 +497,126 @@ func TestGetOrderInfo(t *testing.T) {
}
}
// TestWsAuth dials websocket, sends login request.
func TestWsAuth(t *testing.T) {
// TestWsGetBalance dials websocket, sends balance request.
func TestWsGetBalance(t *testing.T) {
g.SetDefaults()
TestSetup(t)
if !g.Websocket.IsEnabled() && !g.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
g.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: g.Name,
URL: gateioWebsocketEndpoint,
Verbose: g.Verbose,
RateLimit: gateioWebsocketRateLimit,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var err error
var dialer websocket.Dialer
g.WebsocketConn, _, err = dialer.Dial(g.Websocket.GetWebsocketURL(),
http.Header{})
err := g.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
go g.WsHandleData()
g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
go g.WsHandleData()
defer g.WebsocketConn.Close()
err = g.wsServerSignIn()
resp, err := g.wsServerSignIn()
if err != nil {
t.Fatal(err)
}
if resp.Result.Status != "success" {
t.Fatal("Unsuccessful login")
}
_, err = g.wsGetBalance([]string{"EOS", "BTC"})
if err != nil {
t.Error(err)
}
}
// TestWsGetOrderInfo dials websocket, sends order info request.
func TestWsGetOrderInfo(t *testing.T) {
g.SetDefaults()
TestSetup(t)
if !g.Websocket.IsEnabled() && !g.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(wshandler.WebsocketNotEnabled)
}
g.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: g.Name,
URL: gateioWebsocketEndpoint,
Verbose: g.Verbose,
RateLimit: gateioWebsocketRateLimit,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var dialer websocket.Dialer
err := g.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
go g.WsHandleData()
g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
resp, err := g.wsServerSignIn()
if err != nil {
t.Fatal(err)
}
if resp.Result.Status != "success" {
t.Fatal("Unsuccessful login")
}
_, err = g.wsGetOrderInfo("EOS_USDT", 0, 10)
if err != nil {
t.Error(err)
}
}
func setupWSTestAuth(t *testing.T) {
if wsSetupRan {
return
}
g.SetDefaults()
TestSetup(t)
if !g.Websocket.IsEnabled() && !g.AuthenticatedWebsocketAPISupport {
t.Skip(wshandler.WebsocketNotEnabled)
}
g.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: g.Name,
URL: gateioWebsocketEndpoint,
Verbose: g.Verbose,
RateLimit: gateioWebsocketRateLimit,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var dialer websocket.Dialer
err := g.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
go g.WsHandleData()
g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
wsSetupRan = true
}
// TestWsSubscribe dials websocket, sends a subscribe request.
func TestWsSubscribe(t *testing.T) {
setupWSTestAuth(t)
err := g.Subscribe(wshandler.WebsocketChannelSubscription{
Channel: "ticker.subscribe",
Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_"),
})
if err != nil {
t.Error(err)
}
}
// TestWsUnsubscribe dials websocket, sends an unsubscribe request.
func TestWsUnsubscribe(t *testing.T) {
setupWSTestAuth(t)
err := g.Unsubscribe(wshandler.WebsocketChannelSubscription{
Channel: "ticker.subscribe",
Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_"),
})
if err != nil {
t.Error(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case resultString := <-g.Websocket.DataHandler:
if !common.StringContains(resultString.(string), "success") {
t.Error("Authentication failed")
}
case <-timer.C:
t.Error("Expected response")
}
timer.Stop()
err = g.wsGetBalance()
if err != nil {
t.Error(err)
}
timer = time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case <-g.Websocket.DataHandler:
case <-timer.C:
t.Error("Expected response")
}
timer.Stop()
}

View File

@@ -35,14 +35,6 @@ var (
TimeIntervalDay = TimeInterval(60 * 60 * 24)
)
// IDs for requests
const (
IDGeneric = 0000
IDSignIn = 1010
IDBalance = 2000
IDOrderQuery = 3001
)
// MarketInfoResponse holds the market info data
type MarketInfoResponse struct {
Result string `json:"result"`
@@ -478,3 +470,32 @@ type WebSocketOrderQueryRecords struct {
FilledAmount string `json:"filledAmount"`
FilledTotal string `json:"filledTotal"`
}
// WebsocketAuthenticationResponse contains the result of a login request
type WebsocketAuthenticationResponse struct {
Error string `json:"error"`
Result struct {
Status string `json:"status"`
} `json:"result"`
ID int64 `json:"id"`
}
// wsGetBalanceRequest
type wsGetBalanceRequest struct {
ID int64 `json:"id"`
Method string `json:"method"`
Params []string `json:"params,omitempty"`
}
// WsGetBalanceResponse stores WS GetBalance response
type WsGetBalanceResponse struct {
Error interface{} `json:"error"`
Result map[currency.Code]WsGetBalanceResponseData `json:"result,omitempty"`
ID int64 `json:"id"`
}
// WsGetBalanceResponseData contains currency data
type WsGetBalanceResponseData struct {
Available float64 `json:"available,string"`
Freeze float64 `json:"freeze,string"`
}

View File

@@ -1,11 +1,9 @@
package gateio
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
@@ -15,40 +13,28 @@ import (
"github.com/thrasher-/gocryptotrader/currency"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
log "github.com/thrasher-/gocryptotrader/logger"
)
const (
gateioWebsocketEndpoint = "wss://ws.gate.io/v3/"
gatioWsMethodPing = "ping"
gateioWebsocketRateLimit = 120 * time.Millisecond
gateioWebsocketRateLimit = 120
)
// WsConnect initiates a websocket connection
func (g *Gateio) WsConnect() error {
if !g.Websocket.IsEnabled() || !g.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if g.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(g.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
g.WebsocketConn, _, err = dialer.Dial(g.Websocket.GetWebsocketURL(),
http.Header{})
err := g.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
go g.WsHandleData()
err = g.wsServerSignIn()
_, err = g.wsServerSignIn()
if err != nil {
log.Errorf("%v - authentication failed: %v", g.Name, err)
}
@@ -57,37 +43,33 @@ func (g *Gateio) WsConnect() error {
return nil
}
func (g *Gateio) wsServerSignIn() error {
func (g *Gateio) wsServerSignIn() (*WebsocketAuthenticationResponse, error) {
if !g.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", g.Name)
return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", g.Name)
}
nonce := int(time.Now().Unix() * 1000)
sigTemp := g.GenerateSignature(strconv.Itoa(nonce))
signature := common.Base64Encode(sigTemp)
signinWsRequest := WebsocketRequest{
ID: IDSignIn,
ID: g.WebsocketConn.GenerateMessageID(true),
Method: "server.sign",
Params: []interface{}{g.APIKey, signature, nonce},
}
err := g.wsSend(signinWsRequest)
resp, err := g.WebsocketConn.SendMessageReturnResponse(signinWsRequest.ID, signinWsRequest)
if err != nil {
g.Websocket.SetCanUseAuthenticatedEndpoints(false)
return err
return nil, err
}
time.Sleep(time.Second * 2) // sleep to allow server to complete sign-on if further authenticated requests are sent prior to this they will fail
return nil
}
// WsReadData reads from the websocket connection and returns the websocket
// response
func (g *Gateio) WsReadData() (exchange.WebsocketResponse, error) {
_, resp, err := g.WebsocketConn.ReadMessage()
var response WebsocketAuthenticationResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return exchange.WebsocketResponse{}, err
g.Websocket.SetCanUseAuthenticatedEndpoints(false)
return nil, err
}
g.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{Raw: resp}, nil
if response.Result.Status == "success" {
g.Websocket.SetCanUseAuthenticatedEndpoints(true)
}
return &response, nil
}
// WsHandleData handles all the websocket data coming from the websocket
@@ -105,25 +87,28 @@ func (g *Gateio) WsHandleData() {
return
default:
resp, err := g.WsReadData()
resp, err := g.WebsocketConn.ReadMessage()
if err != nil {
g.Websocket.DataHandler <- err
// Read data error messages can overwhelm and panic the application
time.Sleep(time.Second)
continue
return
}
g.Websocket.TrafficAlert <- struct{}{}
var result WebsocketResponse
err = common.JSONDecode(resp.Raw, &result)
if err != nil {
g.Websocket.DataHandler <- err
continue
}
if result.ID > 0 {
g.WebsocketConn.AddResponseWithID(result.ID, resp.Raw)
continue
}
if result.Error.Code != 0 {
if common.StringContains(result.Error.Message, "authentication") {
g.Websocket.DataHandler <- fmt.Errorf("%v - authentication failed: %v", g.Name, err)
g.Websocket.SetCanUseAuthenticatedEndpoints(false)
continue
}
g.Websocket.DataHandler <- fmt.Errorf("%v error %s",
@@ -131,49 +116,6 @@ func (g *Gateio) WsHandleData() {
continue
}
switch result.ID {
case IDSignIn:
g.Websocket.SetCanUseAuthenticatedEndpoints(true)
g.Websocket.DataHandler <- string(result.Result)
case IDBalance:
var balance WebsocketBalance
var balanceInterface interface{}
err = json.Unmarshal(result.Result, &balanceInterface)
if err != nil {
g.Websocket.DataHandler <- err
}
var p WebsocketBalanceCurrency
switch x := balanceInterface.(type) {
case map[string]interface{}:
for xx := range x {
switch kk := x[xx].(type) {
case map[string]interface{}:
p = WebsocketBalanceCurrency{
Currency: xx,
Available: kk["available"].(string),
Locked: kk["freeze"].(string),
}
balance.Currency = append(balance.Currency, p)
default:
break
}
}
default:
break
}
g.Websocket.DataHandler <- balance
case IDOrderQuery:
var orderQuery WebSocketOrderQueryResult
err = common.JSONDecode(result.Result, &orderQuery)
if err != nil {
g.Websocket.DataHandler <- err
continue
}
g.Websocket.DataHandler <- orderQuery
default:
break
}
switch {
case common.StringContains(result.Method, "ticker"):
var ticker WebsocketTicker
@@ -190,7 +132,7 @@ func (g *Gateio) WsHandleData() {
continue
}
g.Websocket.DataHandler <- exchange.TickerData{
g.Websocket.DataHandler <- wshandler.TickerData{
Timestamp: time.Now(),
Pair: currency.NewPairFromString(c),
AssetType: "SPOT",
@@ -218,7 +160,7 @@ func (g *Gateio) WsHandleData() {
}
for _, trade := range trades {
g.Websocket.DataHandler <- exchange.TradeData{
g.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: time.Now(),
CurrencyPair: currency.NewPairFromString(c),
AssetType: "SPOT",
@@ -310,7 +252,7 @@ func (g *Gateio) WsHandleData() {
}
}
g.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: currency.NewPairFromString(c),
Asset: "SPOT",
Exchange: g.GetName(),
@@ -330,7 +272,7 @@ func (g *Gateio) WsHandleData() {
low, _ := strconv.ParseFloat(data[4].(string), 64)
volume, _ := strconv.ParseFloat(data[5].(string), 64)
g.Websocket.DataHandler <- exchange.KlineData{
g.Websocket.DataHandler <- wshandler.KlineData{
Timestamp: time.Now(),
Pair: currency.NewPairFromString(data[7].(string)),
AssetType: "SPOT",
@@ -352,11 +294,11 @@ func (g *Gateio) GenerateAuthenticatedSubscriptions() {
return
}
var channels = []string{"balance.subscribe", "order.subscribe"}
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
enabledCurrencies := g.GetEnabledCurrencies()
for i := range channels {
for j := range enabledCurrencies {
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channels[i],
Currency: enabledCurrencies[j],
})
@@ -368,7 +310,7 @@ func (g *Gateio) GenerateAuthenticatedSubscriptions() {
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (g *Gateio) GenerateDefaultSubscriptions() {
var channels = []string{"ticker.subscribe", "trades.subscribe", "depth.subscribe", "kline.subscribe"}
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
enabledCurrencies := g.GetEnabledCurrencies()
for i := range channels {
for j := range enabledCurrencies {
@@ -379,7 +321,7 @@ func (g *Gateio) GenerateDefaultSubscriptions() {
} else if strings.EqualFold(channels[i], "kline.subscribe") {
params["interval"] = 1800
}
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channels[i],
Currency: enabledCurrencies[j],
Params: params,
@@ -390,54 +332,84 @@ func (g *Gateio) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (g *Gateio) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (g *Gateio) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
params := []interface{}{channelToSubscribe.Currency.String()}
for _, paramValue := range channelToSubscribe.Params {
params = append(params, paramValue)
}
subscribe := WebsocketRequest{
ID: IDGeneric,
ID: g.WebsocketConn.GenerateMessageID(true),
Method: channelToSubscribe.Channel,
Params: params,
}
if strings.EqualFold(channelToSubscribe.Channel, "balance.subscribe") {
subscribe.ID = IDBalance
resp, err := g.WebsocketConn.SendMessageReturnResponse(subscribe.ID, subscribe)
if err != nil {
return err
}
return g.wsSend(subscribe)
var response WebsocketAuthenticationResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return err
}
if response.Result.Status != "success" {
return fmt.Errorf("%v could not subscribe to %v", g.Name, channelToSubscribe.Channel)
}
return nil
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (g *Gateio) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (g *Gateio) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
unsbuscribeText := strings.Replace(channelToSubscribe.Channel, "subscribe", "unsubscribe", 1)
subscribe := WebsocketRequest{
ID: IDGeneric,
ID: g.WebsocketConn.GenerateMessageID(true),
Method: unsbuscribeText,
Params: []interface{}{channelToSubscribe.Currency.String(), 1800},
}
return g.wsSend(subscribe)
resp, err := g.WebsocketConn.SendMessageReturnResponse(subscribe.ID, subscribe)
if err != nil {
return err
}
var response WebsocketAuthenticationResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return err
}
if response.Result.Status != "success" {
return fmt.Errorf("%v could not subscribe to %v", g.Name, channelToSubscribe.Channel)
}
return nil
}
func (g *Gateio) wsGetBalance() error {
func (g *Gateio) wsGetBalance(currencies []string) (*WsGetBalanceResponse, error) {
if !g.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authorised to get balance", g.Name)
return nil, fmt.Errorf("%v not authorised to get balance", g.Name)
}
balanceWsRequest := WebsocketRequest{
ID: IDBalance,
balanceWsRequest := wsGetBalanceRequest{
ID: g.WebsocketConn.GenerateMessageID(false),
Method: "balance.query",
Params: []interface{}{},
Params: currencies,
}
return g.wsSend(balanceWsRequest)
resp, err := g.WebsocketConn.SendMessageReturnResponse(balanceWsRequest.ID, balanceWsRequest)
if err != nil {
return nil, err
}
var balance WsGetBalanceResponse
err = common.JSONDecode(resp, &balance)
if err != nil {
return &balance, err
}
return &balance, nil
}
func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) error {
func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) (*WebSocketOrderQueryResult, error) {
if !g.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authorised to get order info", g.Name)
return nil, fmt.Errorf("%v not authorised to get order info", g.Name)
}
order := WebsocketRequest{
ID: IDOrderQuery,
ID: g.WebsocketConn.GenerateMessageID(true),
Method: "order.query",
Params: []interface{}{
market,
@@ -445,17 +417,14 @@ func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) error {
limit,
},
}
return g.wsSend(order)
}
// WsSend sends data to the websocket server
func (g *Gateio) wsSend(data interface{}) error {
g.wsRequestMtx.Lock()
defer g.wsRequestMtx.Unlock()
if g.Verbose {
log.Debugf("%v sending message to websocket %v", g.Name, data)
resp, err := g.WebsocketConn.SendMessageReturnResponse(order.ID, order)
if err != nil {
return nil, err
}
// Basic rate limiter
time.Sleep(gateioWebsocketRateLimit)
return g.WebsocketConn.WriteJSON(data)
var orderQuery WebSocketOrderQueryResult
err = common.JSONDecode(resp, &orderQuery)
if err != nil {
return &orderQuery, err
}
return &orderQuery, nil
}

View File

@@ -13,6 +13,7 @@ import (
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
log "github.com/thrasher-/gocryptotrader/logger"
)
@@ -355,7 +356,7 @@ func (g *Gateio) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.
}
// GetWebsocket returns a pointer to the exchange websocket
func (g *Gateio) GetWebsocket() (*exchange.Websocket, error) {
func (g *Gateio) GetWebsocket() (*wshandler.Websocket, error) {
return g.Websocket, nil
}
@@ -448,24 +449,25 @@ func (g *Gateio) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (g *Gateio) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (g *Gateio) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
g.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (g *Gateio) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
g.Websocket.UnsubscribeToChannels(channels)
func (g *Gateio) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
g.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (g *Gateio) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (g *Gateio) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return g.Websocket.GetSubscriptions(), nil
}
// AuthenticateWebsocket sends an authentication message to the websocket
func (g *Gateio) AuthenticateWebsocket() error {
return g.wsServerSignIn()
_, err := g.wsServerSignIn()
return err
}