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

@@ -175,7 +175,7 @@ func (s *Slack) NewConnection() error {
// WebsocketConnect creates a websocket dialer amd initiates a websocket
// connection
func (s *Slack) WebsocketConnect() error {
var Dialer websocket.Dialer
var dialer websocket.Dialer
var err error
websocketURL := s.Details.URL
@@ -183,7 +183,7 @@ func (s *Slack) WebsocketConnect() error {
websocketURL = s.ReconnectURL
}
s.WebsocketConn, _, err = Dialer.Dial(websocketURL, http.Header{})
s.WebsocketConn, _, err = dialer.Dial(websocketURL, http.Header{})
if err != nil {
return err
}

View File

@@ -82,7 +82,9 @@ have multiple deposit accounts for different FIAT deposit currencies.
"Websocket": false,
"UseSandbox": false,
"RESTPollingDelay": 10,
"HTTPTimeout": 15000000000,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",

View File

@@ -26,18 +26,20 @@ import (
// Constants declared here are filename strings and test strings
const (
FXProviderFixer = "fixer"
EncryptedConfigFile = "config.dat"
ConfigFile = "config.json"
ConfigTestFile = "../testdata/configtest.json"
configFileEncryptionPrompt = 0
configFileEncryptionEnabled = 1
configFileEncryptionDisabled = -1
configPairsLastUpdatedWarningThreshold = 30 // 30 days
configDefaultHTTPTimeout = time.Second * 15
configMaxAuthFailres = 3
defaultNTPAllowedDifference = 50000000
defaultNTPAllowedNegativeDifference = 50000000
FXProviderFixer = "fixer"
EncryptedConfigFile = "config.dat"
ConfigFile = "config.json"
ConfigTestFile = "../testdata/configtest.json"
configFileEncryptionPrompt = 0
configFileEncryptionEnabled = 1
configFileEncryptionDisabled = -1
configPairsLastUpdatedWarningThreshold = 30 // 30 days
configDefaultHTTPTimeout = time.Second * 15
configDefaultWebsocketResponseCheckTimeout = time.Millisecond * 30
configDefaultWebsocketResponseMaxLimit = time.Second * 7
configMaxAuthFailres = 3
defaultNTPAllowedDifference = 50000000
defaultNTPAllowedNegativeDifference = 50000000
)
// Constants here hold some messages
@@ -157,6 +159,8 @@ type ExchangeConfig struct {
UseSandbox bool `json:"useSandbox"`
RESTPollingDelay time.Duration `json:"restPollingDelay"`
HTTPTimeout time.Duration `json:"httpTimeout"`
WebsocketResponseCheckTimeout time.Duration `json:"websocketResponseCheckTimeout"`
WebsocketResponseMaxLimit time.Duration `json:"websocketResponseMaxLimit"`
HTTPUserAgent string `json:"httpUserAgent"`
HTTPDebugging bool `json:"httpDebugging"`
AuthenticatedAPISupport bool `json:"authenticatedApiSupport"`
@@ -824,6 +828,16 @@ func (c *Config) CheckExchangeConfigValues() error {
c.Exchanges[i].HTTPTimeout = configDefaultHTTPTimeout
}
if c.Exchanges[i].WebsocketResponseCheckTimeout <= 0 {
log.Warnf("Exchange %s Websocket response check timeout value not set, defaulting to %v.", c.Exchanges[i].Name, configDefaultWebsocketResponseCheckTimeout)
c.Exchanges[i].WebsocketResponseCheckTimeout = configDefaultWebsocketResponseCheckTimeout
}
if c.Exchanges[i].WebsocketResponseMaxLimit <= 0 {
log.Warnf("Exchange %s Websocket response max limit value not set, defaulting to %v.", c.Exchanges[i].Name, configDefaultWebsocketResponseMaxLimit)
c.Exchanges[i].WebsocketResponseMaxLimit = configDefaultWebsocketResponseMaxLimit
}
err := c.CheckPairConsistency(c.Exchanges[i].Name)
if err != nil {
log.Errorf("Exchange %s: CheckPairConsistency error: %s", c.Exchanges[i].Name, err)

View File

@@ -650,7 +650,6 @@ func TestUpdateExchangeConfig(t *testing.T) {
// TestCheckExchangeConfigValues logic test
func TestCheckExchangeConfigValues(t *testing.T) {
checkExchangeConfigValues := Config{}
err := checkExchangeConfigValues.LoadConfig(ConfigTestFile)
if err != nil {
t.Errorf(
@@ -665,12 +664,28 @@ func TestCheckExchangeConfigValues(t *testing.T) {
)
}
checkExchangeConfigValues.Exchanges[0].WebsocketResponseMaxLimit = 0
checkExchangeConfigValues.Exchanges[0].WebsocketResponseCheckTimeout = 0
checkExchangeConfigValues.Exchanges[0].HTTPTimeout = 0
checkExchangeConfigValues.CheckExchangeConfigValues()
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err != nil {
t.Errorf("Test failed. checkExchangeConfigValues.CheckExchangeConfigValues: %s",
err.Error(),
)
}
if checkExchangeConfigValues.Exchanges[0].HTTPTimeout == 0 {
t.Fatalf("Test failed. Expected exchange %s to have updated HTTPTimeout value", checkExchangeConfigValues.Exchanges[0].Name)
}
if checkExchangeConfigValues.Exchanges[0].WebsocketResponseMaxLimit == 0 {
t.Fatalf("Test failed. Expected exchange %s to have updated WebsocketResponseMaxLimit value", checkExchangeConfigValues.Exchanges[0].Name)
}
if checkExchangeConfigValues.Exchanges[0].WebsocketResponseCheckTimeout == 0 {
t.Fatalf("Test failed. Expected exchange %s to have updated WebsocketResponseCheckTimeout value", checkExchangeConfigValues.Exchanges[0].Name)
}
checkExchangeConfigValues.Exchanges[0].APIKey = "Key"
checkExchangeConfigValues.Exchanges[0].APISecret = "Secret"
checkExchangeConfigValues.Exchanges[0].AuthenticatedAPISupport = true

View File

@@ -168,6 +168,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -209,6 +211,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -250,6 +254,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -291,6 +297,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -334,6 +342,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -376,6 +386,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -387,7 +399,7 @@
"apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"proxyAddress": "",
"websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API",
"availablePairs": "XRPM19,BCHM19,ADAM19,EOSM19,TRXM19,XBTUSD,XBT7D_U105,XBT7D_D95,XBTM19,XBTU19,ETHUSD,ETHM19,LTCM19",
"availablePairs": "XRPU19,BCHU19,ADAU19,ADAU19,TRXU19,XBTUSD,XBT7D_U105,XBT7D_D95,XBTU19,XBTZ19,ETHUSD,ETHU19,LTCU19",
"enabledPairs": "XBTUSD",
"baseCurrencies": "USD",
"assetTypes": "SPOT",
@@ -417,6 +429,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -458,6 +472,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -500,6 +516,8 @@
"websocket": true,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -542,6 +560,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -583,6 +603,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -624,6 +646,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -667,6 +691,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -710,6 +736,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -753,6 +781,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -794,6 +824,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -836,6 +868,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -879,6 +913,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -922,6 +958,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -964,6 +1002,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -1006,6 +1046,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -1046,6 +1088,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -1086,6 +1130,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -1129,6 +1175,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -1172,6 +1220,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -1215,6 +1265,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
@@ -1259,6 +1311,8 @@
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,

View File

@@ -15,9 +15,9 @@ const (
// WebsocketClient starts a new webstocket connection
func (a *Alphapoint) WebsocketClient() {
for a.Enabled {
var Dialer websocket.Dialer
var dialer websocket.Dialer
var err error
a.WebsocketConn, _, err = Dialer.Dial(a.WebsocketURL, http.Header{})
a.WebsocketConn, _, err = dialer.Dial(a.WebsocketURL, http.Header{})
if err != nil {
log.Errorf("%s Unable to connect to Websocket. Error: %s\n", a.Name, err)

View File

@@ -11,6 +11,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"
)
// GetAccountInfo retrieves balances for all enabled currencies on the
@@ -224,7 +225,7 @@ func (a *Alphapoint) WithdrawFiatFundsToInternationalBank(withdrawRequest *excha
}
// GetWebsocket returns a pointer to the exchange websocket
func (a *Alphapoint) GetWebsocket() (*exchange.Websocket, error) {
func (a *Alphapoint) GetWebsocket() (*wshandler.Websocket, error) {
return nil, common.ErrNotYetImplemented
}
@@ -320,18 +321,18 @@ func (a *Alphapoint) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (a *Alphapoint) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (a *Alphapoint) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (a *Alphapoint) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (a *Alphapoint) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (a *Alphapoint) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (a *Alphapoint) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -14,6 +14,7 @@ import (
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"
)
@@ -72,7 +73,7 @@ func (a *ANX) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
a.APIUrlDefault = anxAPIURL
a.APIUrl = a.APIUrlDefault
a.WebsocketInit()
a.Websocket = wshandler.New()
}
// Setup is run on startup to setup exchange with config values

View File

@@ -12,6 +12,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"
)
@@ -361,7 +362,7 @@ func (a *ANX) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.Wit
}
// GetWebsocket returns a pointer to the exchange websocket
func (a *ANX) GetWebsocket() (*exchange.Websocket, error) {
func (a *ANX) GetWebsocket() (*wshandler.Websocket, error) {
return nil, common.ErrFunctionNotSupported
}
@@ -448,18 +449,18 @@ func (a *ANX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]ex
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (a *ANX) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (a *ANX) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (a *ANX) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (a *ANX) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (a *ANX) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (a *ANX) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -10,20 +10,20 @@ import (
"strconv"
"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"
)
// Binance is the overarching type across the Bithumb package
type Binance struct {
exchange.Base
WebsocketConn *websocket.Conn
WebsocketConn *wshandler.WebsocketConnection
// Valid string list that is required by the exchange
validLimits []int
@@ -93,12 +93,14 @@ func (b *Binance) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
b.APIUrlDefault = apiURL
b.APIUrl = b.APIUrlDefault
b.WebsocketInit()
b.Websocket = wshandler.New()
b.WebsocketURL = binanceDefaultWebsocketURL
b.Websocket.Functionality = exchange.WebsocketTradeDataSupported |
exchange.WebsocketTickerSupported |
exchange.WebsocketKlineSupported |
exchange.WebsocketOrderbookSupported
b.Websocket.Functionality = wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketTickerSupported |
wshandler.WebsocketKlineSupported |
wshandler.WebsocketOrderbookSupported
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup takes in the supplied exchange configuration details and sets params
@@ -138,17 +140,27 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = b.WebsocketSetup(b.WSConnect,
err = b.Websocket.Setup(b.WSConnect,
nil,
nil,
exch.Name,
exch.Websocket,
exch.Verbose,
binanceDefaultWebsocketURL,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
b.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: b.Name,
URL: b.Websocket.GetWebsocketURL(),
ProxyURL: b.Websocket.GetProxyAddress(),
Verbose: b.Verbose,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
@@ -16,6 +15,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"
)
const (
@@ -70,7 +70,7 @@ func (b *Binance) UpdateLocalCache(ob *WebsocketDepthStream) error {
ID, ok := lastUpdateID[ob.Pair]
if !ok {
m.Unlock()
return errors.New("binance_websocket.go - Unable to find lastUpdateID")
return fmt.Errorf("%v - Unable to find lastUpdateID", b.Name)
}
if ob.LastUpdateID+1 <= ID || ID >= ob.LastUpdateID+1 {
@@ -124,10 +124,10 @@ func (b *Binance) UpdateLocalCache(ob *WebsocketDepthStream) error {
// WSConnect intiates a websocket connection
func (b *Binance) WSConnect() error {
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var Dialer websocket.Dialer
var dialer websocket.Dialer
var err error
tick := strings.ToLower(
@@ -152,18 +152,6 @@ func (b *Binance) WSConnect() error {
kline +
"/" +
depth
if b.Websocket.GetProxyAddress() != "" {
var u *url.URL
u, err = url.Parse(b.Websocket.GetProxyAddress())
if err != nil {
return fmt.Errorf("binance_websocket.go - Unable to connect to parse proxy address. Error: %s",
err)
}
Dialer.Proxy = http.ProxyURL(u)
}
for _, ePair := range b.GetEnabledCurrencies() {
err = b.SeedLocalCache(ePair)
if err != nil {
@@ -171,9 +159,11 @@ func (b *Binance) WSConnect() error {
}
}
b.WebsocketConn, _, err = Dialer.Dial(wsurl, http.Header{})
b.WebsocketConn.URL = wsurl
err = b.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return fmt.Errorf("binance_websocket.go - Unable to connect to Websocket. Error: %s",
return fmt.Errorf("%v - Unable to connect to Websocket. Error: %s",
b.Name,
err)
}
@@ -182,18 +172,6 @@ func (b *Binance) WSConnect() error {
return nil
}
// WSReadData reads from the websocket connection and returns the response
func (b *Binance) WSReadData() (exchange.WebsocketResponse, error) {
msgType, resp, err := b.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
b.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{Type: msgType, Raw: resp}, nil
}
// WsHandleData handles websocket data from WsReadData
func (b *Binance) WsHandleData() {
b.Websocket.Wg.Add(1)
@@ -208,134 +186,133 @@ func (b *Binance) WsHandleData() {
return
default:
read, err := b.WSReadData()
read, err := b.WebsocketConn.ReadMessage()
if err != nil {
b.Websocket.DataHandler <- err
return
}
if read.Type == websocket.TextMessage {
multiStreamData := MultiStreamData{}
err = common.JSONDecode(read.Raw, &multiStreamData)
b.Websocket.TrafficAlert <- struct{}{}
var multiStreamData MultiStreamData
err = common.JSONDecode(read.Raw, &multiStreamData)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("%v - Could not load multi stream data: %s",
b.Name,
read.Raw)
continue
}
streamType := strings.Split(multiStreamData.Stream, "@")
switch streamType[1] {
case "trade":
trade := TradeStream{}
err := common.JSONDecode(multiStreamData.Data, &trade)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("binance_websocket.go - Could not load multi stream data: %s",
string(read.Raw))
b.Websocket.DataHandler <- fmt.Errorf("%v - Could not unmarshal trade data: %s",
b.Name,
err)
continue
}
streamType := strings.Split(multiStreamData.Stream, "@")
switch streamType[1] {
case "trade":
trade := TradeStream{}
err := common.JSONDecode(multiStreamData.Data, &trade)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("binance_websocket.go - Could not unmarshal trade data: %s",
err)
continue
}
price, err := strconv.ParseFloat(trade.Price, 64)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("binance_websocket.go - price conversion error: %s",
err)
continue
}
amount, err := strconv.ParseFloat(trade.Quantity, 64)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("binance_websocket.go - amount conversion error: %s",
err)
continue
}
b.Websocket.DataHandler <- exchange.TradeData{
CurrencyPair: currency.NewPairFromString(trade.Symbol),
Timestamp: time.Unix(0, trade.TimeStamp*int64(time.Millisecond)),
Price: price,
Amount: amount,
Exchange: b.GetName(),
AssetType: "SPOT",
Side: trade.EventType,
}
continue
case "ticker":
t := TickerStream{}
err := common.JSONDecode(multiStreamData.Data, &t)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("binance_websocket.go - Could not convert to a TickerStream structure %s",
err.Error())
continue
}
var wsTicker exchange.TickerData
wsTicker.Timestamp = time.Unix(t.EventTime/1000, 0)
wsTicker.Pair = currency.NewPairFromString(t.Symbol)
wsTicker.AssetType = ticker.Spot
wsTicker.Exchange = b.GetName()
wsTicker.ClosePrice, _ = strconv.ParseFloat(t.CurrDayClose, 64)
wsTicker.Quantity, _ = strconv.ParseFloat(t.TotalTradedVolume, 64)
wsTicker.OpenPrice, _ = strconv.ParseFloat(t.OpenPrice, 64)
wsTicker.HighPrice, _ = strconv.ParseFloat(t.HighPrice, 64)
wsTicker.LowPrice, _ = strconv.ParseFloat(t.LowPrice, 64)
b.Websocket.DataHandler <- wsTicker
continue
case "kline":
kline := KlineStream{}
err := common.JSONDecode(multiStreamData.Data, &kline)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("binance_websocket.go - Could not convert to a KlineStream structure %s",
err)
continue
}
var wsKline exchange.KlineData
wsKline.Timestamp = time.Unix(0, kline.EventTime)
wsKline.Pair = currency.NewPairFromString(kline.Symbol)
wsKline.AssetType = ticker.Spot
wsKline.Exchange = b.GetName()
wsKline.StartTime = time.Unix(0, kline.Kline.StartTime)
wsKline.CloseTime = time.Unix(0, kline.Kline.CloseTime)
wsKline.Interval = kline.Kline.Interval
wsKline.OpenPrice, _ = strconv.ParseFloat(kline.Kline.OpenPrice, 64)
wsKline.ClosePrice, _ = strconv.ParseFloat(kline.Kline.ClosePrice, 64)
wsKline.HighPrice, _ = strconv.ParseFloat(kline.Kline.HighPrice, 64)
wsKline.LowPrice, _ = strconv.ParseFloat(kline.Kline.LowPrice, 64)
wsKline.Volume, _ = strconv.ParseFloat(kline.Kline.Volume, 64)
b.Websocket.DataHandler <- wsKline
continue
case "depth":
depth := WebsocketDepthStream{}
err := common.JSONDecode(multiStreamData.Data, &depth)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("binance_websocket.go - Could not convert to depthStream structure %s",
err)
continue
}
err = b.UpdateLocalCache(&depth)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("binance_websocket.go - UpdateLocalCache error: %s",
err)
continue
}
currencyPair := currency.NewPairFromString(depth.Pair)
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
Pair: currencyPair,
Asset: "SPOT",
Exchange: b.GetName(),
}
price, err := strconv.ParseFloat(trade.Price, 64)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("%v - price conversion error: %s",
b.Name,
err)
continue
}
amount, err := strconv.ParseFloat(trade.Quantity, 64)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("%v - amount conversion error: %s",
b.Name,
err)
continue
}
b.Websocket.DataHandler <- wshandler.TradeData{
CurrencyPair: currency.NewPairFromString(trade.Symbol),
Timestamp: time.Unix(0, trade.TimeStamp),
Price: price,
Amount: amount,
Exchange: b.GetName(),
AssetType: "SPOT",
Side: trade.EventType,
}
continue
case "ticker":
t := TickerStream{}
err := common.JSONDecode(multiStreamData.Data, &t)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("%v - Could not convert to a TickerStream structure %s",
b.Name,
err.Error())
continue
}
var wsTicker wshandler.TickerData
wsTicker.Timestamp = time.Unix(t.EventTime/1000, 0)
wsTicker.Pair = currency.NewPairFromString(t.Symbol)
wsTicker.AssetType = ticker.Spot
wsTicker.Exchange = b.GetName()
wsTicker.ClosePrice, _ = strconv.ParseFloat(t.CurrDayClose, 64)
wsTicker.Quantity, _ = strconv.ParseFloat(t.TotalTradedVolume, 64)
wsTicker.OpenPrice, _ = strconv.ParseFloat(t.OpenPrice, 64)
wsTicker.HighPrice, _ = strconv.ParseFloat(t.HighPrice, 64)
wsTicker.LowPrice, _ = strconv.ParseFloat(t.LowPrice, 64)
b.Websocket.DataHandler <- wsTicker
continue
case "kline":
kline := KlineStream{}
err := common.JSONDecode(multiStreamData.Data, &kline)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("%v - Could not convert to a KlineStream structure %s",
b.Name,
err)
continue
}
var wsKline wshandler.KlineData
wsKline.Timestamp = time.Unix(0, kline.EventTime)
wsKline.Pair = currency.NewPairFromString(kline.Symbol)
wsKline.AssetType = ticker.Spot
wsKline.Exchange = b.GetName()
wsKline.StartTime = time.Unix(0, kline.Kline.StartTime)
wsKline.CloseTime = time.Unix(0, kline.Kline.CloseTime)
wsKline.Interval = kline.Kline.Interval
wsKline.OpenPrice, _ = strconv.ParseFloat(kline.Kline.OpenPrice, 64)
wsKline.ClosePrice, _ = strconv.ParseFloat(kline.Kline.ClosePrice, 64)
wsKline.HighPrice, _ = strconv.ParseFloat(kline.Kline.HighPrice, 64)
wsKline.LowPrice, _ = strconv.ParseFloat(kline.Kline.LowPrice, 64)
wsKline.Volume, _ = strconv.ParseFloat(kline.Kline.Volume, 64)
b.Websocket.DataHandler <- wsKline
continue
case "depth":
depth := WebsocketDepthStream{}
err := common.JSONDecode(multiStreamData.Data, &depth)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("%v - Could not convert to depthStream structure %s",
b.Name,
err)
continue
}
err = b.UpdateLocalCache(&depth)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("%v - UpdateLocalCache error: %s",
b.Name,
err)
continue
}
currencyPair := currency.NewPairFromString(depth.Pair)
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: currencyPair,
Asset: "SPOT",
Exchange: b.GetName(),
}
continue
}
}
}

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"
)
@@ -318,7 +319,7 @@ func (b *Binance) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange
}
// GetWebsocket returns a pointer to the exchange websocket
func (b *Binance) GetWebsocket() (*exchange.Websocket, error) {
func (b *Binance) GetWebsocket() (*wshandler.Websocket, error) {
return b.Websocket, nil
}
@@ -416,18 +417,18 @@ func (b *Binance) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) (
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (b *Binance) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Binance) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (b *Binance) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Binance) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (b *Binance) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (b *Binance) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return b.Websocket.GetSubscriptions(), nil
}

View File

@@ -6,16 +6,15 @@ import (
"net/http"
"net/url"
"strconv"
"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"
)
@@ -86,9 +85,8 @@ const (
// depending on some factors (e.g. servers load, endpoint, etc.).
type Bitfinex struct {
exchange.Base
WebsocketConn *websocket.Conn
WebsocketConn *wshandler.WebsocketConnection
WebsocketSubdChannels map[int]WebsocketChanInfo
wsRequestMtx sync.Mutex
}
// SetDefaults sets the basic defaults for bitfinex
@@ -113,13 +111,15 @@ func (b *Bitfinex) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
b.APIUrlDefault = bitfinexAPIURLBase
b.APIUrl = b.APIUrlDefault
b.WebsocketInit()
b.Websocket.Functionality = exchange.WebsocketTickerSupported |
exchange.WebsocketTradeDataSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported
b.Websocket = wshandler.New()
b.Websocket.Functionality = wshandler.WebsocketTickerSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup takes in the supplied exchange configuration details and sets params
@@ -160,17 +160,28 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = b.WebsocketSetup(b.WsConnect,
err = b.Websocket.Setup(b.WsConnect,
b.Subscribe,
b.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
bitfinexWebsocket,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
b.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: b.Name,
URL: b.Websocket.GetWebsocketURL(),
ProxyURL: b.Websocket.GetProxyAddress(),
Verbose: b.Verbose,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
}

View File

@@ -13,6 +13,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 keys here to do better tests
@@ -961,19 +962,23 @@ func TestWsAuth(t *testing.T) {
b.SetDefaults()
TestSetup(t)
if !b.Websocket.IsEnabled() && !b.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
b.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: b.Name,
URL: b.Websocket.GetWebsocketURL(),
Verbose: b.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var err error
var dialer websocket.Dialer
b.WebsocketConn, _, err = dialer.Dial(b.Websocket.GetWebsocketURL(),
http.Header{})
err := b.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
go b.WsDataHandler()
defer b.WebsocketConn.Close()
err = b.WsSendAuth()
if err != nil {
t.Error(err)

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"reflect"
"strconv"
"time"
@@ -14,6 +13,7 @@ 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"
)
@@ -59,21 +59,7 @@ func (b *Bitfinex) WsPingHandler() error {
req := make(map[string]string)
req["event"] = "ping"
return b.wsSend(req)
}
// WsSend sends data to the websocket server
func (b *Bitfinex) wsSend(data interface{}) error {
b.wsRequestMtx.Lock()
defer b.wsRequestMtx.Unlock()
json, err := common.JSONEncode(data)
if err != nil {
return err
}
if b.Verbose {
log.Debugf("%v sending message to websocket %v", b.Name, data)
}
return b.WebsocketConn.WriteMessage(websocket.TextMessage, json)
return b.WebsocketConn.SendMessage(req)
}
// WsSendAuth sends a autheticated event payload
@@ -94,7 +80,7 @@ func (b *Bitfinex) WsSendAuth() error {
req["authPayload"] = payload
err := b.wsSend(req)
err := b.WebsocketConn.SendMessage(req)
if err != nil {
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
return err
@@ -107,7 +93,7 @@ func (b *Bitfinex) WsSendUnauth() error {
req := make(map[string]string)
req["event"] = "unauth"
return b.wsSend(req)
return b.WebsocketConn.SendMessage(req)
}
// WsAddSubscriptionChannel adds a new subscription channel to the
@@ -128,33 +114,28 @@ func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) {
// WsConnect starts a new websocket connection
func (b *Bitfinex) WsConnect() error {
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var Dialer websocket.Dialer
var err error
if b.Websocket.GetProxyAddress() != "" {
var proxy *url.URL
proxy, err = url.Parse(b.Websocket.GetProxyAddress())
if err != nil {
return err
}
Dialer.Proxy = http.ProxyURL(proxy)
var dialer websocket.Dialer
b.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: b.Name,
URL: b.Websocket.GetWebsocketURL(),
ProxyURL: b.Websocket.GetProxyAddress(),
Verbose: b.Verbose,
}
b.WebsocketConn, _, err = Dialer.Dial(b.Websocket.GetWebsocketURL(), http.Header{})
err := b.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return fmt.Errorf("%v unable to connect to Websocket. Error: %s", b.Name, err)
}
_, resp, err := b.WebsocketConn.ReadMessage()
resp, err := b.WebsocketConn.ReadMessage()
if err != nil {
return fmt.Errorf("%v unable to read from Websocket. Error: %s", b.Name, err)
}
b.Websocket.TrafficAlert <- struct{}{}
var hs WebsocketHandshake
err = common.JSONDecode(resp, &hs)
err = common.JSONDecode(resp.Raw, &hs)
if err != nil {
return err
}
@@ -178,22 +159,6 @@ func (b *Bitfinex) WsConnect() error {
return nil
}
// WsReadData reads and handles websocket stream data
func (b *Bitfinex) WsReadData() (exchange.WebsocketResponse, error) {
msgType, resp, err := b.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
b.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{
Type: msgType,
Raw: resp,
}, nil
}
// WsDataHandler handles data from WsReadData
func (b *Bitfinex) WsDataHandler() {
b.Websocket.Wg.Add(1)
@@ -208,11 +173,12 @@ func (b *Bitfinex) WsDataHandler() {
return
default:
stream, err := b.WsReadData()
stream, err := b.WebsocketConn.ReadMessage()
if err != nil {
b.Websocket.DataHandler <- err
return
}
b.Websocket.TrafficAlert <- struct{}{}
if stream.Type == websocket.TextMessage {
var result interface{}
@@ -221,9 +187,6 @@ func (b *Bitfinex) WsDataHandler() {
case "map[string]interface {}":
eventData := result.(map[string]interface{})
event := eventData["event"]
if b.Verbose {
log.Debugf("%v Received message. Type '%v' Message: %v", b.Name, event, eventData)
}
switch event {
case "subscribed":
b.WsAddSubscriptionChannel(int(eventData["chanId"].(float64)),
@@ -306,7 +269,7 @@ func (b *Bitfinex) WsDataHandler() {
}
case "ticker":
b.Websocket.DataHandler <- exchange.TickerData{
b.Websocket.DataHandler <- wshandler.TickerData{
Quantity: chanData[8].(float64),
ClosePrice: chanData[7].(float64),
HighPrice: chanData[9].(float64),
@@ -476,7 +439,7 @@ func (b *Bitfinex) WsDataHandler() {
newAmount *= -1
}
b.Websocket.DataHandler <- exchange.TradeData{
b.Websocket.DataHandler <- wshandler.TradeData{
CurrencyPair: currency.NewPairFromString(chanInfo.Pair),
Timestamp: time.Unix(trades[0].Timestamp, 0),
Price: trades[0].Price,
@@ -524,7 +487,7 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType string, books []W
return fmt.Errorf("bitfinex.go error - %s", err)
}
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: p,
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
Asset: assetType,
Exchange: b.GetName()}
return nil
@@ -549,7 +512,7 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType string, book Web
return err
}
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: p,
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
Asset: assetType,
Exchange: b.GetName()}
@@ -569,7 +532,7 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType string, book Web
return err
}
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: p,
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
Asset: assetType,
Exchange: b.GetName()}
@@ -590,7 +553,7 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType string, book Web
return err
}
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: p,
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
Asset: assetType,
Exchange: b.GetName()}
@@ -610,7 +573,7 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType string, book Web
return err
}
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: p,
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
Asset: assetType,
Exchange: b.GetName()}
@@ -620,14 +583,14 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType string, book Web
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (b *Bitfinex) GenerateDefaultSubscriptions() {
var channels = []string{"book", "trades", "ticker"}
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
for i := range channels {
for j := range b.EnabledPairs {
params := make(map[string]interface{})
if channels[i] == "book" {
params["prec"] = "P0"
}
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channels[i],
Currency: b.EnabledPairs[j],
Params: params,
@@ -638,7 +601,7 @@ func (b *Bitfinex) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (b *Bitfinex) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (b *Bitfinex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
req := make(map[string]interface{})
req["event"] = "subscribe"
req["channel"] = channelToSubscribe.Channel
@@ -650,11 +613,11 @@ func (b *Bitfinex) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscri
req[k] = v
}
}
return b.wsSend(req)
return b.WebsocketConn.SendMessage(req)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (b *Bitfinex) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (b *Bitfinex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
req := make(map[string]interface{})
req["event"] = "unsubscribe"
req["channel"] = channelToSubscribe.Channel
@@ -664,5 +627,5 @@ func (b *Bitfinex) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubsc
req[k] = v
}
}
return b.wsSend(req)
return b.WebsocketConn.SendMessage(req)
}

View File

@@ -14,6 +14,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"
)
@@ -323,7 +324,7 @@ func (b *Bitfinex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchang
}
// GetWebsocket returns a pointer to the exchange websocket
func (b *Bitfinex) GetWebsocket() (*exchange.Websocket, error) {
func (b *Bitfinex) GetWebsocket() (*wshandler.Websocket, error) {
return b.Websocket, nil
}
@@ -457,20 +458,20 @@ func (b *Bitfinex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest)
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (b *Bitfinex) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Bitfinex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
b.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (b *Bitfinex) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
b.Websocket.UnsubscribeToChannels(channels)
func (b *Bitfinex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
b.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (b *Bitfinex) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (b *Bitfinex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return b.Websocket.GetSubscriptions(), nil
}

View File

@@ -14,6 +14,7 @@ import (
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"
)
@@ -99,7 +100,7 @@ func (b *Bitflyer) SetDefaults() {
b.APIUrl = b.APIUrlDefault
b.APIUrlSecondaryDefault = chainAnalysis
b.APIUrlSecondary = b.APIUrlSecondaryDefault
b.WebsocketInit()
b.Websocket = wshandler.New()
}
// Setup takes in the supplied exchange configuration details and sets params

View File

@@ -9,6 +9,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"
)
@@ -219,7 +220,7 @@ func (b *Bitflyer) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchang
}
// GetWebsocket returns a pointer to the exchange websocket
func (b *Bitflyer) GetWebsocket() (*exchange.Websocket, error) {
func (b *Bitflyer) GetWebsocket() (*wshandler.Websocket, error) {
return nil, common.ErrNotYetImplemented
}
@@ -245,18 +246,18 @@ func (b *Bitflyer) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (b *Bitflyer) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Bitflyer) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (b *Bitflyer) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Bitflyer) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (b *Bitflyer) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (b *Bitflyer) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -17,6 +17,7 @@ import (
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"
)
@@ -81,7 +82,7 @@ func (b *Bithumb) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
b.APIUrlDefault = apiURL
b.APIUrl = b.APIUrlDefault
b.WebsocketInit()
b.Websocket = wshandler.New()
}
// Setup takes in the supplied exchange configuration details and sets params

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"
)
@@ -320,7 +321,7 @@ func (b *Bithumb) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange
}
// GetWebsocket returns a pointer to the exchange websocket
func (b *Bithumb) GetWebsocket() (*exchange.Websocket, error) {
func (b *Bithumb) GetWebsocket() (*wshandler.Websocket, error) {
return nil, common.ErrFunctionNotSupported
}
@@ -423,18 +424,18 @@ func (b *Bithumb) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) (
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (b *Bithumb) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Bithumb) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (b *Bithumb) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Bithumb) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (b *Bithumb) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (b *Bithumb) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -7,24 +7,22 @@ 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"
)
// Bitmex is the overarching type across this package
type Bitmex struct {
exchange.Base
WebsocketConn *websocket.Conn
wsRequestMtx sync.Mutex
WebsocketConn *wshandler.WebsocketConnection
}
const (
@@ -135,13 +133,16 @@ func (b *Bitmex) SetDefaults() {
b.APIUrlDefault = bitmexAPIURL
b.APIUrl = b.APIUrlDefault
b.SupportsAutoPairUpdating = true
b.WebsocketInit()
b.Websocket.Functionality = exchange.WebsocketTradeDataSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported |
exchange.WebsocketAccountDataSupported
b.Websocket = wshandler.New()
b.Websocket.Functionality = wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported |
wshandler.WebsocketAccountDataSupported |
wshandler.WebsocketDeadMansSwitchSupported
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup takes in the supplied exchange configuration details and sets params
@@ -180,17 +181,26 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = b.WebsocketSetup(b.WsConnector,
err = b.Websocket.Setup(b.WsConnector,
b.Subscribe,
b.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
bitmexWSURL,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
b.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: b.Name,
URL: b.Websocket.GetWebsocketURL(),
ProxyURL: b.Websocket.GetProxyAddress(),
Verbose: b.Verbose,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -12,6 +12,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 keys here for due diligence testing
@@ -689,22 +690,26 @@ func TestWsAuth(t *testing.T) {
b.SetDefaults()
TestSetup(t)
if !b.Websocket.IsEnabled() && !b.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
b.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: b.Name,
URL: b.Websocket.GetWebsocketURL(),
Verbose: b.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var err error
var dialer websocket.Dialer
b.WebsocketConn, _, err = dialer.Dial(b.Websocket.GetWebsocketURL(),
http.Header{})
err := b.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
go b.wsHandleIncomingData()
defer b.WebsocketConn.Close()
err = b.websocketSendAuth()
if err != nil {
t.Error(err)
t.Fatal(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
@@ -14,6 +13,7 @@ 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"
)
@@ -66,34 +66,21 @@ var (
// WsConnector initiates a new websocket connection
func (b *Bitmex) WsConnector() error {
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
var err error
if b.Websocket.GetProxyAddress() != "" {
var proxy *url.URL
proxy, err = url.Parse(b.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
b.WebsocketConn, _, err = dialer.Dial(b.Websocket.GetWebsocketURL(), nil)
err := b.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
_, p, err := b.WebsocketConn.ReadMessage()
p, err := b.WebsocketConn.ReadMessage()
if err != nil {
return err
}
b.Websocket.TrafficAlert <- struct{}{}
var welcomeResp WebsocketWelcome
err = common.JSONDecode(p, &welcomeResp)
err = common.JSONDecode(p.Raw, &welcomeResp)
if err != nil {
return err
}
@@ -116,19 +103,6 @@ func (b *Bitmex) WsConnector() error {
return nil
}
func (b *Bitmex) wsReadData() (exchange.WebsocketResponse, error) {
_, resp, err := b.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
b.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{
Raw: resp,
}, nil
}
// wsHandleIncomingData services incoming data from the websocket connection
func (b *Bitmex) wsHandleIncomingData() {
b.Websocket.Wg.Add(1)
@@ -143,12 +117,12 @@ func (b *Bitmex) wsHandleIncomingData() {
return
default:
resp, err := b.wsReadData()
resp, err := b.WebsocketConn.ReadMessage()
if err != nil {
b.Websocket.DataHandler <- err
return
}
b.Websocket.TrafficAlert <- struct{}{}
message := string(resp.Raw)
if common.StringContains(message, "pong") {
pongChan <- 1
@@ -156,7 +130,7 @@ func (b *Bitmex) wsHandleIncomingData() {
}
if common.StringContains(message, "ping") {
err = b.wsSend("pong")
err = b.WebsocketConn.SendMessage("pong")
if err != nil {
b.Websocket.DataHandler <- err
continue
@@ -255,7 +229,7 @@ func (b *Bitmex) wsHandleIncomingData() {
}
// TODO: update this to support multiple asset types
b.Websocket.DataHandler <- exchange.TradeData{
b.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: timestamp,
Price: trade.Price,
Amount: float64(trade.Size),
@@ -405,7 +379,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai
err)
}
snapshotloaded[currencyPair][assetType] = true
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: currencyPair,
Asset: assetType,
Exchange: b.GetName(),
@@ -440,7 +414,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai
return err
}
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: currencyPair,
Asset: assetType,
Exchange: b.GetName(),
@@ -454,7 +428,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai
func (b *Bitmex) GenerateDefaultSubscriptions() {
contracts := b.GetEnabledCurrencies()
channels := []string{bitmexWSOrderbookL2, bitmexWSTrade}
subscriptions := []exchange.WebsocketChannelSubscription{
subscriptions := []wshandler.WebsocketChannelSubscription{
{
Channel: bitmexWSAnnouncement,
},
@@ -462,7 +436,7 @@ func (b *Bitmex) GenerateDefaultSubscriptions() {
for i := range channels {
for j := range contracts {
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: fmt.Sprintf("%v:%v", channels[i], contracts[j].String()),
Currency: contracts[j],
})
@@ -480,7 +454,7 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() {
channels := []string{bitmexWSExecution,
bitmexWSPosition,
}
subscriptions := []exchange.WebsocketChannelSubscription{
subscriptions := []wshandler.WebsocketChannelSubscription{
{
Channel: bitmexWSAffiliate,
},
@@ -502,7 +476,7 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() {
}
for i := range channels {
for j := range contracts {
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: fmt.Sprintf("%v:%v", channels[i], contracts[j].String()),
Currency: contracts[j],
})
@@ -512,21 +486,21 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() {
}
// Subscribe subscribes to a websocket channel
func (b *Bitmex) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (b *Bitmex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
var subscriber WebsocketRequest
subscriber.Command = "subscribe"
subscriber.Arguments = append(subscriber.Arguments, channelToSubscribe.Channel)
return b.wsSend(subscriber)
return b.WebsocketConn.SendMessage(subscriber)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (b *Bitmex) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (b *Bitmex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
var subscriber WebsocketRequest
subscriber.Command = "unsubscribe"
subscriber.Arguments = append(subscriber.Arguments,
channelToSubscribe.Params["args"],
channelToSubscribe.Channel+":"+channelToSubscribe.Currency.String())
return b.wsSend(subscriber)
return b.WebsocketConn.SendMessage(subscriber)
}
// WebsocketSendAuth sends an authenticated subscription
@@ -545,20 +519,10 @@ func (b *Bitmex) websocketSendAuth() error {
sendAuth.Command = "authKeyExpires"
sendAuth.Arguments = append(sendAuth.Arguments, b.APIKey, timestamp,
signature)
err := b.wsSend(sendAuth)
err := b.WebsocketConn.SendMessage(sendAuth)
if err != nil {
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
return err
}
return nil
}
// WsSend sends data to the websocket server
func (b *Bitmex) wsSend(data interface{}) error {
b.wsRequestMtx.Lock()
defer b.wsRequestMtx.Unlock()
if b.Verbose {
log.Debugf("%v sending message to websocket %v", b.Name, data)
}
return b.WebsocketConn.WriteJSON(data)
}

View File

@@ -12,6 +12,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"
)
@@ -300,7 +301,7 @@ func (b *Bitmex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.
}
// GetWebsocket returns a pointer to the exchange websocket
func (b *Bitmex) GetWebsocket() (*exchange.Websocket, error) {
func (b *Bitmex) GetWebsocket() (*wshandler.Websocket, error) {
return b.Websocket, nil
}
@@ -401,20 +402,20 @@ func (b *Bitmex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (b *Bitmex) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Bitmex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
b.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (b *Bitmex) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
b.Websocket.UnsubscribeToChannels(channels)
func (b *Bitmex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
b.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (b *Bitmex) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (b *Bitmex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return b.Websocket.GetSubscriptions(), nil
}

View File

@@ -9,16 +9,15 @@ import (
"reflect"
"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"
)
@@ -64,8 +63,7 @@ const (
type Bitstamp struct {
exchange.Base
Balance Balances
WebsocketConn *websocket.Conn
wsRequestMtx sync.Mutex
WebsocketConn *wshandler.WebsocketConnection
}
// SetDefaults sets default for Bitstamp
@@ -89,11 +87,13 @@ func (b *Bitstamp) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
b.APIUrlDefault = bitstampAPIURL
b.APIUrl = b.APIUrlDefault
b.WebsocketInit()
b.Websocket.Functionality = exchange.WebsocketOrderbookSupported |
exchange.WebsocketTradeDataSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported
b.Websocket = wshandler.New()
b.Websocket.Functionality = wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup sets configuration values to bitstamp
@@ -138,17 +138,26 @@ func (b *Bitstamp) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = b.WebsocketSetup(b.WsConnect,
err = b.Websocket.Setup(b.WsConnect,
b.Subscribe,
b.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
bitstampWSURL,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
b.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: b.Name,
URL: b.Websocket.GetWebsocketURL(),
ProxyURL: b.Websocket.GetProxyAddress(),
Verbose: b.Verbose,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -4,16 +4,15 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency"
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"
)
@@ -24,26 +23,13 @@ const (
// WsConnect connects to a websocket feed
func (b *Bitstamp) WsConnect() error {
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if b.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(b.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
b.WebsocketConn, _, err = dialer.Dial(b.Websocket.GetWebsocketURL(), http.Header{})
err := b.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return fmt.Errorf("%s Unable to connect to Websocket. Error: %s",
b.Name,
err)
return err
}
if b.Verbose {
log.Debugf("%s Connected to Websocket.\n", b.GetName())
}
@@ -59,22 +45,6 @@ func (b *Bitstamp) WsConnect() error {
return nil
}
// WsReadData reads data coming from bitstamp websocket connection
func (b *Bitstamp) WsReadData() (exchange.WebsocketResponse, error) {
msgType, resp, err := b.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
if b.Verbose {
log.Debugf("%s websocket raw response: %s", b.GetName(), resp)
}
b.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{Type: msgType, Raw: resp}, nil
}
// WsHandleData handles websocket data from WsReadData
func (b *Bitstamp) WsHandleData() {
b.Websocket.Wg.Add(1)
@@ -89,12 +59,12 @@ func (b *Bitstamp) WsHandleData() {
return
default:
resp, err := b.WsReadData()
resp, err := b.WebsocketConn.ReadMessage()
if err != nil {
b.Websocket.DataHandler <- err
return
}
b.Websocket.TrafficAlert <- struct{}{}
wsResponse := websocketResponse{}
err = common.JSONDecode(resp.Raw, &wsResponse)
if err != nil {
@@ -138,7 +108,7 @@ func (b *Bitstamp) WsHandleData() {
currencyPair := common.SplitStrings(wsResponse.Channel, "_")
p := currency.NewPairFromString(common.StringToUpper(currencyPair[2]))
b.Websocket.DataHandler <- exchange.TradeData{
b.Websocket.DataHandler <- wshandler.TradeData{
Price: wsTradeTemp.Data.Price,
Amount: wsTradeTemp.Data.Amount,
CurrencyPair: p,
@@ -153,10 +123,10 @@ func (b *Bitstamp) WsHandleData() {
func (b *Bitstamp) generateDefaultSubscriptions() {
var channels = []string{"live_trades_", "diff_order_book_"}
enabledCurrencies := b.GetEnabledCurrencies()
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
for i := range channels {
for j := range enabledCurrencies {
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: fmt.Sprintf("%v%v", channels[i], enabledCurrencies[j].Lower().String()),
})
}
@@ -165,31 +135,25 @@ func (b *Bitstamp) generateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (b *Bitstamp) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
b.wsRequestMtx.Lock()
defer b.wsRequestMtx.Unlock()
func (b *Bitstamp) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
req := websocketEventRequest{
Event: "bts:subscribe",
Data: websocketData{
Channel: channelToSubscribe.Channel,
},
}
return b.WebsocketConn.WriteJSON(req)
return b.WebsocketConn.SendMessage(req)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (b *Bitstamp) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
b.wsRequestMtx.Lock()
defer b.wsRequestMtx.Unlock()
func (b *Bitstamp) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
req := websocketEventRequest{
Event: "bts:unsubscribe",
Data: websocketData{
Channel: channelToSubscribe.Channel,
},
}
return b.WebsocketConn.WriteJSON(req)
return b.WebsocketConn.SendMessage(req)
}
func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, assetType string) error {
@@ -240,7 +204,7 @@ func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, ass
return err
}
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: p,
Asset: assetType,
Exchange: b.GetName(),
@@ -284,7 +248,7 @@ func (b *Bitstamp) seedOrderBook() error {
return err
}
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: p[x],
Asset: ticker.Spot,
Exchange: b.GetName(),

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"
)
@@ -305,7 +306,7 @@ func (b *Bitstamp) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchang
}
// GetWebsocket returns a pointer to the exchange websocket
func (b *Bitstamp) GetWebsocket() (*exchange.Websocket, error) {
func (b *Bitstamp) GetWebsocket() (*wshandler.Websocket, error) {
return b.Websocket, nil
}
@@ -405,20 +406,20 @@ func (b *Bitstamp) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest)
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (b *Bitstamp) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Bitstamp) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
b.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (b *Bitstamp) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
b.Websocket.UnsubscribeToChannels(channels)
func (b *Bitstamp) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
b.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (b *Bitstamp) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (b *Bitstamp) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return b.Websocket.GetSubscriptions(), nil
}

View File

@@ -14,6 +14,7 @@ import (
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"
)
@@ -85,7 +86,7 @@ func (b *Bittrex) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
b.APIUrlDefault = bittrexAPIURL
b.APIUrl = b.APIUrlDefault
b.WebsocketInit()
b.Websocket = wshandler.New()
}
// Setup method sets current configuration details if enabled

View File

@@ -12,6 +12,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"
)
@@ -294,7 +295,7 @@ func (b *Bittrex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange
}
// GetWebsocket returns a pointer to the exchange websocket
func (b *Bittrex) GetWebsocket() (*exchange.Websocket, error) {
func (b *Bittrex) GetWebsocket() (*wshandler.Websocket, error) {
return nil, common.ErrNotYetImplemented
}
@@ -400,18 +401,18 @@ func (b *Bittrex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) (
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (b *Bittrex) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Bittrex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (b *Bittrex) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *Bittrex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (b *Bittrex) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (b *Bittrex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -14,6 +14,7 @@ import (
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"
)
@@ -74,7 +75,7 @@ func (b *BTCMarkets) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
b.APIUrlDefault = btcMarketsAPIURL
b.APIUrl = b.APIUrlDefault
b.WebsocketInit()
b.Websocket = wshandler.New()
}
// Setup takes in an exchange configuration and sets all parameters

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"
)
@@ -322,7 +323,7 @@ func (b *BTCMarkets) WithdrawFiatFundsToInternationalBank(withdrawRequest *excha
}
// GetWebsocket returns a pointer to the exchange websocket
func (b *BTCMarkets) GetWebsocket() (*exchange.Websocket, error) {
func (b *BTCMarkets) GetWebsocket() (*wshandler.Websocket, error) {
return nil, common.ErrNotYetImplemented
}
@@ -463,18 +464,18 @@ func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (b *BTCMarkets) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *BTCMarkets) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (b *BTCMarkets) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *BTCMarkets) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (b *BTCMarkets) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (b *BTCMarkets) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -6,24 +6,22 @@ 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"
)
// BTSE is the overarching type across this package
type BTSE struct {
exchange.Base
WebsocketConn *websocket.Conn
wsRequestMtx sync.Mutex
WebsocketConn *wshandler.WebsocketConnection
}
const (
@@ -66,11 +64,13 @@ func (b *BTSE) SetDefaults() {
b.APIUrl = b.APIUrlDefault
b.SupportsAutoPairUpdating = true
b.SupportsRESTTickerBatching = false
b.WebsocketInit()
b.Websocket.Functionality = exchange.WebsocketOrderbookSupported |
exchange.WebsocketTickerSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported
b.Websocket = wshandler.New()
b.Websocket.Functionality = wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketTickerSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup takes in the supplied exchange configuration details and sets params
@@ -109,17 +109,26 @@ func (b *BTSE) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = b.WebsocketSetup(b.WsConnect,
err = b.Websocket.Setup(b.WsConnect,
b.Subscribe,
b.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
btseWebsocket,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
b.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: b.Name,
URL: b.Websocket.GetWebsocketURL(),
ProxyURL: b.Websocket.GetProxyAddress(),
Verbose: b.Verbose,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -2,9 +2,7 @@ package btse
import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
@@ -12,8 +10,8 @@ import (
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"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"
)
@@ -24,27 +22,12 @@ const (
// WsConnect connects the websocket client
func (b *BTSE) WsConnect() error {
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if b.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(b.Websocket.GetProxyAddress())
if err != nil {
return fmt.Errorf("%s websocket error - proxy address %s",
b.Name, err)
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
b.WebsocketConn, _, err = dialer.Dial(b.Websocket.GetWebsocketURL(),
http.Header{})
err := b.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return fmt.Errorf("%s websocket error - unable to connect %s",
b.Name, err)
return err
}
go b.WsHandleData()
@@ -53,17 +36,6 @@ func (b *BTSE) WsConnect() error {
return nil
}
// WsReadData reads data from the websocket connection
func (b *BTSE) WsReadData() (exchange.WebsocketResponse, error) {
_, resp, err := b.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
b.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{Raw: resp}, nil
}
// WsHandleData handles read data from websocket connection
func (b *BTSE) WsHandleData() {
b.Websocket.Wg.Add(1)
@@ -78,11 +50,12 @@ func (b *BTSE) WsHandleData() {
return
default:
resp, err := b.WsReadData()
resp, err := b.WebsocketConn.ReadMessage()
if err != nil {
b.Websocket.DataHandler <- err
return
}
b.Websocket.TrafficAlert <- struct{}{}
type MsgType struct {
Type string `json:"type"`
@@ -118,7 +91,7 @@ func (b *BTSE) WsHandleData() {
continue
}
b.Websocket.DataHandler <- exchange.TickerData{
b.Websocket.DataHandler <- wshandler.TickerData{
Timestamp: time.Now(),
Pair: currency.NewPairDelimiter(t.ProductID, "-"),
AssetType: "SPOT",
@@ -191,7 +164,7 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error {
return err
}
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: p,
Asset: "SPOT",
Exchange: b.GetName(),
@@ -204,10 +177,10 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error {
func (b *BTSE) GenerateDefaultSubscriptions() {
var channels = []string{"snapshot", "ticker"}
enabledCurrencies := b.GetEnabledCurrencies()
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
for i := range channels {
for j := range enabledCurrencies {
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channels[i],
Currency: enabledCurrencies[j],
})
@@ -217,7 +190,7 @@ func (b *BTSE) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (b *BTSE) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (b *BTSE) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
subscribe := websocketSubscribe{
Type: "subscribe",
Channels: []websocketChannel{
@@ -227,11 +200,11 @@ func (b *BTSE) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscriptio
},
},
}
return b.wsSend(subscribe)
return b.WebsocketConn.SendMessage(subscribe)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (b *BTSE) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (b *BTSE) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
subscribe := websocketSubscribe{
Type: "unsubscribe",
Channels: []websocketChannel{
@@ -241,19 +214,5 @@ func (b *BTSE) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscript
},
},
}
return b.wsSend(subscribe)
}
// WsSend sends data to the websocket server
func (b *BTSE) wsSend(data interface{}) error {
b.wsRequestMtx.Lock()
defer b.wsRequestMtx.Unlock()
if b.Verbose {
log.Debugf("%v sending message to websocket %v", b.Name, data)
}
json, err := common.JSONEncode(data)
if err != nil {
return err
}
return b.WebsocketConn.WriteMessage(websocket.TextMessage, json)
return b.WebsocketConn.SendMessage(subscribe)
}

View File

@@ -12,6 +12,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"
)
@@ -284,7 +285,7 @@ func (b *BTSE) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.Wi
}
// GetWebsocket returns a pointer to the exchange websocket
func (b *BTSE) GetWebsocket() (*exchange.Websocket, error) {
func (b *BTSE) GetWebsocket() (*wshandler.Websocket, error) {
return b.Websocket, nil
}
@@ -361,20 +362,20 @@ func (b *BTSE) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (b *BTSE) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (b *BTSE) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
b.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (b *BTSE) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
b.Websocket.UnsubscribeToChannels(channels)
func (b *BTSE) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
b.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (b *BTSE) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (b *BTSE) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return b.Websocket.GetSubscriptions(), nil
}

View File

@@ -8,16 +8,15 @@ import (
"net/url"
"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"
)
@@ -61,8 +60,7 @@ const (
// CoinbasePro is the overarching type across the coinbasepro package
type CoinbasePro struct {
exchange.Base
WebsocketConn *websocket.Conn
wsRequestMtx sync.Mutex
WebsocketConn *wshandler.WebsocketConnection
}
// SetDefaults sets default values for the exchange
@@ -88,12 +86,15 @@ func (c *CoinbasePro) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
c.APIUrlDefault = coinbaseproAPIURL
c.APIUrl = c.APIUrlDefault
c.WebsocketInit()
c.Websocket.Functionality = exchange.WebsocketTickerSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported
c.Websocket = wshandler.New()
c.Websocket.Functionality = wshandler.WebsocketTickerSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported |
wshandler.WebsocketSequenceNumberSupported
c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup initialises the exchange parameters with the current configuration
@@ -137,17 +138,26 @@ func (c *CoinbasePro) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = c.WebsocketSetup(c.WsConnect,
err = c.Websocket.Setup(c.WsConnect,
c.Subscribe,
c.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
coinbaseproWebsocketURL,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
c.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: c.Name,
URL: c.Websocket.GetWebsocketURL(),
ProxyURL: c.Websocket.GetProxyAddress(),
Verbose: c.Verbose,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -10,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"
)
var c CoinbasePro
@@ -585,20 +586,24 @@ func TestWsAuth(t *testing.T) {
c.SetDefaults()
TestSetup(t)
if !c.Websocket.IsEnabled() && !c.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
c.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: c.Name,
URL: c.Websocket.GetWebsocketURL(),
Verbose: c.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var err error
var dialer websocket.Dialer
c.WebsocketConn, _, err = dialer.Dial(c.Websocket.GetWebsocketURL(),
http.Header{})
err := c.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
go c.WsHandleData()
defer c.WebsocketConn.Close()
err = c.Subscribe(exchange.WebsocketChannelSubscription{
err = c.Subscribe(wshandler.WebsocketChannelSubscription{
Channel: "user",
Currency: currency.NewPairFromString("BTC-USD"),
})

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
@@ -13,7 +12,7 @@ import (
"github.com/thrasher-/gocryptotrader/currency"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
log "github.com/thrasher-/gocryptotrader/logger"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
)
const (
@@ -23,27 +22,12 @@ const (
// WsConnect initiates a websocket connection
func (c *CoinbasePro) WsConnect() error {
if !c.Websocket.IsEnabled() || !c.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if c.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(c.Websocket.GetProxyAddress())
if err != nil {
return fmt.Errorf("coinbasepro_websocket.go error - proxy address %s",
err)
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
c.WebsocketConn, _, err = dialer.Dial(c.Websocket.GetWebsocketURL(),
http.Header{})
err := c.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return fmt.Errorf("coinbasepro_websocket.go error - unable to connect to websocket %s",
err)
return err
}
c.GenerateDefaultSubscriptions()
@@ -52,16 +36,6 @@ func (c *CoinbasePro) WsConnect() error {
return nil
}
// WsReadData reads data from the websocket connection
func (c *CoinbasePro) WsReadData() (exchange.WebsocketResponse, error) {
_, resp, err := c.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
c.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{Raw: resp}, nil
}
// WsHandleData handles read data from websocket connection
func (c *CoinbasePro) WsHandleData() {
c.Websocket.Wg.Add(1)
@@ -75,11 +49,13 @@ func (c *CoinbasePro) WsHandleData() {
case <-c.Websocket.ShutdownC:
return
default:
resp, err := c.WsReadData()
resp, err := c.WebsocketConn.ReadMessage()
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.TrafficAlert <- struct{}{}
type MsgType struct {
Type string `json:"type"`
Sequence int64 `json:"sequence"`
@@ -109,7 +85,7 @@ func (c *CoinbasePro) WsHandleData() {
continue
}
c.Websocket.DataHandler <- exchange.TickerData{
c.Websocket.DataHandler <- wshandler.TickerData{
Timestamp: ticker.Time,
Pair: currency.NewPairFromString(ticker.ProductID),
AssetType: "SPOT",
@@ -240,7 +216,7 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro
return err
}
c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: pair,
Asset: "SPOT",
Exchange: c.GetName(),
@@ -275,7 +251,7 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error {
return err
}
c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: p,
Asset: "SPOT",
Exchange: c.GetName(),
@@ -288,14 +264,14 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error {
func (c *CoinbasePro) GenerateDefaultSubscriptions() {
var channels = []string{"heartbeat", "level2", "ticker", "user"}
enabledCurrencies := c.GetEnabledCurrencies()
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
for i := range channels {
if (channels[i] == "user" || channels[i] == "full") && !c.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
continue
}
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = "-"
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channels[i],
Currency: enabledCurrencies[j],
})
@@ -305,7 +281,7 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (c *CoinbasePro) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (c *CoinbasePro) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
subscribe := WebsocketSubscribe{
Type: "subscribe",
Channels: []WsChannels{
@@ -326,11 +302,11 @@ func (c *CoinbasePro) Subscribe(channelToSubscribe exchange.WebsocketChannelSubs
subscribe.Passphrase = c.ClientID
subscribe.Timestamp = n
}
return c.wsSend(subscribe)
return c.WebsocketConn.SendMessage(subscribe)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (c *CoinbasePro) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (c *CoinbasePro) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
subscribe := WebsocketSubscribe{
Type: "unsubscribe",
Channels: []WsChannels{
@@ -342,19 +318,5 @@ func (c *CoinbasePro) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSu
},
},
}
return c.wsSend(subscribe)
}
// WsSend sends data to the websocket server
func (c *CoinbasePro) wsSend(data interface{}) error {
c.wsRequestMtx.Lock()
defer c.wsRequestMtx.Unlock()
if c.Verbose {
log.Debugf("%v sending message to websocket %v", c.Name, data)
}
json, err := common.JSONEncode(data)
if err != nil {
return err
}
return c.WebsocketConn.WriteMessage(websocket.TextMessage, json)
return c.WebsocketConn.SendMessage(subscribe)
}

View File

@@ -12,6 +12,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"
)
@@ -282,7 +283,7 @@ func (c *CoinbasePro) WithdrawFiatFundsToInternationalBank(withdrawRequest *exch
}
// GetWebsocket returns a pointer to the exchange websocket
func (c *CoinbasePro) GetWebsocket() (*exchange.Websocket, error) {
func (c *CoinbasePro) GetWebsocket() (*wshandler.Websocket, error) {
return c.Websocket, nil
}
@@ -385,20 +386,20 @@ func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersReques
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (c *CoinbasePro) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (c *CoinbasePro) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
c.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (c *CoinbasePro) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
c.Websocket.UnsubscribeToChannels(channels)
func (c *CoinbasePro) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
c.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (c *CoinbasePro) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (c *CoinbasePro) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return c.Websocket.GetSubscriptions(), nil
}

View File

@@ -6,16 +6,15 @@ import (
"errors"
"fmt"
"net/http"
"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"
)
@@ -47,9 +46,8 @@ const (
// COINUT is the overarching type across the coinut package
type COINUT struct {
exchange.Base
WebsocketConn *websocket.Conn
WebsocketConn *wshandler.WebsocketConnection
InstrumentMap map[string]int
wsRequestMtx sync.Mutex
}
// SetDefaults sets current default values
@@ -76,15 +74,18 @@ func (c *COINUT) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
c.APIUrlDefault = coinutAPIURL
c.APIUrl = c.APIUrlDefault
c.WebsocketInit()
c.Websocket.Functionality = exchange.WebsocketTickerSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketTradeDataSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported |
exchange.WebsocketSubmitOrderSupported |
exchange.WebsocketCancelOrderSupported
c.Websocket = wshandler.New()
c.Websocket.Functionality = wshandler.WebsocketTickerSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported |
wshandler.WebsocketSubmitOrderSupported |
wshandler.WebsocketCancelOrderSupported |
wshandler.WebsocketMessageCorrelationSupported
c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup sets the current exchange configuration
@@ -125,17 +126,27 @@ func (c *COINUT) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = c.WebsocketSetup(c.WsConnect,
err = c.Websocket.Setup(c.WsConnect,
c.Subscribe,
c.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
coinutWebsocketURL,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
c.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: c.Name,
URL: c.Websocket.GetWebsocketURL(),
ProxyURL: c.Websocket.GetProxyAddress(),
Verbose: c.Verbose,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
RateLimit: coinutWebsocketRateLimit,
}
}
}

View File

@@ -11,6 +11,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"
)
var c COINUT
@@ -55,12 +56,18 @@ func setupWSTestAuth(t *testing.T) {
c.SetDefaults()
TestSetup(t)
if !c.Websocket.IsEnabled() && !c.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
c.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: c.Name,
URL: coinutWebsocketURL,
Verbose: c.Verbose,
RateLimit: coinutWebsocketRateLimit,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var err error
var dialer websocket.Dialer
c.WebsocketConn, _, err = dialer.Dial(c.Websocket.GetWebsocketURL(),
http.Header{})
err := c.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
@@ -72,17 +79,6 @@ func setupWSTestAuth(t *testing.T) {
t.Error(err)
}
timer := time.NewTimer(5 * time.Second)
select {
case resp := <-c.Websocket.DataHandler:
if resp.(WsLoginResponse).Username != clientID {
t.Fatal("Unsuccessful login")
}
case <-timer.C:
t.Fatal("Expected response")
}
timer.Stop()
time.Sleep(2 * time.Second)
instrumentListByString = make(map[string]int64)
instrumentListByString[currency.NewPair(currency.LTC, currency.BTC).String()] = 1
wsSetupRan = true
@@ -448,28 +444,21 @@ func TestGetDepositAddress(t *testing.T) {
}
}
// TestWsAuthGetAccountBalance dials websocket, sends login request.
// TestWsAuthGetAccountBalance dials websocket, retrieves account balance
func TestWsAuthGetAccountBalance(t *testing.T) {
setupWSTestAuth(t)
err := c.wsGetAccountBalance()
_, err := c.wsGetAccountBalance()
if err != nil {
t.Error(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseExtendedTimeout)
select {
case resp := <-c.Websocket.DataHandler:
if resp.(WsUserBalanceResponse).Status[0] != "OK" {
t.Error("Expected successful response")
}
case <-timer.C:
t.Error("Expected response")
}
timer.Stop()
}
// TestWsAuthSubmitOrders dials websocket, sends login request.
func TestWsAuthSubmitOrders(t *testing.T) {
// TestWsAuthSubmitOrder dials websocket, submit order
func TestWsAuthSubmitOrder(t *testing.T) {
setupWSTestAuth(t)
if !canManipulateRealOrders {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
order := WsSubmitOrderParameters{
Amount: 1,
Currency: currency.NewPair(currency.LTC, currency.BTC),
@@ -477,42 +466,64 @@ func TestWsAuthSubmitOrders(t *testing.T) {
Price: 1,
Side: exchange.BuyOrderSide,
}
err := c.wsSubmitOrders([]WsSubmitOrderParameters{order, order})
_, err := c.wsSubmitOrder(&order)
if err != nil {
t.Error(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseExtendedTimeout)
select {
case <-c.Websocket.DataHandler:
case <-timer.C:
t.Error("Expected response")
}
timer.Stop()
}
// TestWsAuthCancelOrders dials websocket, sends login request.
// TestWsAuthCancelOrders dials websocket, submit orders
func TestWsAuthSubmitOrders(t *testing.T) {
setupWSTestAuth(t)
if !canManipulateRealOrders {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
order1 := WsSubmitOrderParameters{
Amount: 1,
Currency: currency.NewPair(currency.LTC, currency.BTC),
OrderID: 1,
Price: 1,
Side: exchange.BuyOrderSide,
}
order2 := WsSubmitOrderParameters{
Amount: 3,
Currency: currency.NewPair(currency.LTC, currency.BTC),
OrderID: 2,
Price: 2,
Side: exchange.BuyOrderSide,
}
_, err := c.wsSubmitOrders([]WsSubmitOrderParameters{order1, order2})
if err != nil {
t.Error(err)
}
}
// TestWsAuthCancelOrders dials websocket, cancels orders
func TestWsAuthCancelOrders(t *testing.T) {
setupWSTestAuth(t)
if !canManipulateRealOrders {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
order := WsCancelOrderParameters{
Currency: currency.NewPair(currency.LTC, currency.BTC),
OrderID: 1,
}
err := c.wsCancelOrders([]WsCancelOrderParameters{order, order})
if err != nil {
t.Error(err)
order2 := WsCancelOrderParameters{
Currency: currency.NewPair(currency.LTC, currency.BTC),
OrderID: 2,
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseExtendedTimeout)
select {
case <-c.Websocket.DataHandler:
case <-timer.C:
t.Error("Expected response")
_, errs := c.wsCancelOrders([]WsCancelOrderParameters{order, order2})
if len(errs) > 0 {
t.Error(errs)
}
timer.Stop()
}
// TestWsAuthCancelOrder dials websocket, sends login request.
// TestWsAuthCancelOrder dials websocket, cancels order
func TestWsAuthCancelOrder(t *testing.T) {
setupWSTestAuth(t)
if !canManipulateRealOrders {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
order := WsCancelOrderParameters{
Currency: currency.NewPair(currency.LTC, currency.BTC),
OrderID: 1,
@@ -521,27 +532,13 @@ func TestWsAuthCancelOrder(t *testing.T) {
if err != nil {
t.Error(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseExtendedTimeout)
select {
case <-c.Websocket.DataHandler:
case <-timer.C:
t.Error("Expected response")
}
timer.Stop()
}
// TestWsAuthGetOpenOrders dials websocket, sends login request.
// TestWsAuthGetOpenOrders dials websocket, retrieves open orders
func TestWsAuthGetOpenOrders(t *testing.T) {
setupWSTestAuth(t)
err := c.wsGetOpenOrders(currency.NewPair(currency.LTC, currency.BTC))
if err != nil {
t.Error(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseExtendedTimeout)
select {
case <-c.Websocket.DataHandler:
case <-timer.C:
t.Error("Expected response")
}
timer.Stop()
}

View File

@@ -263,11 +263,12 @@ type wsRequest struct {
SecType string `json:"sec_type,omitempty"`
InstID int64 `json:"inst_id,omitempty"`
TopN int64 `json:"top_n,omitempty"`
Subscribe bool `json:"subscribe"`
Nonce int64 `json:"nonce"`
Subscribe bool `json:"subscribe,omitempty"`
Nonce int64 `json:"nonce,omitempty"`
}
type wsResponse struct {
Nonce int64 `json:"nonce,omitempty"`
Reply string `json:"reply"`
}
@@ -400,14 +401,14 @@ type WsCancelOrderParameters struct {
OrderID int64
}
// WsCancelOrderRequest ws request
// WsCancelOrderRequest data required for cancelling an order
type WsCancelOrderRequest struct {
InstID int64 `json:"inst_id"`
OrderID int64 `json:"order_id"`
WsRequest
}
// WsCancelOrderResponse ws response
// WsCancelOrderResponse contains cancelled order data
type WsCancelOrderResponse struct {
Nonce int64 `json:"nonce"`
Reply string `json:"reply"`
@@ -416,16 +417,20 @@ type WsCancelOrderResponse struct {
Status []string `json:"status"`
}
// WsCancelOrdersResponse ws response
// WsCancelOrdersResponse contains all cancelled order data
type WsCancelOrdersResponse struct {
WsRequest
Entries []WsCancelOrdersResponseEntry `json:"entries"`
Nonce int64 `json:"nonce"`
Reply string `json:"reply"`
Results []WsCancelOrdersResponseData `json:"results"`
Status []string `json:"status"`
TransID int64 `json:"trans_id"`
}
// WsCancelOrdersResponseEntry ws response entry
type WsCancelOrdersResponseEntry struct {
InstID int64 `json:"inst_id"`
OrderID int64 `json:"order_id"`
// WsCancelOrdersResponseData individual cancellation response data
type WsCancelOrdersResponseData struct {
InstID int64 `json:"inst_id"`
OrderID int64 `json:"order_id"`
Status string `json:"status"`
}
// WsGetOpenOrdersRequest ws request
@@ -547,6 +552,25 @@ type WsOrderRejectedResponse struct {
TransID int64 `json:"trans_id"`
}
// WsStandardOrderResponse a standardised order
type WsStandardOrderResponse struct {
InstID int64
OrderID int64
ClientOrdID int64
TransID int64
Nonce int64
Status []string
Qty float64
OpenQty float64
Price float64
Side string
Reasons []string
Timestamp int64
OrderType string
CommissionAmount float64
CommissionCurrency currency.Pair
}
// WsUserOpenOrdersResponse ws response
type WsUserOpenOrdersResponse struct {
Nonce int64 `json:"nonce"`
@@ -612,3 +636,25 @@ type WsNewOrderResponse struct {
Reply string `json:"reply"`
Status []string `json:"status"`
}
// WsGetAccountBalanceResponse contains values of each currency
type WsGetAccountBalanceResponse struct {
BCH string `json:"BCH"`
BTC string `json:"BTC"`
BTG string `json:"BTG"`
CAD string `json:"CAD"`
ETC string `json:"ETC"`
ETH string `json:"ETH"`
LCH string `json:"LCH"`
LTC string `json:"LTC"`
MYR string `json:"MYR"`
SGD string `json:"SGD"`
USD string `json:"USD"`
USDT string `json:"USDT"`
XMR string `json:"XMR"`
ZEC string `json:"ZEC"`
Nonce int64 `json:"nonce"`
Reply string `json:"reply"`
Status []string `json:"status"`
TransID int64 `json:"trans_id"`
}

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
@@ -13,11 +12,11 @@ import (
"github.com/thrasher-/gocryptotrader/currency"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
log "github.com/thrasher-/gocryptotrader/logger"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
)
const coinutWebsocketURL = "wss://wsapi.coinut.com"
const coinutWebsocketRateLimit = 30 * time.Millisecond
const coinutWebsocketRateLimit = 30
var nNonce map[int64]string
var channels map[string]chan []byte
@@ -33,32 +32,18 @@ var populatedList bool
// WsConnect intiates a websocket connection
func (c *COINUT) WsConnect() error {
if !c.Websocket.IsEnabled() || !c.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var Dialer websocket.Dialer
if c.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(c.Websocket.GetProxyAddress())
if err != nil {
return err
}
Dialer.Proxy = http.ProxyURL(proxy)
}
var err error
c.WebsocketConn, _, err = Dialer.Dial(c.Websocket.GetWebsocketURL(),
http.Header{})
var dialer websocket.Dialer
err := c.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
go c.WsHandleData()
if !populatedList {
instrumentListByString = make(map[string]int64)
instrumentListByCode = make(map[int64]string)
err = c.WsSetInstrumentList()
if err != nil {
return err
@@ -72,21 +57,9 @@ func (c *COINUT) WsConnect() error {
channels = make(map[string]chan []byte)
channels["hb"] = make(chan []byte, 1)
go c.WsHandleData()
return nil
}
// WsReadData reads data from the websocket connection
func (c *COINUT) WsReadData() (exchange.WebsocketResponse, error) {
_, resp, err := c.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
c.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{Raw: resp}, nil
}
// WsHandleData handles read data
func (c *COINUT) WsHandleData() {
c.Websocket.Wg.Add(1)
@@ -101,11 +74,12 @@ func (c *COINUT) WsHandleData() {
return
default:
resp, err := c.WsReadData()
resp, err := c.WebsocketConn.ReadMessage()
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.TrafficAlert <- struct{}{}
if strings.HasPrefix(string(resp.Raw), "[") {
var incoming []wsResponse
@@ -115,6 +89,10 @@ func (c *COINUT) WsHandleData() {
continue
}
for i := range incoming {
if incoming[i].Nonce > 0 {
c.WebsocketConn.AddResponseWithID(incoming[i].Nonce, resp.Raw)
break
}
var individualJSON []byte
individualJSON, err = common.JSONEncode(incoming[i])
if err != nil {
@@ -131,6 +109,7 @@ func (c *COINUT) WsHandleData() {
c.Websocket.DataHandler <- err
continue
}
c.wsProcessResponse(resp.Raw)
}
@@ -146,15 +125,6 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
return
}
switch incoming.Reply {
case "login":
var login WsLoginResponse
err := common.JSONDecode(resp, &login)
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.SetCanUseAuthenticatedEndpoints(login.Username == c.ClientID)
c.Websocket.DataHandler <- login
case "hb":
channels["hb"] <- resp
case "inst_tick":
@@ -164,7 +134,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
c.Websocket.DataHandler <- err
return
}
c.Websocket.DataHandler <- exchange.TickerData{
c.Websocket.DataHandler <- wshandler.TickerData{
Timestamp: time.Unix(0, ticker.Timestamp),
Exchange: c.GetName(),
AssetType: "SPOT",
@@ -187,7 +157,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
return
}
currencyPair := instrumentListByCode[orderbooksnapshot.InstID]
c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: c.GetName(),
Asset: "SPOT",
Pair: currency.NewPairFromString(currencyPair),
@@ -205,7 +175,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
return
}
currencyPair := instrumentListByCode[orderbookUpdate.InstID]
c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: c.GetName(),
Asset: "SPOT",
Pair: currency.NewPairFromString(currencyPair),
@@ -226,7 +196,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
return
}
currencyPair := instrumentListByCode[tradeUpdate.InstID]
c.Websocket.DataHandler <- exchange.TradeData{
c.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: time.Unix(tradeUpdate.Timestamp, 0),
CurrencyPair: currency.NewPairFromString(currencyPair),
AssetType: "SPOT",
@@ -234,78 +204,12 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
Price: tradeUpdate.Price,
Side: tradeUpdate.Side,
}
case "user_balance":
var userBalance WsUserBalanceResponse
err := common.JSONDecode(resp, &userBalance)
if err != nil {
c.Websocket.DataHandler <- err
default:
if incoming.Nonce > 0 {
c.WebsocketConn.AddResponseWithID(incoming.Nonce, resp)
return
}
c.Websocket.DataHandler <- userBalance
case "new_order":
var newOrder WsNewOrderResponse
err := common.JSONDecode(resp, &newOrder)
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.DataHandler <- newOrder
case "order_accepted":
var orderAccepted WsOrderAcceptedResponse
err := common.JSONDecode(resp, &orderAccepted)
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.DataHandler <- orderAccepted
case "order_filled":
var orderFilled WsOrderFilledResponse
err := common.JSONDecode(resp, &orderFilled)
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.DataHandler <- orderFilled
case "order_rejected":
var orderRejected WsOrderRejectedResponse
err := common.JSONDecode(resp, &orderRejected)
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.DataHandler <- orderRejected
case "user_open_orders":
var openOrders WsUserOpenOrdersResponse
err := common.JSONDecode(resp, &openOrders)
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.DataHandler <- openOrders
case "trade_history":
var tradeHistory WsTradeHistoryResponse
err := common.JSONDecode(resp, &tradeHistory)
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.DataHandler <- tradeHistory
case "cancel_orders":
var cancelOrders WsCancelOrdersResponse
err := common.JSONDecode(resp, &cancelOrders)
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.DataHandler <- cancelOrders
case "cancel_order":
var cancelOrder WsCancelOrderResponse
err := common.JSONDecode(resp, &cancelOrder)
if err != nil {
c.Websocket.DataHandler <- err
return
}
c.Websocket.DataHandler <- cancelOrder
c.Websocket.DataHandler <- fmt.Errorf("%v unhandled websocket response: %s", c.Name, resp)
}
}
@@ -322,37 +226,27 @@ func (c *COINUT) GetNonce() int64 {
// WsSetInstrumentList fetches instrument list and propagates a local cache
func (c *COINUT) WsSetInstrumentList() error {
err := c.wsSend(wsRequest{
request := wsRequest{
Request: "inst_list",
SecType: "SPOT",
Nonce: c.GetNonce(),
})
Nonce: c.WebsocketConn.GenerateMessageID(false),
}
resp, err := c.WebsocketConn.SendMessageReturnResponse(request.Nonce, request)
if err != nil {
return err
}
_, resp, err := c.WebsocketConn.ReadMessage()
if err != nil {
return err
}
c.Websocket.TrafficAlert <- struct{}{}
var list WsInstrumentList
err = common.JSONDecode(resp, &list)
if err != nil {
return err
}
for currency, data := range list.Spot {
instrumentListByString[currency] = data[0].InstID
instrumentListByCode[data[0].InstID] = currency
}
if len(instrumentListByString) == 0 || len(instrumentListByCode) == 0 {
return errors.New("instrument lists failed to populate")
}
return nil
}
@@ -409,11 +303,11 @@ func (c *COINUT) WsProcessOrderbookUpdate(ob *WsOrderbookUpdate) error {
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (c *COINUT) GenerateDefaultSubscriptions() {
var channels = []string{"inst_tick", "inst_order_book"}
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
enabledCurrencies := c.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],
})
@@ -423,42 +317,37 @@ func (c *COINUT) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (c *COINUT) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
subscribe := wsRequest{
Request: channelToSubscribe.Channel,
InstID: instrumentListByString[channelToSubscribe.Currency.String()],
Subscribe: true,
Nonce: c.GetNonce(),
Nonce: c.WebsocketConn.GenerateMessageID(false),
}
return c.wsSend(subscribe)
return c.WebsocketConn.SendMessage(subscribe)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (c *COINUT) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (c *COINUT) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
subscribe := wsRequest{
Request: channelToSubscribe.Channel,
InstID: instrumentListByString[channelToSubscribe.Currency.String()],
Subscribe: false,
Nonce: c.GetNonce(),
Nonce: c.WebsocketConn.GenerateMessageID(false),
}
return c.wsSend(subscribe)
}
// WsSend sends data to the websocket server
func (c *COINUT) wsSend(data interface{}) error {
c.wsRequestMtx.Lock()
defer c.wsRequestMtx.Unlock()
json, err := common.JSONEncode(data)
resp, err := c.WebsocketConn.SendMessageReturnResponse(subscribe.Nonce, subscribe)
if err != nil {
return err
}
if c.Verbose {
log.Debugf("%v sending message to websocket %v", c.Name, string(json))
var response map[string]interface{}
err = common.JSONDecode(resp, &response)
if err != nil {
return err
}
// Basic rate limiter
time.Sleep(coinutWebsocketRateLimit)
return c.WebsocketConn.WriteMessage(websocket.TextMessage, json)
if response["status"].([]interface{})[0] != "OK" {
return fmt.Errorf("%v unsubscribe failed for channel %v", c.Name, channelToSubscribe.Channel)
}
return nil
}
func (c *COINUT) wsAuthenticate() error {
@@ -466,7 +355,7 @@ func (c *COINUT) wsAuthenticate() error {
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", c.Name)
}
timestamp := time.Now().Unix()
nonce := c.GetNonce()
nonce := c.WebsocketConn.GenerateMessageID(false)
payload := fmt.Sprintf("%v|%v|%v", c.ClientID, timestamp, nonce)
hmac := common.GetHMAC(common.HashSHA256, []byte(payload), []byte(c.APIKey))
loginRequest := struct {
@@ -483,34 +372,54 @@ func (c *COINUT) wsAuthenticate() error {
Timestamp: timestamp,
}
err := c.wsSend(loginRequest)
resp, err := c.WebsocketConn.SendMessageReturnResponse(loginRequest.Nonce, loginRequest)
if err != nil {
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
return err
}
var response map[string]interface{}
err = common.JSONDecode(resp, &response)
if err != nil {
return err
}
if response["status"].([]interface{})[0] != "OK" {
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
return fmt.Errorf("%v failed to authenticate", c.Name)
}
c.Websocket.SetCanUseAuthenticatedEndpoints(true)
return nil
}
func (c *COINUT) wsGetAccountBalance() error {
func (c *COINUT) wsGetAccountBalance() (*WsGetAccountBalanceResponse, error) {
if !c.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authorised to submit order", c.Name)
return nil, fmt.Errorf("%v not authorised to submit order", c.Name)
}
accBalance := wsRequest{
Request: "user_balance",
Nonce: c.GetNonce(),
Nonce: c.WebsocketConn.GenerateMessageID(false),
}
return c.wsSend(accBalance)
resp, err := c.WebsocketConn.SendMessageReturnResponse(accBalance.Nonce, accBalance)
if err != nil {
return nil, err
}
var response WsGetAccountBalanceResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return nil, err
}
if response.Status[0] != "OK" {
return &response, fmt.Errorf("%v get account balance failed", c.Name)
}
return &response, nil
}
func (c *COINUT) wsSubmitOrder(order *WsSubmitOrderParameters) error {
func (c *COINUT) wsSubmitOrder(order *WsSubmitOrderParameters) (*WsStandardOrderResponse, error) {
if !c.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authorised to submit order", c.Name)
return nil, fmt.Errorf("%v not authorised to submit order", c.Name)
}
currency := exchange.FormatExchangeCurrency(c.Name, order.Currency).String()
var orderSubmissionRequest WsSubmitOrderRequest
orderSubmissionRequest.Request = "new_order"
orderSubmissionRequest.Nonce = c.GetNonce()
orderSubmissionRequest.Nonce = c.WebsocketConn.GenerateMessageID(false)
orderSubmissionRequest.InstID = instrumentListByString[currency]
orderSubmissionRequest.Qty = order.Amount
orderSubmissionRequest.Price = order.Price
@@ -519,12 +428,100 @@ func (c *COINUT) wsSubmitOrder(order *WsSubmitOrderParameters) error {
if order.OrderID > 0 {
orderSubmissionRequest.OrderID = order.OrderID
}
return c.wsSend(orderSubmissionRequest)
resp, err := c.WebsocketConn.SendMessageReturnResponse(orderSubmissionRequest.Nonce, orderSubmissionRequest)
if err != nil {
return nil, err
}
var standardOrder WsStandardOrderResponse
standardOrder, err = c.wsStandardiseOrderResponse(resp)
if err != nil {
return nil, err
}
if standardOrder.Status[0] != "OK" {
return &standardOrder, fmt.Errorf("%v order submission failed. %v", c.Name, standardOrder)
}
if len(standardOrder.Reasons) > 0 && standardOrder.Reasons[0] != "" {
return &standardOrder, fmt.Errorf("%v order submission failed. %v", c.Name, standardOrder.Reasons[0])
}
return &standardOrder, nil
}
func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) error {
func (c *COINUT) wsStandardiseOrderResponse(resp []byte) (WsStandardOrderResponse, error) {
var response WsStandardOrderResponse
var incoming wsResponse
err := common.JSONDecode(resp, &incoming)
if err != nil {
return response, err
}
switch incoming.Reply {
case "order_accepted":
var orderAccepted WsOrderAcceptedResponse
err := common.JSONDecode(resp, &orderAccepted)
if err != nil {
return response, err
}
response = WsStandardOrderResponse{
InstID: orderAccepted.InstID,
Nonce: orderAccepted.Nonce,
OpenQty: orderAccepted.OpenQty,
OrderID: orderAccepted.OrderID,
OrderType: orderAccepted.Reply,
Price: orderAccepted.OrderPrice,
Qty: orderAccepted.Qty,
Side: orderAccepted.Side,
Status: orderAccepted.Status,
TransID: orderAccepted.TransID,
ClientOrdID: orderAccepted.ClientOrdID,
}
case "order_filled":
var orderFilled WsOrderFilledResponse
err := common.JSONDecode(resp, &orderFilled)
if err != nil {
return response, err
}
response = WsStandardOrderResponse{
InstID: orderFilled.Order.InstID,
Nonce: orderFilled.Nonce,
OpenQty: orderFilled.Order.OpenQty,
OrderID: orderFilled.Order.OrderID,
OrderType: orderFilled.Reply,
Price: orderFilled.Order.Price,
Qty: orderFilled.Order.Qty,
Side: orderFilled.Order.Side,
Status: orderFilled.Status,
TransID: orderFilled.TransID,
ClientOrdID: orderFilled.Order.ClientOrdID,
}
case "order_rejected":
var orderRejected WsOrderRejectedResponse
err := common.JSONDecode(resp, &orderRejected)
if err != nil {
return response, err
}
response = WsStandardOrderResponse{
InstID: orderRejected.InstID,
Nonce: orderRejected.Nonce,
OpenQty: orderRejected.OpenQty,
OrderID: orderRejected.OrderID,
OrderType: orderRejected.Reply,
Price: orderRejected.Price,
Qty: orderRejected.Qty,
Side: orderRejected.Side,
Status: orderRejected.Status,
TransID: orderRejected.TransID,
ClientOrdID: orderRejected.ClientOrdID,
Reasons: orderRejected.Reasons,
}
}
return response, nil
}
func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardOrderResponse, []error) {
var errors []error
var ordersResponse []WsStandardOrderResponse
if !c.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authorised to submit orders", c.Name)
errors = append(errors, fmt.Errorf("%v not authorised to submit orders", c.Name))
return nil, errors
}
orderRequest := WsSubmitOrdersRequest{}
for i := range orders {
@@ -539,9 +536,48 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) error {
})
}
orderRequest.Nonce = c.GetNonce()
orderRequest.Nonce = c.WebsocketConn.GenerateMessageID(false)
orderRequest.Request = "new_orders"
return c.wsSend(orderRequest)
resp, err := c.WebsocketConn.SendMessageReturnResponse(orderRequest.Nonce, orderRequest)
if err != nil {
errors = append(errors, err)
return nil, errors
}
var incoming []interface{}
err = common.JSONDecode(resp, &incoming)
if err != nil {
errors = append(errors, err)
return nil, errors
}
for i := range incoming {
var individualJSON []byte
individualJSON, err = common.JSONEncode(incoming[i])
if err != nil {
errors = append(errors, err)
continue
}
standardOrder, err := c.wsStandardiseOrderResponse(individualJSON)
if err != nil {
errors = append(errors, err)
continue
}
if standardOrder.Status[0] != "OK" {
errors = append(errors, fmt.Errorf("%v order submission failed. %v", c.Name, standardOrder))
continue
}
if len(standardOrder.Reasons) > 0 && standardOrder.Reasons[0] != "" {
errors = append(errors, fmt.Errorf("%v order submission failed for currency %v and orderID %v, message %v ",
c.Name,
instrumentListByCode[standardOrder.InstID],
standardOrder.OrderID,
standardOrder.Reasons[0]))
continue
}
ordersResponse = append(ordersResponse, standardOrder)
}
return ordersResponse, errors
}
func (c *COINUT) wsGetOpenOrders(p currency.Pair) error {
@@ -551,10 +587,24 @@ func (c *COINUT) wsGetOpenOrders(p currency.Pair) error {
currency := exchange.FormatExchangeCurrency(c.Name, p).String()
var openOrdersRequest WsGetOpenOrdersRequest
openOrdersRequest.Request = "user_open_orders"
openOrdersRequest.Nonce = c.GetNonce()
openOrdersRequest.Nonce = c.WebsocketConn.GenerateMessageID(false)
openOrdersRequest.InstID = instrumentListByString[currency]
return c.wsSend(openOrdersRequest)
resp, err := c.WebsocketConn.SendMessageReturnResponse(openOrdersRequest.Nonce, openOrdersRequest)
if err != nil {
return err
}
var response map[string]interface{}
err = common.JSONDecode(resp, &response)
if err != nil {
return err
}
if response["status"].([]interface{})[0] != "OK" {
return fmt.Errorf("%v get open orders failed for currency %v",
c.Name,
p)
}
return nil
}
func (c *COINUT) wsCancelOrder(cancellation WsCancelOrderParameters) error {
@@ -566,14 +616,31 @@ func (c *COINUT) wsCancelOrder(cancellation WsCancelOrderParameters) error {
cancellationRequest.Request = "cancel_order"
cancellationRequest.InstID = instrumentListByString[currency]
cancellationRequest.OrderID = cancellation.OrderID
cancellationRequest.Nonce = c.GetNonce()
cancellationRequest.Nonce = c.WebsocketConn.GenerateMessageID(false)
return c.wsSend(cancellationRequest)
resp, err := c.WebsocketConn.SendMessageReturnResponse(cancellationRequest.Nonce, cancellationRequest)
if err != nil {
return err
}
var response map[string]interface{}
err = common.JSONDecode(resp, &response)
if err != nil {
return err
}
if response["status"].([]interface{})[0] != "OK" {
return fmt.Errorf("%v order cancellation failed for currency %v and orderID %v, message %v",
c.Name,
cancellation.Currency,
cancellation.OrderID,
response["status"])
}
return nil
}
func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) error {
func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*WsCancelOrdersResponse, []error) {
var errors []error
if !c.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authorised to cancel orders", c.Name)
return nil, errors
}
cancelOrderRequest := WsCancelOrdersRequest{}
for i := range cancellations {
@@ -585,8 +652,29 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) error {
}
cancelOrderRequest.Request = "cancel_orders"
cancelOrderRequest.Nonce = c.GetNonce()
return c.wsSend(cancelOrderRequest)
cancelOrderRequest.Nonce = c.WebsocketConn.GenerateMessageID(false)
resp, err := c.WebsocketConn.SendMessageReturnResponse(cancelOrderRequest.Nonce, cancelOrderRequest)
if err != nil {
return nil, []error{err}
}
var response WsCancelOrdersResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return nil, []error{err}
}
if response.Status[0] != "OK" {
return &response, []error{err}
}
for i := range response.Results {
if response.Results[i].Status != "OK" {
errors = append(errors, fmt.Errorf("%v order cancellation failed for currency %v and orderID %v, message %v",
c.Name,
instrumentListByCode[response.Results[i].InstID],
response.Results[i].OrderID,
response.Results[i].Status))
}
}
return &response, errors
}
func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) error {
@@ -597,9 +685,23 @@ func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) error {
var request WsTradeHistoryRequest
request.Request = "trade_history"
request.InstID = instrumentListByString[currency]
request.Nonce = c.GetNonce()
request.Nonce = c.WebsocketConn.GenerateMessageID(false)
request.Start = start
request.Limit = limit
return c.wsSend(request)
resp, err := c.WebsocketConn.SendMessageReturnResponse(request.Nonce, request)
if err != nil {
return err
}
var response map[string]interface{}
err = common.JSONDecode(resp, &response)
if err != nil {
return err
}
if response["status"].([]interface{})[0] != "OK" {
return fmt.Errorf("%v get trade history failed for %v",
c.Name,
request)
}
return 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"
)
@@ -377,7 +378,7 @@ func (c *COINUT) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.
}
// GetWebsocket returns a pointer to the exchange websocket
func (c *COINUT) GetWebsocket() (*exchange.Websocket, error) {
func (c *COINUT) GetWebsocket() (*wshandler.Websocket, error) {
return c.Websocket, nil
}
@@ -506,20 +507,20 @@ func (c *COINUT) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (c *COINUT) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (c *COINUT) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
c.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (c *COINUT) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
c.Websocket.UnsubscribeToChannels(channels)
func (c *COINUT) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
c.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (c *COINUT) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (c *COINUT) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return c.Websocket.GetSubscriptions(), nil
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
log "github.com/thrasher-/gocryptotrader/logger"
)
@@ -27,6 +28,10 @@ const (
ErrExchangeNotFound = "exchange not found in dataset"
// DefaultHTTPTimeout is the default HTTP/HTTPS Timeout for exchange requests
DefaultHTTPTimeout = time.Second * 15
// DefaultWebsocketResponseCheckTimeout is the default delay in checking for an expected websocket response
DefaultWebsocketResponseCheckTimeout = time.Millisecond * 30
// DefaultWebsocketResponseMaxLimit is the default max wait for an expected websocket response before a timeout
DefaultWebsocketResponseMaxLimit = time.Second * 7
)
// FeeType custom type for calculating fees based on method
@@ -260,6 +265,8 @@ type Base struct {
Enabled bool
Verbose bool
RESTPollingDelay time.Duration
WebsocketResponseCheckTimeout time.Duration
WebsocketResponseMaxLimit time.Duration
AuthenticatedAPISupport bool
AuthenticatedWebsocketAPISupport bool
APIWithdrawPermissions uint32
@@ -283,7 +290,7 @@ type Base struct {
APIUrlSecondaryDefault string
RequestCurrencyPairFormat config.CurrencyPairFormatConfig
ConfigCurrencyPairFormat config.CurrencyPairFormatConfig
Websocket *Websocket
Websocket *wshandler.Websocket
*request.Requester
}
@@ -326,11 +333,11 @@ type IBotExchange interface {
WithdrawCryptocurrencyFunds(withdrawRequest *WithdrawRequest) (string, error)
WithdrawFiatFunds(withdrawRequest *WithdrawRequest) (string, error)
WithdrawFiatFundsToInternationalBank(withdrawRequest *WithdrawRequest) (string, error)
GetWebsocket() (*Websocket, error)
SubscribeToWebsocketChannels(channels []WebsocketChannelSubscription) error
UnsubscribeToWebsocketChannels(channels []WebsocketChannelSubscription) error
GetWebsocket() (*wshandler.Websocket, error)
SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error
UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error
AuthenticateWebsocket() error
GetSubscriptions() ([]WebsocketChannelSubscription, error)
GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error)
}
// SupportsRESTTickerBatchUpdates returns whether or not the

View File

@@ -11,6 +11,7 @@ import (
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
)
const (
@@ -79,7 +80,7 @@ func TestSetClientProxyAddress(t *testing.T) {
newBase := Base{Name: "Testicles", Requester: requester}
newBase.WebsocketInit()
newBase.Websocket = wshandler.New()
err := newBase.SetClientProxyAddress(":invalid")
if err == nil {

View File

@@ -15,6 +15,7 @@ import (
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"
)
@@ -73,7 +74,7 @@ func (e *EXMO) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
e.APIUrlDefault = exmoAPIURL
e.APIUrl = e.APIUrlDefault
e.WebsocketInit()
e.Websocket = wshandler.New()
}
// Setup takes in the supplied exchange configuration details and sets params

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"
)
@@ -313,7 +314,7 @@ func (e *EXMO) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.Wi
}
// GetWebsocket returns a pointer to the exchange websocket
func (e *EXMO) GetWebsocket() (*exchange.Websocket, error) {
func (e *EXMO) GetWebsocket() (*wshandler.Websocket, error) {
return nil, common.ErrFunctionNotSupported
}
@@ -399,18 +400,18 @@ func (e *EXMO) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]e
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (e *EXMO) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (e *EXMO) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (e *EXMO) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (e *EXMO) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (e *EXMO) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (e *EXMO) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

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
}

View File

@@ -9,12 +9,12 @@ import (
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
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"
)
@@ -67,7 +67,7 @@ var (
// AddSession, if sandbox test is needed append a new session with with the same
// API keys and change the IsSandbox variable to true.
type Gemini struct {
WebsocketConn *websocket.Conn
AuthenticatedWebsocketConn *wshandler.WebsocketConnection
exchange.Base
Role string
RequiresHeartBeat bool
@@ -121,10 +121,13 @@ func (g *Gemini) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
g.APIUrlDefault = geminiAPIURL
g.APIUrl = g.APIUrlDefault
g.WebsocketInit()
g.Websocket.Functionality = exchange.WebsocketOrderbookSupported |
exchange.WebsocketTradeDataSupported |
exchange.WebsocketAuthenticatedEndpointsSupported
g.Websocket = wshandler.New()
g.Websocket.Functionality = wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported |
wshandler.WebsocketSequenceNumberSupported
g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup sets exchange configuration parameters
@@ -169,17 +172,20 @@ func (g *Gemini) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = g.WebsocketSetup(g.WsConnect,
err = g.Websocket.Setup(g.WsConnect,
nil,
nil,
exch.Name,
exch.Websocket,
exch.Verbose,
g.WebsocketURL,
g.WebsocketURL)
g.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
responseCheckTimeout = exch.WebsocketResponseCheckTimeout
responseMaxLimit = exch.WebsocketResponseMaxLimit
}
}

View File

@@ -11,6 +11,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 enter sandbox API keys & assigned roles for better testing procedures
@@ -568,7 +569,7 @@ func TestWsAuth(t *testing.T) {
g.WebsocketURL = geminiWebsocketSandboxEndpoint
if !g.Websocket.IsEnabled() && !g.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
go g.WsHandleData()

View File

@@ -14,6 +14,7 @@ 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"
)
@@ -27,11 +28,13 @@ const (
// Instantiates a communications channel between websocket connections
var comms = make(chan ReadData, 1)
var responseMaxLimit time.Duration
var responseCheckTimeout time.Duration
// WsConnect initiates a websocket connection
func (g *Gemini) WsConnect() error {
if !g.Websocket.IsEnabled() || !g.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
@@ -62,11 +65,18 @@ func (g *Gemini) WsSubscribe(dialer *websocket.Dialer) error {
geminiWsMarketData,
c.String(),
val.Encode())
conn, conStatus, err := dialer.Dial(endpoint, http.Header{})
if err != nil {
return fmt.Errorf("%v %v %v Error: %v", endpoint, conStatus, conStatus.StatusCode, err)
connection := &wshandler.WebsocketConnection{
ExchangeName: g.Name,
URL: endpoint,
Verbose: g.Verbose,
ResponseCheckTimeout: responseCheckTimeout,
ResponseMaxLimit: responseMaxLimit,
}
go g.WsReadData(conn, c)
err := connection.Dial(dialer, http.Header{})
if err != nil {
return fmt.Errorf("%v Websocket connection %v error. Error %v", g.Name, endpoint, err)
}
go g.WsReadData(connection, c)
if len(enabledCurrencies)-1 == i {
return nil
}
@@ -99,17 +109,22 @@ func (g *Gemini) WsSecureSubscribe(dialer *websocket.Dialer, url string) error {
headers.Add("X-GEMINI-SIGNATURE", common.HexEncodeToString(hmac))
headers.Add("Cache-Control", "no-cache")
conn, conStatus, err := dialer.Dial(endpoint, headers)
if err != nil {
return fmt.Errorf("%v %v %v Error: %v", endpoint, conStatus, conStatus.StatusCode, err)
g.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: g.Name,
URL: endpoint,
Verbose: g.Verbose,
}
go g.WsReadData(conn, currency.Pair{})
err = g.AuthenticatedWebsocketConn.Dial(dialer, headers)
if err != nil {
return fmt.Errorf("%v Websocket connection %v error. Error %v", g.Name, endpoint, err)
}
go g.WsReadData(g.AuthenticatedWebsocketConn, currency.Pair{})
return nil
}
// WsReadData reads from the websocket connection and returns the websocket
// response
func (g *Gemini) WsReadData(ws *websocket.Conn, c currency.Pair) {
func (g *Gemini) WsReadData(ws *wshandler.WebsocketConnection, c currency.Pair) {
g.Websocket.Wg.Add(1)
defer g.Websocket.Wg.Done()
for {
@@ -117,13 +132,13 @@ func (g *Gemini) WsReadData(ws *websocket.Conn, c currency.Pair) {
case <-g.Websocket.ShutdownC:
return
default:
_, resp, err := ws.ReadMessage()
resp, err := ws.ReadMessage()
if err != nil {
g.Websocket.DataHandler <- err
return
}
g.Websocket.TrafficAlert <- struct{}{}
comms <- ReadData{Raw: resp, Currency: c}
comms <- ReadData{Raw: resp.Raw, Currency: c}
}
}
}
@@ -271,13 +286,13 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa
return
}
g.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: pair,
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair,
Asset: "SPOT",
Exchange: g.GetName()}
} else {
for _, event := range result.Events {
if event.Type == "trade" {
g.Websocket.DataHandler <- exchange.TradeData{
g.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: time.Now(),
CurrencyPair: pair,
AssetType: "SPOT",
@@ -316,7 +331,7 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa
}
}
g.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: pair,
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair,
Asset: "SPOT",
Exchange: g.GetName()}
}

View File

@@ -14,6 +14,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"
)
@@ -253,7 +254,7 @@ func (g *Gemini) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.
}
// GetWebsocket returns a pointer to the exchange websocket
func (g *Gemini) GetWebsocket() (*exchange.Websocket, error) {
func (g *Gemini) GetWebsocket() (*wshandler.Websocket, error) {
return g.Websocket, nil
}
@@ -360,18 +361,18 @@ func (g *Gemini) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (g *Gemini) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (g *Gemini) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (g *Gemini) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (g *Gemini) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (g *Gemini) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (g *Gemini) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -7,16 +7,15 @@ import (
"net/http"
"net/url"
"strconv"
"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"
)
@@ -54,8 +53,7 @@ const (
// HitBTC is the overarching type across the hitbtc package
type HitBTC struct {
exchange.Base
WebsocketConn *websocket.Conn
wsRequestMtx sync.Mutex
WebsocketConn *wshandler.WebsocketConnection
}
// SetDefaults sets default settings for hitbtc
@@ -80,14 +78,17 @@ func (h *HitBTC) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
h.APIUrlDefault = apiURL
h.APIUrl = h.APIUrlDefault
h.WebsocketInit()
h.Websocket.Functionality = exchange.WebsocketTickerSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported |
exchange.WebsocketSubmitOrderSupported |
exchange.WebsocketCancelOrderSupported
h.Websocket = wshandler.New()
h.Websocket.Functionality = wshandler.WebsocketTickerSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported |
wshandler.WebsocketSubmitOrderSupported |
wshandler.WebsocketCancelOrderSupported |
wshandler.WebsocketMessageCorrelationSupported
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup sets user exchange configuration settings
@@ -128,17 +129,27 @@ func (h *HitBTC) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = h.WebsocketSetup(h.WsConnect,
err = h.Websocket.Setup(h.WsConnect,
h.Subscribe,
h.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
hitbtcWebsocketAddress,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
h.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: h.Name,
URL: h.Websocket.GetWebsocketURL(),
ProxyURL: h.Websocket.GetProxyAddress(),
Verbose: h.Verbose,
RateLimit: rateLimit,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -11,9 +11,11 @@ import (
"github.com/thrasher-/gocryptotrader/currency"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
)
var h HitBTC
var wsSetupRan bool
// Please supply your own APIKEYS here for due diligence testing
const (
@@ -102,7 +104,7 @@ func TestGetFee(t *testing.T) {
var feeBuilder = setFeeBuilder()
if areTestAPIKeysSet() {
// CryptocurrencyTradeFee Basic
if resp, err := h.GetFee(feeBuilder); resp != float64(0.001) || err != nil {
if resp, err := h.GetFee(feeBuilder); resp != float64(0.002) || err != nil {
t.Error(err)
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp)
}
@@ -111,7 +113,7 @@ func TestGetFee(t *testing.T) {
feeBuilder = setFeeBuilder()
feeBuilder.Amount = 1000
feeBuilder.PurchasePrice = 1000
if resp, err := h.GetFee(feeBuilder); resp != float64(1000) || err != nil {
if resp, err := h.GetFee(feeBuilder); resp != float64(2000) || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2000), resp)
t.Error(err)
}
@@ -119,7 +121,7 @@ func TestGetFee(t *testing.T) {
// CryptocurrencyTradeFee IsMaker
feeBuilder = setFeeBuilder()
feeBuilder.IsMaker = true
if resp, err := h.GetFee(feeBuilder); resp != float64(-0.0001) || err != nil {
if resp, err := h.GetFee(feeBuilder); resp != float64(0.001) || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp)
t.Error(err)
}
@@ -127,7 +129,7 @@ func TestGetFee(t *testing.T) {
// CryptocurrencyTradeFee Negative purchase price
feeBuilder = setFeeBuilder()
feeBuilder.PurchasePrice = -1000
if resp, err := h.GetFee(feeBuilder); resp != float64(-1) || err != nil {
if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp)
t.Error(err)
}
@@ -135,7 +137,7 @@ func TestGetFee(t *testing.T) {
// CryptocurrencyWithdrawalFee Basic
feeBuilder = setFeeBuilder()
feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee
if resp, err := h.GetFee(feeBuilder); resp != float64(0.009580) || err != nil {
if resp, err := h.GetFee(feeBuilder); resp != float64(0.042800) || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.042800), resp)
t.Error(err)
}
@@ -387,16 +389,25 @@ func TestGetDepositAddress(t *testing.T) {
}
}
func setupWsAuth(t *testing.T) {
if wsSetupRan {
return
}
TestSetDefaults(t)
TestSetup(t)
if !h.Websocket.IsEnabled() && !h.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
var err error
var dialer websocket.Dialer
h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
h.WebsocketConn, _, err = dialer.Dial(hitbtcWebsocketAddress, http.Header{})
h.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: h.Name,
URL: hitbtcWebsocketAddress,
Verbose: h.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var dialer websocket.Dialer
err := h.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
@@ -409,84 +420,86 @@ func setupWsAuth(t *testing.T) {
case <-timer.C:
}
timer.Stop()
wsSetupRan = true
}
// TestWsCancelOrder dials websocket, sends cancel request.
func TestWsCancelOrder(t *testing.T) {
setupWsAuth(t)
err := h.wsCancelOrder("ImNotARealOrderID")
if !canManipulateRealOrders {
t.Skip("canManipulateRealOrders false, skipping test")
}
_, err := h.wsCancelOrder("ImNotARealOrderID")
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case <-h.Websocket.DataHandler:
case <-timer.C:
t.Error("Expecting response")
}
timer.Stop()
}
// TestWsPlaceOrder dials websocket, sends order submission.
func TestWsPlaceOrder(t *testing.T) {
setupWsAuth(t)
err := h.wsPlaceOrder(currency.NewPair(currency.LTC, currency.BTC), exchange.BuyOrderSide.ToString(), 1, 1)
if !canManipulateRealOrders {
t.Skip("canManipulateRealOrders false, skipping test")
}
_, err := h.wsPlaceOrder(currency.NewPair(currency.LTC, currency.BTC), exchange.BuyOrderSide.ToString(), 1, 1)
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case <-h.Websocket.DataHandler:
case <-timer.C:
t.Error("Expecting response")
}
timer.Stop()
}
// TestWsReplaceOrder dials websocket, sends replace order request.
func TestWsReplaceOrder(t *testing.T) {
setupWsAuth(t)
err := h.wsReplaceOrder("ImNotARealOrderID", 1, 1)
if !canManipulateRealOrders {
t.Skip("canManipulateRealOrders false, skipping test")
}
_, err := h.wsReplaceOrder("ImNotARealOrderID", 1, 1)
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case <-h.Websocket.DataHandler:
case <-timer.C:
t.Error("Expecting response")
}
timer.Stop()
}
// TestWsGetActiveOrders dials websocket, sends get active orders request.
func TestWsGetActiveOrders(t *testing.T) {
setupWsAuth(t)
err := h.wsGetActiveOrders()
_, err := h.wsGetActiveOrders()
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case <-h.Websocket.DataHandler:
case <-timer.C:
t.Error("Expecting response")
}
timer.Stop()
}
// TestWsGetTradingBalance dials websocket, sends get trading balance request.
func TestWsGetTradingBalance(t *testing.T) {
setupWsAuth(t)
err := h.wsGetTradingBalance()
_, err := h.wsGetTradingBalance()
if err != nil {
t.Fatal(err)
}
}
// TestWsGetTradingBalance dials websocket, sends get trading balance request.
func TestWsGetTrades(t *testing.T) {
setupWsAuth(t)
_, err := h.wsGetTrades(currency.NewPair(currency.ETH, currency.BTC), 1000, "ASC", "id")
if err != nil {
t.Fatal(err)
}
}
// TestWsGetTradingBalance dials websocket, sends get trading balance request.
func TestWsGetSymbols(t *testing.T) {
setupWsAuth(t)
_, err := h.wsGetSymbols(currency.NewPair(currency.ETH, currency.BTC))
if err != nil {
t.Fatal(err)
}
}
// TestWsGetTradingBalance dials websocket, sends get trading balance request.
func TestSsGetCurrencies(t *testing.T) {
setupWsAuth(t)
_, err := h.wsGetCurrencies(currency.BTC)
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case <-h.Websocket.DataHandler:
case <-timer.C:
t.Error("Expecting response")
}
timer.Stop()
}

View File

@@ -299,13 +299,16 @@ type LendingHistory struct {
}
type capture struct {
Method string `json:"method,omitempty"`
Result interface{} `json:"result"`
Error struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
ID int64 `json:"id,omitempty"`
Method string `json:"method,omitempty"`
Result interface{} `json:"result"`
Error ResponseError `json:"error,omitempty"`
ID int64 `json:"id,omitempty"`
}
// ResponseError contains error codes from JSON responses
type ResponseError struct {
Code int `json:"code"`
Message string `json:"message"`
}
// WsRequest defines a request obj for the JSON-RPC and gets a websocket
@@ -393,12 +396,13 @@ type WsLoginData struct {
// WsActiveOrdersResponse Active order response for auth subscription to reports
type WsActiveOrdersResponse struct {
Params []WsActiveOrdersResponseData `json:"params"`
Error ResponseError `json:"error,omitempty"`
}
// WsActiveOrdersResponseData Active order data for WsActiveOrdersResponse
type WsActiveOrdersResponseData struct {
ID string `json:"id"`
ClientOrderID string `json:"clientOrderId"`
ClientOrderID string `json:"clientOrderId,omitempty"`
Symbol currency.Pair `json:"symbol"`
Side string `json:"side"`
Status string `json:"status"`
@@ -416,12 +420,13 @@ type WsActiveOrdersResponseData struct {
// WsReportResponse report response for auth subscription to reports
type WsReportResponse struct {
Params WsReportResponseData `json:"params"`
Error ResponseError `json:"error,omitempty"`
}
// WsReportResponseData Report data for WsReportResponse
type WsReportResponseData struct {
ID string `json:"id"`
ClientOrderID string `json:"clientOrderId"`
ClientOrderID string `json:"clientOrderId,omitempty"`
Symbol currency.Pair `json:"symbol"`
Side string `json:"side"`
Status string `json:"status"`
@@ -449,7 +454,7 @@ type WsSubmitOrderRequest struct {
// WsSubmitOrderRequestData WS request data
type WsSubmitOrderRequestData struct {
ClientOrderID string `json:"clientOrderId"`
ClientOrderID int64 `json:"clientOrderId,string,omitempty"`
Symbol currency.Pair `json:"symbol"`
Side string `json:"side"`
Price float64 `json:"price,string"`
@@ -460,6 +465,7 @@ type WsSubmitOrderRequestData struct {
type WsSubmitOrderSuccessResponse struct {
Result WsSubmitOrderSuccessResponseData `json:"result"`
ID int64 `json:"id"`
Error ResponseError `json:"error,omitempty"`
}
// WsSubmitOrderSuccessResponseData WS response data
@@ -482,7 +488,7 @@ type WsSubmitOrderSuccessResponseData struct {
// WsSubmitOrderErrorResponse WS error response
type WsSubmitOrderErrorResponse struct {
Error WsSubmitOrderErrorResponseData `json:"error"`
Error WsSubmitOrderErrorResponseData `json:"error,omitempty"`
ID int64 `json:"id"`
}
@@ -497,12 +503,13 @@ type WsSubmitOrderErrorResponseData struct {
type WsCancelOrderResponse struct {
Result WsCancelOrderResponseData `json:"result"`
ID int64 `json:"id"`
Error ResponseError `json:"error,omitempty"`
}
// WsCancelOrderResponseData WS response data
type WsCancelOrderResponseData struct {
ID string `json:"id"`
ClientOrderID string `json:"clientOrderId"`
ClientOrderID string `json:"clientOrderId,omitempty"`
Symbol currency.Pair `json:"symbol"`
Side string `json:"side"`
Status string `json:"status"`
@@ -521,12 +528,13 @@ type WsCancelOrderResponseData struct {
type WsReplaceOrderResponse struct {
Result WsReplaceOrderResponseData `json:"result"`
ID int64 `json:"id"`
Error ResponseError `json:"error,omitempty"`
}
// WsReplaceOrderResponseData WS response data
type WsReplaceOrderResponseData struct {
ID string `json:"id"`
ClientOrderID string `json:"clientOrderId"`
ClientOrderID string `json:"clientOrderId,omitempty"`
Symbol currency.Pair `json:"symbol"`
Side string `json:"side"`
Status string `json:"status"`
@@ -546,12 +554,13 @@ type WsReplaceOrderResponseData struct {
type WsGetActiveOrdersResponse struct {
Result []WsGetActiveOrdersResponseData `json:"result"`
ID int64 `json:"id"`
Error ResponseError `json:"error,omitempty"`
}
// WsGetActiveOrdersResponseData WS response data
type WsGetActiveOrdersResponseData struct {
ID string `json:"id"`
ClientOrderID string `json:"clientOrderId"`
ClientOrderID string `json:"clientOrderId,omitempty"`
Symbol currency.Pair `json:"symbol"`
Side string `json:"side"`
Status string `json:"status"`
@@ -571,6 +580,7 @@ type WsGetActiveOrdersResponseData struct {
type WsGetTradingBalanceResponse struct {
Result []WsGetTradingBalanceResponseData `json:"result"`
ID int64 `json:"id"`
Error ResponseError `json:"error,omitempty"`
}
// WsGetTradingBalanceResponseData WS response data
@@ -606,3 +616,106 @@ type WsReplaceOrderRequestData struct {
Quantity float64 `json:"quantity,string,omitempty"`
Price float64 `json:"price,string,omitempty"`
}
// WsGetCurrenciesRequest gets currencies
type WsGetCurrenciesRequest struct {
Method string `json:"method"`
Params WsGetCurrenciesRequestParameters `json:"params"`
ID int64 `json:"id"`
}
// WsGetCurrenciesRequestParameters parameters
type WsGetCurrenciesRequestParameters struct {
Currency currency.Code `json:"currency"`
}
// WsGetCurrenciesResponse currency response
type WsGetCurrenciesResponse struct {
Result WsGetCurrenciesResponseData `json:"result"`
ID int64 `json:"id"`
Error ResponseError `json:"error,omitempty"`
}
// WsGetCurrenciesResponseData currency response data
type WsGetCurrenciesResponseData struct {
ID currency.Code `json:"id"`
FullName string `json:"fullName"`
Crypto bool `json:"crypto"`
PayinEnabled bool `json:"payinEnabled"`
PayinPaymentID bool `json:"payinPaymentId"`
PayinConfirmations int64 `json:"payinConfirmations"`
PayoutEnabled bool `json:"payoutEnabled"`
PayoutIsPaymentID bool `json:"payoutIsPaymentId"`
TransferEnabled bool `json:"transferEnabled"`
Delisted bool `json:"delisted"`
PayoutFee string `json:"payoutFee"`
}
// WsGetSymbolsRequest request data
type WsGetSymbolsRequest struct {
Method string `json:"method"`
Params WsGetSymbolsRequestParameters `json:"params"`
ID int64 `json:"id"`
}
// WsGetSymbolsRequestParameters request parameters
type WsGetSymbolsRequestParameters struct {
Symbol currency.Pair `json:"symbol"`
}
// WsGetSymbolsResponse symbol response
type WsGetSymbolsResponse struct {
Result WsGetSymbolsResponseData `json:"result"`
ID int64 `json:"id"`
Error ResponseError `json:"error,omitempty"`
}
// WsGetSymbolsResponseData symbol response data
type WsGetSymbolsResponseData struct {
ID currency.Pair `json:"id"`
BaseCurrency currency.Code `json:"baseCurrency"`
QuoteCurrency currency.Code `json:"quoteCurrency"`
QuantityIncrement float64 `json:"quantityIncrement,string"`
TickSize float64 `json:"tickSize,string"`
TakeLiquidityRate float64 `json:"takeLiquidityRate,string"`
ProvideLiquidityRate float64 `json:"provideLiquidityRate,string"`
FeeCurrency currency.Code `json:"feeCurrency"`
}
// WsGetTradesRequest trade request
type WsGetTradesRequest struct {
Method string `json:"method"`
Params WsGetTradesRequestParameters `json:"params"`
ID int64 `json:"id"`
}
// WsGetTradesRequestParameters trade request params
type WsGetTradesRequestParameters struct {
Symbol currency.Pair `json:"symbol"`
Limit int64 `json:"limit"`
Sort string `json:"sort"`
By string `json:"by"`
}
// WsGetTradesResponse response
type WsGetTradesResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result WsGetTradesResponseData `json:"result"`
ID int64 `json:"id"`
Error ResponseError `json:"error,omitempty"`
}
// WsGetTradesResponseData trade response data
type WsGetTradesResponseData struct {
Data []WsGetTradesResponseTrades `json:"data"`
Symbol string `json:"symbol"`
}
// WsGetTradesResponseTrades trade response
type WsGetTradesResponseTrades struct {
ID int64 `json:"id"`
Price float64 `json:"price,string"`
Quantity float64 `json:"quantity,string"`
Side string `json:"side"`
Timestamp time.Time `json:"timestamp"`
}

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
@@ -14,12 +13,14 @@ import (
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/nonce"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
log "github.com/thrasher-/gocryptotrader/logger"
)
const (
hitbtcWebsocketAddress = "wss://api.hitbtc.com/api/2/ws"
rpcVersion = "2.0"
rateLimit = 20
)
var requestID nonce.Nonce
@@ -27,26 +28,13 @@ var requestID nonce.Nonce
// WsConnect starts a new connection with the websocket API
func (h *HitBTC) WsConnect() error {
if !h.Websocket.IsEnabled() || !h.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if h.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(h.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
h.WebsocketConn, _, err = dialer.Dial(hitbtcWebsocketAddress, http.Header{})
err := h.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
go h.WsHandleData()
err = h.wsLogin()
if err != nil {
@@ -58,17 +46,6 @@ func (h *HitBTC) WsConnect() error {
return nil
}
// WsReadData reads from the websocket connection
func (h *HitBTC) WsReadData() (exchange.WebsocketResponse, error) {
_, resp, err := h.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
h.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{Raw: resp}, nil
}
// WsHandleData handles websocket data
func (h *HitBTC) WsHandleData() {
h.Websocket.Wg.Add(1)
@@ -83,11 +60,12 @@ func (h *HitBTC) WsHandleData() {
return
default:
resp, err := h.WsReadData()
resp, err := h.WebsocketConn.ReadMessage()
if err != nil {
h.Websocket.DataHandler <- err
return
}
h.Websocket.TrafficAlert <- struct{}{}
var init capture
err = common.JSONDecode(resp.Raw, &init)
@@ -95,11 +73,14 @@ func (h *HitBTC) WsHandleData() {
h.Websocket.DataHandler <- err
continue
}
if init.Error.Code == 1002 {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
if init.ID > 0 {
h.WebsocketConn.AddResponseWithID(init.ID, resp.Raw)
continue
}
if init.Error.Message != "" || init.Error.Code != 0 {
if init.Error.Code == 1002 {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
h.Websocket.DataHandler <- fmt.Errorf("hitbtc.go error - Code: %d, Message: %s",
init.Error.Code,
init.Error.Message)
@@ -117,7 +98,7 @@ func (h *HitBTC) WsHandleData() {
}
}
func (h *HitBTC) handleSubscriptionUpdates(resp exchange.WebsocketResponse, init capture) {
func (h *HitBTC) handleSubscriptionUpdates(resp wshandler.WebsocketResponse, init capture) {
switch init.Method {
case "ticker":
var ticker WsTicker
@@ -131,7 +112,7 @@ func (h *HitBTC) handleSubscriptionUpdates(resp exchange.WebsocketResponse, init
h.Websocket.DataHandler <- err
return
}
h.Websocket.DataHandler <- exchange.TickerData{
h.Websocket.DataHandler <- wshandler.TickerData{
Exchange: h.GetName(),
AssetType: "SPOT",
Pair: currency.NewPairFromString(ticker.Params.Symbol),
@@ -187,7 +168,7 @@ func (h *HitBTC) handleSubscriptionUpdates(resp exchange.WebsocketResponse, init
}
}
func (h *HitBTC) handleCommandResponses(resp exchange.WebsocketResponse, init capture) {
func (h *HitBTC) handleCommandResponses(resp wshandler.WebsocketResponse, init capture) {
switch resultType := init.Result.(type) {
case map[string]interface{}:
switch resultType["reportType"].(string) {
@@ -266,7 +247,7 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error {
return err
}
h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: h.GetName(),
Asset: "SPOT",
Pair: p,
@@ -297,7 +278,7 @@ func (h *HitBTC) WsProcessOrderbookUpdate(ob WsOrderbook) error {
return err
}
h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: h.GetName(),
Asset: "SPOT",
Pair: p,
@@ -308,9 +289,9 @@ func (h *HitBTC) WsProcessOrderbookUpdate(ob WsOrderbook) error {
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (h *HitBTC) GenerateDefaultSubscriptions() {
var channels = []string{"subscribeTicker", "subscribeOrderbook", "subscribeTrades", "subscribeCandles"}
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
if h.Websocket.CanUseAuthenticatedEndpoints() {
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: "subscribeReports",
})
}
@@ -318,7 +299,7 @@ func (h *HitBTC) GenerateDefaultSubscriptions() {
for i := range channels {
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = ""
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channels[i],
Currency: enabledCurrencies[j],
})
@@ -328,7 +309,7 @@ func (h *HitBTC) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (h *HitBTC) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (h *HitBTC) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
subscribe := WsNotification{
Method: channelToSubscribe.Channel,
}
@@ -350,11 +331,11 @@ func (h *HitBTC) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscript
}
}
return h.wsSend(subscribe)
return h.WebsocketConn.SendMessage(subscribe)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (h *HitBTC) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (h *HitBTC) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
unsubscribeChannel := strings.Replace(channelToSubscribe.Channel, "subscribe", "unsubscribe", 1)
subscribe := WsNotification{
JSONRPCVersion: rpcVersion,
@@ -376,21 +357,7 @@ func (h *HitBTC) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscri
}
}
return h.wsSend(subscribe)
}
// WsSend sends data to the websocket server
func (h *HitBTC) wsSend(data interface{}) error {
h.wsRequestMtx.Lock()
defer h.wsRequestMtx.Unlock()
json, err := common.JSONEncode(data)
if err != nil {
return err
}
if h.Verbose {
log.Debugf("%v sending message to websocket %v", h.Name, string(json))
}
return h.WebsocketConn.WriteMessage(websocket.TextMessage, json)
return h.WebsocketConn.SendMessage(subscribe)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
@@ -411,7 +378,7 @@ func (h *HitBTC) wsLogin() error {
},
}
err := h.wsSend(request)
err := h.WebsocketConn.SendMessage(request)
if err != nil {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
return err
@@ -420,43 +387,68 @@ func (h *HitBTC) wsLogin() error {
}
// wsPlaceOrder sends a websocket message to submit an order
func (h *HitBTC) wsPlaceOrder(pair currency.Pair, side string, price, quantity float64) error {
func (h *HitBTC) wsPlaceOrder(pair currency.Pair, side string, price, quantity float64) (*WsSubmitOrderSuccessResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated, cannot place order", h.Name)
return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name)
}
id := h.WebsocketConn.GenerateMessageID(false)
request := WsSubmitOrderRequest{
Method: "newOrder",
Params: WsSubmitOrderRequestData{
ClientOrderID: fmt.Sprintf("%v", time.Now().Unix()),
ClientOrderID: id,
Symbol: pair,
Side: common.StringToLower(side),
Price: price,
Quantity: quantity,
},
ID: int64(requestID.GetInc()),
ID: id,
}
return h.wsSend(request)
resp, err := h.WebsocketConn.SendMessageReturnResponse(id, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsSubmitOrderSuccessResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsCancelOrder sends a websocket message to cancel an order
func (h *HitBTC) wsCancelOrder(clientOrderID string) error {
func (h *HitBTC) wsCancelOrder(clientOrderID string) (*WsCancelOrderResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated, cannot place order", h.Name)
return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name)
}
request := WsCancelOrderRequest{
Method: "cancelOrder",
Params: WsCancelOrderRequestData{
ClientOrderID: clientOrderID,
},
ID: int64(requestID.GetInc()),
ID: h.WebsocketConn.GenerateMessageID(false),
}
return h.wsSend(request)
resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsCancelOrderResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsReplaceOrder sends a websocket message to replace an order
func (h *HitBTC) wsReplaceOrder(clientOrderID string, quantity, price float64) error {
func (h *HitBTC) wsReplaceOrder(clientOrderID string, quantity, price float64) (*WsReplaceOrderResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated, cannot place order", h.Name)
return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name)
}
request := WsReplaceOrderRequest{
Method: "cancelReplaceOrder",
@@ -466,33 +458,144 @@ func (h *HitBTC) wsReplaceOrder(clientOrderID string, quantity, price float64) e
Quantity: quantity,
Price: price,
},
ID: int64(requestID.GetInc()),
ID: h.WebsocketConn.GenerateMessageID(false),
}
return h.wsSend(request)
resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsReplaceOrderResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsGetActiveOrders sends a websocket message to get all active orders
func (h *HitBTC) wsGetActiveOrders() error {
func (h *HitBTC) wsGetActiveOrders() (*WsActiveOrdersResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated, cannot place order", h.Name)
return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name)
}
request := WsReplaceOrderRequest{
Method: "getOrders",
Params: WsReplaceOrderRequestData{},
ID: int64(requestID.GetInc()),
ID: h.WebsocketConn.GenerateMessageID(false),
}
return h.wsSend(request)
resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsActiveOrdersResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsGetTradingBalance sends a websocket message to get trading balance
func (h *HitBTC) wsGetTradingBalance() error {
func (h *HitBTC) wsGetTradingBalance() (*WsGetTradingBalanceResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated, cannot place order", h.Name)
return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name)
}
request := WsReplaceOrderRequest{
Method: "getTradingBalance",
Params: WsReplaceOrderRequestData{},
ID: int64(requestID.GetInc()),
ID: h.WebsocketConn.GenerateMessageID(false),
}
return h.wsSend(request)
resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsGetTradingBalanceResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsGetCurrencies sends a websocket message to get trading balance
func (h *HitBTC) wsGetCurrencies(currencyItem currency.Code) (*WsGetCurrenciesResponse, error) {
request := WsGetCurrenciesRequest{
Method: "getCurrency",
Params: WsGetCurrenciesRequestParameters{
Currency: currencyItem,
},
ID: h.WebsocketConn.GenerateMessageID(false),
}
resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsGetCurrenciesResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsGetSymbols sends a websocket message to get trading balance
func (h *HitBTC) wsGetSymbols(currencyItem currency.Pair) (*WsGetSymbolsResponse, error) {
request := WsGetSymbolsRequest{
Method: "getSymbol",
Params: WsGetSymbolsRequestParameters{
Symbol: currencyItem,
},
ID: h.WebsocketConn.GenerateMessageID(false),
}
resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsGetSymbolsResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsGetSymbols sends a websocket message to get trading balance
func (h *HitBTC) wsGetTrades(currencyItem currency.Pair, limit int64, sort, by string) (*WsGetTradesResponse, error) {
request := WsGetTradesRequest{
Method: "getTrades",
Params: WsGetTradesRequestParameters{
Symbol: currencyItem,
Limit: limit,
Sort: sort,
By: by,
},
ID: h.WebsocketConn.GenerateMessageID(false),
}
resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsGetTradesResponse
err = common.JSONDecode(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}

View File

@@ -12,7 +12,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"
)
@@ -285,7 +285,7 @@ func (h *HitBTC) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.
}
// GetWebsocket returns a pointer to the exchange websocket
func (h *HitBTC) GetWebsocket() (*exchange.Websocket, error) {
func (h *HitBTC) GetWebsocket() (*wshandler.Websocket, error) {
return h.Websocket, nil
}
@@ -376,20 +376,20 @@ func (h *HitBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (h *HitBTC) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (h *HitBTC) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
h.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (h *HitBTC) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
h.Websocket.UnsubscribeToChannels(channels)
func (h *HitBTC) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
h.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (h *HitBTC) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (h *HitBTC) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return h.Websocket.GetSubscriptions(), nil
}

View File

@@ -14,16 +14,15 @@ import (
"net/url"
"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"
)
@@ -69,9 +68,8 @@ const (
type HUOBI struct {
exchange.Base
AccountID string
WebsocketConn *websocket.Conn
AuthenticatedWebsocketConn *websocket.Conn
wsRequestMtx sync.Mutex
WebsocketConn *wshandler.WebsocketConnection
AuthenticatedWebsocketConn *wshandler.WebsocketConnection
}
// SetDefaults sets default values for the exchange
@@ -96,14 +94,17 @@ func (h *HUOBI) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
h.APIUrlDefault = huobiAPIURL
h.APIUrl = h.APIUrlDefault
h.WebsocketInit()
h.Websocket.Functionality = exchange.WebsocketKlineSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketTradeDataSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported |
exchange.WebsocketAccountDataSupported
h.Websocket = wshandler.New()
h.Websocket.Functionality = wshandler.WebsocketKlineSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported |
wshandler.WebsocketAccountDataSupported |
wshandler.WebsocketMessageCorrelationSupported
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup sets user configuration
@@ -146,17 +147,36 @@ func (h *HUOBI) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = h.WebsocketSetup(h.WsConnect,
err = h.Websocket.Setup(h.WsConnect,
h.Subscribe,
h.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
wsMarketURL,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
h.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: h.Name,
URL: wsMarketURL,
ProxyURL: h.Websocket.GetProxyAddress(),
Verbose: h.Verbose,
RateLimit: rateLimit,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
h.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: h.Name,
URL: wsAccountsOrdersURL,
ProxyURL: h.Websocket.GetProxyAddress(),
Verbose: h.Verbose,
RateLimit: rateLimit,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -9,7 +9,6 @@ import (
"strconv"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
@@ -17,6 +16,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 you own test keys here for due diligence testing.
@@ -56,37 +56,36 @@ func setupWsTests(t *testing.T) {
TestSetDefaults(t)
TestSetup(t)
if !h.Websocket.IsEnabled() && !h.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
var err error
var dialer websocket.Dialer
comms = make(chan WsMessage, sharedtestvalues.WebsocketChannelOverrideCapacity)
h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
go h.WsHandleData()
err = h.wsAuthenticatedDial(&dialer)
h.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: h.Name,
URL: wsAccountsOrdersURL,
Verbose: h.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
h.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: h.Name,
URL: wsMarketURL,
Verbose: h.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var dialer websocket.Dialer
err := h.wsAuthenticatedDial(&dialer)
if err != nil {
t.Error(err)
t.Fatal(err)
}
err = h.wsLogin()
if err != nil {
t.Error(err)
t.Fatal(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case response := <-h.Websocket.DataHandler:
switch respType := response.(type) {
case WsAuthenticatedDataResponse:
if respType.ErrorCode > 0 {
t.Error(respType)
}
case error:
t.Error(respType)
}
case <-timer.C:
t.Error("Websocket did not receive a response")
}
timer.Stop()
wsSetupRan = true
}
@@ -656,46 +655,36 @@ func TestGetDepositAddress(t *testing.T) {
// TestWsGetAccountsList connects to WS, logs in, gets account list
func TestWsGetAccountsList(t *testing.T) {
setupWsTests(t)
h.wsGetAccountsList(currency.NewPairFromString("ethbtc"))
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case response := <-h.Websocket.DataHandler:
switch respType := response.(type) {
case WsAuthenticatedAccountsListResponse:
if respType.ErrorCode > 0 {
t.Error(respType)
}
case error:
t.Error(respType)
}
case <-timer.C:
t.Error("Websocket did not receive a response")
resp, err := h.wsGetAccountsList(currency.NewPairFromString("ethbtc"))
if err != nil {
t.Fatal(err)
}
if resp.ErrorCode > 0 {
t.Error(resp.ErrorMessage)
}
timer.Stop()
}
// TestWsGetOrderList connects to WS, logs in, gets order list
func TestWsGetOrderList(t *testing.T) {
setupWsTests(t)
h.wsGetOrdersList(1, currency.NewPairFromString("ethbtc"))
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case <-h.Websocket.DataHandler:
case <-timer.C:
t.Error("Websocket did not receive a response")
resp, err := h.wsGetOrdersList(1, currency.NewPairFromString("ethbtc"))
if err != nil {
t.Fatal(err)
}
if resp.ErrorCode > 0 {
t.Error(resp.ErrorMessage)
}
timer.Stop()
}
// TestWsGetOrderDetails connects to WS, logs in, gets order details
func TestWsGetOrderDetails(t *testing.T) {
setupWsTests(t)
h.wsGetOrderDetails("123")
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case <-h.Websocket.DataHandler:
case <-timer.C:
t.Error("Websocket did not receive a response")
orderID := "123"
resp, err := h.wsGetOrderDetails(orderID)
if err != nil {
t.Fatal(err)
}
if resp.ErrorCode > 0 && (orderID == "123" && resp.ErrorCode != 10022) {
t.Error(resp.ErrorMessage)
}
timer.Stop()
}

View File

@@ -270,10 +270,10 @@ var (
// WsRequest defines a request data structure
type WsRequest struct {
Topic string `json:"req,omitempty"`
Subscribe string `json:"sub,omitempty"`
Unsubscribe string `json:"unsub,omitempty"`
ClientGeneratedID string `json:"id,omitempty"`
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
@@ -286,6 +286,7 @@ type WsResponse struct {
Ping int64 `json:"ping"`
Channel string `json:"ch"`
Subscribed string `json:"subbed"`
ClientID int64 `json:"cid,string,omitempty"`
}
// WsHeartBeat defines a heartbeat request
@@ -346,6 +347,7 @@ type WsAuthenticationRequest struct {
SignatureVersion string `json:"SignatureVersion"`
Timestamp string `json:"Timestamp"`
Signature string `json:"Signature"`
ClientID int64 `json:"cid,string,omitempty"`
}
// WsMessage defines read data from the websocket connection
@@ -363,6 +365,7 @@ 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
@@ -375,6 +378,7 @@ type WsAuthenticatedAccountsListRequest struct {
Signature string `json:"Signature"`
Topic string `json:"topic"`
Symbol currency.Pair `json:"symbol"`
ClientID int64 `json:"cid,string,omitempty"`
}
// WsAuthenticatedOrderDetailsRequest request for order details authenticated connection
@@ -387,6 +391,7 @@ 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
@@ -401,6 +406,7 @@ type WsAuthenticatedOrdersListRequest struct {
States string `json:"states"`
AccountID int64 `json:"account-id"`
Symbol currency.Pair `json:"symbol"`
ClientID int64 `json:"cid,string,omitempty"`
}
// WsAuthenticatedDataResponse response from authenticated connection
@@ -411,7 +417,7 @@ type WsAuthenticatedDataResponse struct {
ErrorCode int64 `json:"err-code,omitempty"`
ErrorMessage string `json:"err-msg,omitempty"`
Ping int64 `json:"ping,omitempty"`
CID string `json:"cid,omitempty"`
ClientID int64 `json:"cid,string,omitempty"`
}
// WsAuthenticatedAccountsResponse response from Accounts authenticated subscription
@@ -529,3 +535,8 @@ type WsAuthenticatedOrderDetailResponse struct {
WsAuthenticatedDataResponse
Data WsAuthenticatedOrdersListResponseData `json:"data"`
}
// WsPong sent for pong messages
type WsPong struct {
Pong int64 `json:"pong"`
}

View File

@@ -1,11 +1,8 @@
package huobi
import (
"bytes"
"compress/gzip"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
@@ -16,6 +13,7 @@ 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"
)
@@ -42,6 +40,9 @@ const (
signatureVersion = "2"
requestOp = "req"
authOp = "auth"
loginDelay = 50 * time.Millisecond
rateLimit = 20
)
// Instantiates a communications channel between websocket connections
@@ -50,20 +51,9 @@ var comms = make(chan WsMessage, 1)
// WsConnect initiates a new websocket connection
func (h *HUOBI) WsConnect() error {
if !h.Websocket.IsEnabled() || !h.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if h.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(h.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
err := h.wsDial(&dialer)
if err != nil {
return err
@@ -84,11 +74,9 @@ func (h *HUOBI) WsConnect() error {
}
func (h *HUOBI) wsDial(dialer *websocket.Dialer) error {
var err error
var conStatus *http.Response
h.WebsocketConn, conStatus, err = dialer.Dial(wsMarketURL, http.Header{})
err := h.WebsocketConn.Dial(dialer, http.Header{})
if err != nil {
return fmt.Errorf("%v %v %v Error: %v", wsMarketURL, conStatus, conStatus.StatusCode, err)
return err
}
go h.wsMultiConnectionFunnel(h.WebsocketConn, wsMarketURL)
return nil
@@ -98,18 +86,16 @@ func (h *HUOBI) wsAuthenticatedDial(dialer *websocket.Dialer) error {
if !h.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", h.Name)
}
var err error
var conStatus *http.Response
h.AuthenticatedWebsocketConn, conStatus, err = dialer.Dial(wsAccountsOrdersURL, http.Header{})
err := h.AuthenticatedWebsocketConn.Dial(dialer, http.Header{})
if err != nil {
return fmt.Errorf("%v %v %v Error: %v", wsAccountsOrdersURL, conStatus, conStatus.StatusCode, err)
return err
}
go h.wsMultiConnectionFunnel(h.AuthenticatedWebsocketConn, wsAccountsOrdersURL)
return nil
}
// wsMultiConnectionFunnel manages data from multiple endpoints and passes it to a channel
func (h *HUOBI) wsMultiConnectionFunnel(ws *websocket.Conn, url string) {
func (h *HUOBI) wsMultiConnectionFunnel(ws *wshandler.WebsocketConnection, url string) {
h.Websocket.Wg.Add(1)
defer h.Websocket.Wg.Done()
for {
@@ -117,29 +103,13 @@ func (h *HUOBI) wsMultiConnectionFunnel(ws *websocket.Conn, url string) {
case <-h.Websocket.ShutdownC:
return
default:
_, resp, err := ws.ReadMessage()
resp, err := ws.ReadMessage()
if err != nil {
h.Websocket.DataHandler <- err
return
}
h.Websocket.TrafficAlert <- struct{}{}
b := bytes.NewReader(resp)
gReader, err := gzip.NewReader(b)
if err != nil {
h.Websocket.DataHandler <- err
return
}
unzipped, err := ioutil.ReadAll(gReader)
if err != nil {
h.Websocket.DataHandler <- err
return
}
err = gReader.Close()
if err != nil {
h.Websocket.DataHandler <- err
return
}
comms <- WsMessage{Raw: unzipped, URL: url}
comms <- WsMessage{Raw: resp.Raw, URL: url}
}
}
}
@@ -153,9 +123,6 @@ func (h *HUOBI) WsHandleData() {
case <-h.Websocket.ShutdownC:
return
case resp := <-comms:
if h.Verbose {
log.Debugf("%v: %v: %v", h.Name, resp.URL, string(resp.Raw))
}
switch resp.URL {
case wsMarketURL:
h.wsHandleMarketData(resp)
@@ -173,31 +140,26 @@ func (h *HUOBI) wsHandleAuthenticatedData(resp WsMessage) {
h.Websocket.DataHandler <- err
return
}
if init.ErrorCode > 0 {
if init.ErrorMessage == "api-signature-not-valid" {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
h.Websocket.DataHandler <- fmt.Errorf("%v %v Websocket error %v %s",
h.Name,
resp.URL,
init.ErrorCode,
init.ErrorMessage)
return
}
if init.Ping != 0 {
err = h.WebsocketConn.WriteJSON(`{"pong":1337}`)
err = h.WebsocketConn.SendMessage(WsPong{Pong: init.Ping})
if err != nil {
log.Error(err)
}
return
}
if init.ErrorMessage == "api-signature-not-valid" {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
if init.Op == "sub" {
if h.Verbose {
log.Debugf("%v: %v: Successfully subscribed to %v", h.Name, resp.URL, init.Topic)
}
return
}
if init.ClientID > 0 {
h.AuthenticatedWebsocketConn.AddResponseWithID(init.ClientID, resp.Raw)
return
}
switch {
case strings.EqualFold(init.Op, authOp):
@@ -230,27 +192,6 @@ func (h *HUOBI) wsHandleAuthenticatedData(resp WsMessage) {
h.Websocket.DataHandler <- err
}
h.Websocket.DataHandler <- response
case strings.EqualFold(init.Topic, wsAccountsList):
var response WsAuthenticatedAccountsListResponse
err := common.JSONDecode(resp.Raw, &response)
if err != nil {
h.Websocket.DataHandler <- err
}
h.Websocket.DataHandler <- response
case strings.EqualFold(init.Topic, wsOrdersList):
var response WsAuthenticatedOrdersListResponse
err := common.JSONDecode(resp.Raw, &response)
if err != nil {
h.Websocket.DataHandler <- err
}
h.Websocket.DataHandler <- response
case strings.EqualFold(init.Topic, wsOrdersDetail):
var response WsAuthenticatedOrderDetailResponse
err := common.JSONDecode(resp.Raw, &response)
if err != nil {
h.Websocket.DataHandler <- err
}
h.Websocket.DataHandler <- response
}
}
@@ -273,7 +214,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
return
}
if init.Ping != 0 {
err = h.WebsocketConn.WriteJSON(`{"pong":1337}`)
err = h.WebsocketConn.SendMessage(WsPong{Pong: init.Ping})
if err != nil {
log.Error(err)
}
@@ -298,7 +239,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
return
}
data := common.SplitStrings(kline.Channel, ".")
h.Websocket.DataHandler <- exchange.KlineData{
h.Websocket.DataHandler <- wshandler.KlineData{
Timestamp: time.Unix(0, kline.Timestamp),
Exchange: h.GetName(),
AssetType: "SPOT",
@@ -317,7 +258,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
return
}
data := common.SplitStrings(trade.Channel, ".")
h.Websocket.DataHandler <- exchange.TradeData{
h.Websocket.DataHandler <- wshandler.TradeData{
Exchange: h.GetName(),
AssetType: "SPOT",
CurrencyPair: currency.NewPairFromString(data[1]),
@@ -354,7 +295,7 @@ func (h *HUOBI) WsProcessOrderbook(ob *WsDepth, symbol string) error {
return err
}
h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: p,
Exchange: h.GetName(),
Asset: "SPOT",
@@ -366,10 +307,10 @@ func (h *HUOBI) WsProcessOrderbook(ob *WsDepth, symbol string) error {
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (h *HUOBI) GenerateDefaultSubscriptions() {
var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade}
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
if h.Websocket.CanUseAuthenticatedEndpoints() {
channels = append(channels, "orders.%v", "orders.%v.update")
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: "accounts",
})
}
@@ -378,7 +319,7 @@ func (h *HUOBI) GenerateDefaultSubscriptions() {
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = ""
channel := fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String())
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channel,
Currency: enabledCurrencies[j],
})
@@ -388,39 +329,33 @@ func (h *HUOBI) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (h *HUOBI) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (h *HUOBI) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
if common.StringContains(channelToSubscribe.Channel, "orders.") ||
common.StringContains(channelToSubscribe.Channel, "accounts") {
return h.wsAuthenticatedSubscribe("sub", wsAccountsOrdersEndPoint+channelToSubscribe.Channel, channelToSubscribe.Channel)
}
subscription, err := common.JSONEncode(WsRequest{Subscribe: channelToSubscribe.Channel})
if err != nil {
return err
}
return h.wsSend(subscription)
return h.WebsocketConn.SendMessage(WsRequest{Subscribe: channelToSubscribe.Channel})
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (h *HUOBI) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (h *HUOBI) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
if common.StringContains(channelToSubscribe.Channel, "orders.") ||
common.StringContains(channelToSubscribe.Channel, "accounts") {
return h.wsAuthenticatedSubscribe("unsub", wsAccountsOrdersEndPoint+channelToSubscribe.Channel, channelToSubscribe.Channel)
}
subscription, err := common.JSONEncode(WsRequest{Unsubscribe: channelToSubscribe.Channel})
if err != nil {
return err
}
return h.wsSend(subscription)
return h.WebsocketConn.SendMessage(WsRequest{Unsubscribe: channelToSubscribe.Channel})
}
// WsSend sends data to the websocket server
func (h *HUOBI) wsSend(data []byte) error {
h.wsRequestMtx.Lock()
defer h.wsRequestMtx.Unlock()
if h.Verbose {
log.Debugf("%v sending message to websocket %s", h.Name, string(data))
}
return h.WebsocketConn.WriteMessage(websocket.TextMessage, data)
func (h *HUOBI) wsGenerateSignature(timestamp, endpoint string) []byte {
values := url.Values{}
values.Set("AccessKeyId", h.APIKey)
values.Set("SignatureMethod", signatureMethod)
values.Set("SignatureVersion", signatureVersion)
values.Set("Timestamp", timestamp)
host := "api.huobi.pro"
payload := fmt.Sprintf("%s\n%s\n%s\n%s",
"GET", host, endpoint, values.Encode())
return common.GetHMAC(common.HashSHA256, []byte(payload), []byte(h.APISecret))
}
func (h *HUOBI) wsLogin() error {
@@ -438,39 +373,16 @@ func (h *HUOBI) wsLogin() error {
}
hmac := h.wsGenerateSignature(timestamp, wsAccountsOrdersEndPoint)
request.Signature = common.Base64Encode(hmac)
err := h.wsAuthenticatedSend(request)
err := h.AuthenticatedWebsocketConn.SendMessage(request)
if err != nil {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
return err
}
time.Sleep(loginDelay)
return nil
}
func (h *HUOBI) wsAuthenticatedSend(request interface{}) error {
h.wsRequestMtx.Lock()
defer h.wsRequestMtx.Unlock()
encodedRequest, err := common.JSONEncode(request)
if err != nil {
return err
}
if h.Verbose {
log.Debugf("%v sending Authenticated message to websocket %s", h.Name, string(encodedRequest))
}
return h.AuthenticatedWebsocketConn.WriteMessage(websocket.TextMessage, encodedRequest)
}
func (h *HUOBI) wsGenerateSignature(timestamp, endpoint string) []byte {
values := url.Values{}
values.Set("AccessKeyId", h.APIKey)
values.Set("SignatureMethod", signatureMethod)
values.Set("SignatureVersion", signatureVersion)
values.Set("Timestamp", timestamp)
host := "api.huobi.pro"
payload := fmt.Sprintf("%s\n%s\n%s\n%s",
"GET", host, endpoint, values.Encode())
return common.GetHMAC(common.HashSHA256, []byte(payload), []byte(h.APISecret))
}
func (h *HUOBI) wsAuthenticatedSubscribe(operation, endpoint, topic string) error {
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
request := WsAuthenticatedSubscriptionRequest{
@@ -483,12 +395,12 @@ func (h *HUOBI) wsAuthenticatedSubscribe(operation, endpoint, topic string) erro
}
hmac := h.wsGenerateSignature(timestamp, endpoint)
request.Signature = common.Base64Encode(hmac)
return h.wsAuthenticatedSend(request)
return h.AuthenticatedWebsocketConn.SendMessage(request)
}
func (h *HUOBI) wsGetAccountsList(pair currency.Pair) error {
func (h *HUOBI) wsGetAccountsList(pair currency.Pair) (*WsAuthenticatedAccountsListResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated cannot get accounts list", h.Name)
return nil, fmt.Errorf("%v not authenticated cannot get accounts list", h.Name)
}
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
request := WsAuthenticatedAccountsListRequest{
@@ -502,12 +414,19 @@ func (h *HUOBI) wsGetAccountsList(pair currency.Pair) error {
}
hmac := h.wsGenerateSignature(timestamp, wsAccountListEndpoint)
request.Signature = common.Base64Encode(hmac)
return h.wsAuthenticatedSend(request)
request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true)
resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request)
if err != nil {
return nil, err
}
var response WsAuthenticatedAccountsListResponse
err = common.JSONDecode(resp, &response)
return &response, err
}
func (h *HUOBI) wsGetOrdersList(accountID int64, pair currency.Pair) error {
func (h *HUOBI) wsGetOrdersList(accountID int64, pair currency.Pair) (*WsAuthenticatedOrdersResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated cannot get orders list", h.Name)
return nil, fmt.Errorf("%v not authenticated cannot get orders list", h.Name)
}
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
request := WsAuthenticatedOrdersListRequest{
@@ -523,12 +442,19 @@ func (h *HUOBI) wsGetOrdersList(accountID int64, pair currency.Pair) error {
}
hmac := h.wsGenerateSignature(timestamp, wsOrdersListEndpoint)
request.Signature = common.Base64Encode(hmac)
return h.wsAuthenticatedSend(request)
request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true)
resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request)
if err != nil {
return nil, err
}
var response WsAuthenticatedOrdersResponse
err = common.JSONDecode(resp, &response)
return &response, err
}
func (h *HUOBI) wsGetOrderDetails(orderID string) error {
func (h *HUOBI) wsGetOrderDetails(orderID string) (*WsAuthenticatedOrderDetailResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated cannot get order details", h.Name)
return nil, fmt.Errorf("%v not authenticated cannot get order details", h.Name)
}
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
request := WsAuthenticatedOrderDetailsRequest{
@@ -542,5 +468,12 @@ func (h *HUOBI) wsGetOrderDetails(orderID string) error {
}
hmac := h.wsGenerateSignature(timestamp, wsOrdersDetailEndpoint)
request.Signature = common.Base64Encode(hmac)
return h.wsAuthenticatedSend(request)
request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true)
resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request)
if err != nil {
return nil, err
}
var response WsAuthenticatedOrderDetailResponse
err = common.JSONDecode(resp, &response)
return &response, err
}

View File

@@ -14,6 +14,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"
)
@@ -385,7 +386,7 @@ func (h *HUOBI) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.W
}
// GetWebsocket returns a pointer to the exchange websocket
func (h *HUOBI) GetWebsocket() (*exchange.Websocket, error) {
func (h *HUOBI) GetWebsocket() (*wshandler.Websocket, error) {
return h.Websocket, nil
}
@@ -512,20 +513,20 @@ func setOrderSideAndType(requestType string, orderDetail *exchange.OrderDetail)
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (h *HUOBI) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (h *HUOBI) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
h.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (h *HUOBI) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
h.Websocket.UnsubscribeToChannels(channels)
func (h *HUOBI) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
h.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (h *HUOBI) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (h *HUOBI) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return h.Websocket.GetSubscriptions(), nil
}

View File

@@ -8,16 +8,15 @@ import (
"net/http"
"net/url"
"strconv"
"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"
)
@@ -63,10 +62,9 @@ const (
// HUOBIHADAX is the overarching type across this package
type HUOBIHADAX struct {
WebsocketConn *websocket.Conn
AuthenticatedWebsocketConn *websocket.Conn
WebsocketConn *wshandler.WebsocketConnection
AuthenticatedWebsocketConn *wshandler.WebsocketConnection
exchange.Base
wsRequestMtx sync.Mutex
}
// SetDefaults sets default values for the exchange
@@ -91,14 +89,18 @@ func (h *HUOBIHADAX) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
h.APIUrlDefault = huobihadaxAPIURL
h.APIUrl = h.APIUrlDefault
h.WebsocketInit()
h.Websocket.Functionality = exchange.WebsocketKlineSupported |
exchange.WebsocketTradeDataSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported |
exchange.WebsocketAccountDataSupported
h.Websocket = wshandler.New()
h.Websocket.Functionality = wshandler.WebsocketKlineSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported |
wshandler.WebsocketAccountDataSupported |
wshandler.WebsocketMessageCorrelationSupported
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup sets user configuration
@@ -140,17 +142,36 @@ func (h *HUOBIHADAX) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = h.WebsocketSetup(h.WsConnect,
err = h.Websocket.Setup(h.WsConnect,
h.Subscribe,
h.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
HuobiHadaxSocketIOAddress,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
h.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: h.Name,
URL: HuobiHadaxSocketIOAddress,
ProxyURL: h.Websocket.GetProxyAddress(),
Verbose: h.Verbose,
RateLimit: rateLimit,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
h.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: h.Name,
URL: wsAccountsOrdersURL,
ProxyURL: h.Websocket.GetProxyAddress(),
Verbose: h.Verbose,
RateLimit: rateLimit,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"strconv"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
@@ -12,6 +11,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
@@ -52,37 +52,36 @@ func setupWsTests(t *testing.T) {
TestSetDefaults(t)
TestSetup(t)
if !h.Websocket.IsEnabled() && !h.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
var err error
var dialer websocket.Dialer
comms = make(chan WsMessage, sharedtestvalues.WebsocketChannelOverrideCapacity)
h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
go h.WsHandleData()
err = h.wsAuthenticatedDial(&dialer)
h.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: h.Name,
URL: wsAccountsOrdersURL,
Verbose: h.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
h.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: h.Name,
URL: HuobiHadaxSocketIOAddress,
Verbose: h.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var dialer websocket.Dialer
err := h.wsAuthenticatedDial(&dialer)
if err != nil {
t.Error(err)
t.Fatal(err)
}
err = h.wsLogin()
if err != nil {
t.Error(err)
t.Fatal(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case response := <-h.Websocket.DataHandler:
switch respType := response.(type) {
case WsAuthenticatedDataResponse:
if respType.ErrorCode > 0 {
t.Error(respType)
}
case error:
t.Error(respType)
}
case <-timer.C:
t.Error("Websocket did not receive a response")
}
timer.Stop()
wsSetupRan = true
}
@@ -636,46 +635,36 @@ func TestGetDepositAddress(t *testing.T) {
// TestWsGetAccountsList connects to WS, logs in, gets account list
func TestWsGetAccountsList(t *testing.T) {
setupWsTests(t)
h.wsGetAccountsList(currency.NewPairFromString("ethbtc"))
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case response := <-h.Websocket.DataHandler:
switch respType := response.(type) {
case WsAuthenticatedAccountsListResponse:
if respType.ErrorCode > 0 {
t.Error(respType)
}
case error:
t.Error(respType)
}
case <-timer.C:
t.Error("Websocket did not receive a response")
resp, err := h.wsGetAccountsList(currency.NewPairFromString("ethbtc"))
if err != nil {
t.Fatal(err)
}
if resp.ErrorCode > 0 {
t.Error(resp.ErrorMessage)
}
timer.Stop()
}
// TestWsGetOrderList connects to WS, logs in, gets order list
func TestWsGetOrderList(t *testing.T) {
setupWsTests(t)
h.wsGetOrdersList(1, currency.NewPairFromString("ethbtc"))
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case <-h.Websocket.DataHandler:
case <-timer.C:
t.Error("Websocket did not receive a response")
resp, err := h.wsGetOrdersList(1, currency.NewPairFromString("ethbtc"))
if err != nil {
t.Fatal(err)
}
if resp.ErrorCode > 0 {
t.Error(resp.ErrorMessage)
}
timer.Stop()
}
// TestWsGetOrderDetails connects to WS, logs in, gets order details
func TestWsGetOrderDetails(t *testing.T) {
setupWsTests(t)
h.wsGetOrderDetails("123")
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case <-h.Websocket.DataHandler:
case <-timer.C:
t.Error("Websocket did not receive a response")
orderID := "123"
resp, err := h.wsGetOrderDetails(orderID)
if err != nil {
t.Fatal(err)
}
if resp.ErrorCode > 0 && (orderID == "123" && resp.ErrorCode != 10022) {
t.Error(resp.ErrorMessage)
}
timer.Stop()
}

View File

@@ -340,6 +340,7 @@ type WsAuthenticationRequest struct {
SignatureVersion string `json:"SignatureVersion"`
Timestamp string `json:"Timestamp"`
Signature string `json:"Signature"`
ClientID int64 `json:"cid,string,omitempty"`
}
// WsMessage defines read data from the websocket connection
@@ -357,6 +358,7 @@ 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
@@ -369,6 +371,7 @@ type WsAuthenticatedAccountsListRequest struct {
Signature string `json:"Signature"`
Topic string `json:"topic"`
Symbol currency.Pair `json:"symbol"`
ClientID int64 `json:"cid,string,omitempty"`
}
// WsAuthenticatedOrderDetailsRequest request for order details authenticated connection
@@ -381,6 +384,7 @@ 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
@@ -395,6 +399,7 @@ type WsAuthenticatedOrdersListRequest struct {
States string `json:"states"`
AccountID int64 `json:"account-id"`
Symbol currency.Pair `json:"symbol"`
ClientID int64 `json:"cid,string,omitempty"`
}
// WsAuthenticatedDataResponse response from authenticated connection
@@ -405,7 +410,7 @@ type WsAuthenticatedDataResponse struct {
ErrorCode int64 `json:"err-code,omitempty"`
ErrorMessage string `json:"err-msg,omitempty"`
Ping int64 `json:"ping,omitempty"`
CID string `json:"cid,omitempty"`
ClientID int64 `json:"cid,string,omitempty"`
}
// WsAuthenticatedAccountsResponse response from Accounts authenticated subscription
@@ -523,3 +528,8 @@ type WsAuthenticatedOrderDetailResponse struct {
WsAuthenticatedDataResponse
Data WsAuthenticatedOrdersListResponseData `json:"data"`
}
// WsPong sent for pong messages
type WsPong struct {
Pong int64 `json:"pong"`
}

View File

@@ -1,11 +1,8 @@
package huobihadax
import (
"bytes"
"compress/gzip"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
@@ -16,6 +13,7 @@ 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"
)
@@ -42,6 +40,9 @@ const (
signatureVersion = "2"
requestOp = "req"
authOp = "auth"
loginDelay = 50 * time.Millisecond
rateLimit = 20
)
// Instantiates a communications channel between websocket connections
@@ -50,20 +51,9 @@ var comms = make(chan WsMessage, 1)
// WsConnect initiates a new websocket connection
func (h *HUOBIHADAX) WsConnect() error {
if !h.Websocket.IsEnabled() || !h.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if h.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(h.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
err := h.wsDial(&dialer)
if err != nil {
return err
@@ -76,7 +66,6 @@ func (h *HUOBIHADAX) WsConnect() error {
if err != nil {
log.Errorf("%v - authentication failed: %v", h.Name, err)
}
go h.WsHandleData()
h.GenerateDefaultSubscriptions()
@@ -84,11 +73,9 @@ func (h *HUOBIHADAX) WsConnect() error {
}
func (h *HUOBIHADAX) wsDial(dialer *websocket.Dialer) error {
var err error
var conStatus *http.Response
h.WebsocketConn, conStatus, err = dialer.Dial(HuobiHadaxSocketIOAddress, http.Header{})
err := h.WebsocketConn.Dial(dialer, http.Header{})
if err != nil {
return fmt.Errorf("%v %v %v Error: %v", HuobiHadaxSocketIOAddress, conStatus, conStatus.StatusCode, err)
return err
}
go h.wsMultiConnectionFunnel(h.WebsocketConn, HuobiHadaxSocketIOAddress)
return nil
@@ -98,18 +85,16 @@ func (h *HUOBIHADAX) wsAuthenticatedDial(dialer *websocket.Dialer) error {
if !h.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", h.Name)
}
var err error
var conStatus *http.Response
h.AuthenticatedWebsocketConn, conStatus, err = dialer.Dial(wsAccountsOrdersURL, http.Header{})
err := h.AuthenticatedWebsocketConn.Dial(dialer, http.Header{})
if err != nil {
return fmt.Errorf("%v %v %v Error: %v", wsAccountsOrdersURL, conStatus, conStatus.StatusCode, err)
return err
}
go h.wsMultiConnectionFunnel(h.AuthenticatedWebsocketConn, wsAccountsOrdersURL)
return nil
}
// wsMultiConnectionFunnel manages data from multiple endpoints and passes it to a channel
func (h *HUOBIHADAX) wsMultiConnectionFunnel(ws *websocket.Conn, url string) {
func (h *HUOBIHADAX) wsMultiConnectionFunnel(ws *wshandler.WebsocketConnection, url string) {
h.Websocket.Wg.Add(1)
defer h.Websocket.Wg.Done()
for {
@@ -117,29 +102,13 @@ func (h *HUOBIHADAX) wsMultiConnectionFunnel(ws *websocket.Conn, url string) {
case <-h.Websocket.ShutdownC:
return
default:
_, resp, err := ws.ReadMessage()
resp, err := ws.ReadMessage()
if err != nil {
h.Websocket.DataHandler <- err
return
}
h.Websocket.TrafficAlert <- struct{}{}
b := bytes.NewReader(resp)
gReader, err := gzip.NewReader(b)
if err != nil {
h.Websocket.DataHandler <- err
return
}
unzipped, err := ioutil.ReadAll(gReader)
if err != nil {
h.Websocket.DataHandler <- err
return
}
err = gReader.Close()
if err != nil {
h.Websocket.DataHandler <- err
return
}
comms <- WsMessage{Raw: unzipped, URL: url}
comms <- WsMessage{Raw: resp.Raw, URL: url}
}
}
}
@@ -173,31 +142,26 @@ func (h *HUOBIHADAX) wsHandleAuthenticatedData(resp WsMessage) {
h.Websocket.DataHandler <- err
return
}
if init.ErrorCode > 0 {
if init.ErrorMessage == "api-signature-not-valid" {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
h.Websocket.DataHandler <- fmt.Errorf("%v %v Websocket error %v %s",
h.Name,
resp.URL,
init.ErrorCode,
init.ErrorMessage)
return
}
if init.Ping != 0 {
err = h.WebsocketConn.WriteJSON(`{"pong":1337}`)
err = h.WebsocketConn.SendMessage(WsPong{Pong: init.Ping})
if err != nil {
log.Error(err)
}
return
}
if init.ErrorMessage == "api-signature-not-valid" {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
if init.Op == "sub" {
if h.Verbose {
log.Debugf("%v: %v: Successfully subscribed to %v", h.Name, resp.URL, init.Topic)
}
return
}
if init.ClientID > 0 {
h.AuthenticatedWebsocketConn.AddResponseWithID(init.ClientID, resp.Raw)
return
}
switch {
case strings.EqualFold(init.Op, authOp):
@@ -230,27 +194,6 @@ func (h *HUOBIHADAX) wsHandleAuthenticatedData(resp WsMessage) {
h.Websocket.DataHandler <- err
}
h.Websocket.DataHandler <- response
case strings.EqualFold(init.Topic, wsAccountsList):
var response WsAuthenticatedAccountsListResponse
err := common.JSONDecode(resp.Raw, &response)
if err != nil {
h.Websocket.DataHandler <- err
}
h.Websocket.DataHandler <- response
case strings.EqualFold(init.Topic, wsOrdersList):
var response WsAuthenticatedOrdersListResponse
err := common.JSONDecode(resp.Raw, &response)
if err != nil {
h.Websocket.DataHandler <- err
}
h.Websocket.DataHandler <- response
case strings.EqualFold(init.Topic, wsOrdersDetail):
var response WsAuthenticatedOrderDetailResponse
err := common.JSONDecode(resp.Raw, &response)
if err != nil {
h.Websocket.DataHandler <- err
}
h.Websocket.DataHandler <- response
}
}
@@ -273,7 +216,7 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) {
return
}
if init.Ping != 0 {
err = h.WebsocketConn.WriteJSON(`{"pong":1337}`)
err = h.WebsocketConn.SendMessage(WsPong{Pong: init.Ping})
if err != nil {
log.Error(err)
}
@@ -298,7 +241,7 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) {
return
}
data := common.SplitStrings(kline.Channel, ".")
h.Websocket.DataHandler <- exchange.KlineData{
h.Websocket.DataHandler <- wshandler.KlineData{
Timestamp: time.Unix(0, kline.Timestamp),
Exchange: h.GetName(),
AssetType: "SPOT",
@@ -317,7 +260,7 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) {
return
}
data := common.SplitStrings(trade.Channel, ".")
h.Websocket.DataHandler <- exchange.TradeData{
h.Websocket.DataHandler <- wshandler.TradeData{
Exchange: h.GetName(),
AssetType: "SPOT",
CurrencyPair: currency.NewPairFromString(data[1]),
@@ -354,7 +297,7 @@ func (h *HUOBIHADAX) WsProcessOrderbook(ob *WsDepth, symbol string) error {
return err
}
h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: p,
Exchange: h.GetName(),
Asset: "SPOT",
@@ -366,10 +309,10 @@ func (h *HUOBIHADAX) WsProcessOrderbook(ob *WsDepth, symbol string) error {
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (h *HUOBIHADAX) GenerateDefaultSubscriptions() {
var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade}
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
if h.Websocket.CanUseAuthenticatedEndpoints() {
channels = append(channels, "orders.%v", "orders.%v.update")
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: "accounts",
})
}
@@ -378,7 +321,7 @@ func (h *HUOBIHADAX) GenerateDefaultSubscriptions() {
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = ""
channel := fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String())
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channel,
Currency: enabledCurrencies[j],
})
@@ -388,39 +331,21 @@ func (h *HUOBIHADAX) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (h *HUOBIHADAX) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (h *HUOBIHADAX) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
if common.StringContains(channelToSubscribe.Channel, "orders.") ||
common.StringContains(channelToSubscribe.Channel, "accounts") {
return h.wsAuthenticatedSubscribe("sub", wsAccountsOrdersEndPoint+channelToSubscribe.Channel, channelToSubscribe.Channel)
}
subscription, err := common.JSONEncode(WsRequest{Subscribe: channelToSubscribe.Channel})
if err != nil {
return err
}
return h.wsSend(subscription)
return h.WebsocketConn.SendMessage(WsRequest{Subscribe: channelToSubscribe.Channel})
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (h *HUOBIHADAX) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (h *HUOBIHADAX) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
if common.StringContains(channelToSubscribe.Channel, "orders.") ||
common.StringContains(channelToSubscribe.Channel, "accounts") {
return h.wsAuthenticatedSubscribe("unsub", wsAccountsOrdersEndPoint+channelToSubscribe.Channel, channelToSubscribe.Channel)
}
subscription, err := common.JSONEncode(WsRequest{Unsubscribe: channelToSubscribe.Channel})
if err != nil {
return err
}
return h.wsSend(subscription)
}
// WsSend sends data to the websocket server
func (h *HUOBIHADAX) wsSend(data []byte) error {
h.wsRequestMtx.Lock()
defer h.wsRequestMtx.Unlock()
if h.Verbose {
log.Debugf("%v sending message to websocket %s", h.Name, string(data))
}
return h.WebsocketConn.WriteMessage(websocket.TextMessage, data)
return h.WebsocketConn.SendMessage(WsRequest{Unsubscribe: channelToSubscribe.Channel})
}
func (h *HUOBIHADAX) wsLogin() error {
@@ -438,25 +363,14 @@ func (h *HUOBIHADAX) wsLogin() error {
}
hmac := h.wsGenerateSignature(timestamp, wsAccountsOrdersEndPoint)
request.Signature = common.Base64Encode(hmac)
err := h.wsAuthenticatedSend(request)
err := h.AuthenticatedWebsocketConn.SendMessage(request)
if err != nil {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
return err
}
return nil
}
func (h *HUOBIHADAX) wsAuthenticatedSend(request interface{}) error {
h.wsRequestMtx.Lock()
defer h.wsRequestMtx.Unlock()
encodedRequest, err := common.JSONEncode(request)
if err != nil {
return err
}
if h.Verbose {
log.Debugf("%v sending Authenticated message to websocket %s", h.Name, string(encodedRequest))
}
return h.AuthenticatedWebsocketConn.WriteMessage(websocket.TextMessage, encodedRequest)
time.Sleep(loginDelay)
return nil
}
func (h *HUOBIHADAX) wsGenerateSignature(timestamp, endpoint string) []byte {
@@ -483,12 +397,12 @@ func (h *HUOBIHADAX) wsAuthenticatedSubscribe(operation, endpoint, topic string)
}
hmac := h.wsGenerateSignature(timestamp, endpoint)
request.Signature = common.Base64Encode(hmac)
return h.wsAuthenticatedSend(request)
return h.AuthenticatedWebsocketConn.SendMessage(request)
}
func (h *HUOBIHADAX) wsGetAccountsList(pair currency.Pair) error {
func (h *HUOBIHADAX) wsGetAccountsList(pair currency.Pair) (*WsAuthenticatedAccountsListResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated cannot get accounts list", h.Name)
return nil, fmt.Errorf("%v not authenticated cannot get accounts list", h.Name)
}
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
request := WsAuthenticatedAccountsListRequest{
@@ -502,12 +416,19 @@ func (h *HUOBIHADAX) wsGetAccountsList(pair currency.Pair) error {
}
hmac := h.wsGenerateSignature(timestamp, wsAccountListEndpoint)
request.Signature = common.Base64Encode(hmac)
return h.wsAuthenticatedSend(request)
request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true)
resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request)
if err != nil {
return nil, err
}
var response WsAuthenticatedAccountsListResponse
err = common.JSONDecode(resp, &response)
return &response, err
}
func (h *HUOBIHADAX) wsGetOrdersList(accountID int64, pair currency.Pair) error {
func (h *HUOBIHADAX) wsGetOrdersList(accountID int64, pair currency.Pair) (*WsAuthenticatedOrdersResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated cannot get orders list", h.Name)
return nil, fmt.Errorf("%v not authenticated cannot get orders list", h.Name)
}
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
request := WsAuthenticatedOrdersListRequest{
@@ -523,12 +444,19 @@ func (h *HUOBIHADAX) wsGetOrdersList(accountID int64, pair currency.Pair) error
}
hmac := h.wsGenerateSignature(timestamp, wsOrdersListEndpoint)
request.Signature = common.Base64Encode(hmac)
return h.wsAuthenticatedSend(request)
request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true)
resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request)
if err != nil {
return nil, err
}
var response WsAuthenticatedOrdersResponse
err = common.JSONDecode(resp, &response)
return &response, err
}
func (h *HUOBIHADAX) wsGetOrderDetails(orderID string) error {
func (h *HUOBIHADAX) wsGetOrderDetails(orderID string) (*WsAuthenticatedOrderDetailResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v not authenticated cannot get order details", h.Name)
return nil, fmt.Errorf("%v not authenticated cannot get order details", h.Name)
}
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
request := WsAuthenticatedOrderDetailsRequest{
@@ -542,5 +470,12 @@ func (h *HUOBIHADAX) wsGetOrderDetails(orderID string) error {
}
hmac := h.wsGenerateSignature(timestamp, wsOrdersDetailEndpoint)
request.Signature = common.Base64Encode(hmac)
return h.wsAuthenticatedSend(request)
request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true)
resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request)
if err != nil {
return nil, err
}
var response WsAuthenticatedOrderDetailResponse
err = common.JSONDecode(resp, &response)
return &response, err
}

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"
)
@@ -341,7 +342,7 @@ func (h *HUOBIHADAX) WithdrawFiatFundsToInternationalBank(withdrawRequest *excha
}
// GetWebsocket returns a pointer to the exchange websocket
func (h *HUOBIHADAX) GetWebsocket() (*exchange.Websocket, error) {
func (h *HUOBIHADAX) GetWebsocket() (*wshandler.Websocket, error) {
return h.Websocket, nil
}
@@ -451,20 +452,20 @@ func (h *HUOBIHADAX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (h *HUOBIHADAX) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (h *HUOBIHADAX) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
h.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (h *HUOBIHADAX) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
h.Websocket.UnsubscribeToChannels(channels)
func (h *HUOBIHADAX) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
h.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (h *HUOBIHADAX) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (h *HUOBIHADAX) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return h.Websocket.GetSubscriptions(), nil
}

View File

@@ -16,6 +16,7 @@ import (
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"
)
@@ -65,7 +66,7 @@ func (i *ItBit) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
i.APIUrlDefault = itbitAPIURL
i.APIUrl = i.APIUrlDefault
i.WebsocketInit()
i.Websocket = wshandler.New()
}
// Setup sets the exchange parameters from exchange config

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"
)
@@ -295,7 +296,7 @@ func (i *ItBit) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.W
}
// GetWebsocket returns a pointer to the exchange websocket
func (i *ItBit) GetWebsocket() (*exchange.Websocket, error) {
func (i *ItBit) GetWebsocket() (*wshandler.Websocket, error) {
return nil, common.ErrFunctionNotSupported
}
@@ -409,18 +410,18 @@ func (i *ItBit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (i *ItBit) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (i *ItBit) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (i *ItBit) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (i *ItBit) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (i *ItBit) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (i *ItBit) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -10,13 +10,13 @@ import (
"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"
)
@@ -58,7 +58,7 @@ const (
// Kraken is the overarching type across the alphapoint package
type Kraken struct {
exchange.Base
WebsocketConn *websocket.Conn
WebsocketConn *wshandler.WebsocketConnection
CryptoFee, FiatFee float64
wsRequestMtx sync.Mutex
}
@@ -89,14 +89,17 @@ func (k *Kraken) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
k.APIUrlDefault = krakenAPIURL
k.APIUrl = k.APIUrlDefault
k.WebsocketInit()
k.Websocket = wshandler.New()
k.WebsocketURL = krakenWSURL
k.Websocket.Functionality = exchange.WebsocketTickerSupported |
exchange.WebsocketTradeDataSupported |
exchange.WebsocketKlineSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported
k.Websocket.Functionality = wshandler.WebsocketTickerSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketKlineSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketMessageCorrelationSupported
k.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
k.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
@@ -137,17 +140,27 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = k.WebsocketSetup(k.WsConnect,
err = k.Websocket.Setup(k.WsConnect,
k.Subscribe,
k.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
krakenWSURL,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
k.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: k.Name,
URL: k.Websocket.GetWebsocketURL(),
ProxyURL: k.Websocket.GetProxyAddress(),
Verbose: k.Verbose,
RateLimit: krakenWsRateLimit,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -2,17 +2,21 @@ package kraken
import (
"fmt"
"net/http"
"strings"
"testing"
"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/sharedtestvalues"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
)
var k Kraken
var wsSetupRan bool
// Please add your own APIkeys to do correct due diligence testing.
const (
@@ -743,3 +747,42 @@ func TestOrderBookOutOfOrder(t *testing.T) {
t.Error("Expected out of order orderbook error")
}
}
func setupWsTests(t *testing.T) {
if wsSetupRan {
return
}
TestSetDefaults(t)
TestSetup(t)
if !k.Websocket.IsEnabled() && !k.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(wshandler.WebsocketNotEnabled)
}
k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
k.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
k.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: k.Name,
URL: krakenWSURL,
Verbose: k.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var dialer websocket.Dialer
err := k.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
go k.WsHandleData()
wsSetupRan = true
}
// TestWebsocketSubscribe tests returning a message with an id
func TestWebsocketSubscribe(t *testing.T) {
setupWsTests(t)
err := k.Subscribe(wshandler.WebsocketChannelSubscription{
Channel: defaultSubscribedChannels[0],
Currency: currency.NewPairWithDelimiter("XBT", "USD", "/"),
})
if err != nil {
t.Error(err)
}
}

View File

@@ -395,9 +395,14 @@ type WebsocketSubscriptionEventRequest struct {
Subscription WebsocketSubscriptionData `json:"subscription,omitempty"`
}
// WebsocketBaseEventRequest Just has an "event" property
type WebsocketBaseEventRequest struct {
Event string `json:"event"` // eg "unsubscribe"
}
// WebsocketUnsubscribeByChannelIDEventRequest handles WS unsubscribe events
type WebsocketUnsubscribeByChannelIDEventRequest struct {
Event string `json:"event"` // unsubscribe
WebsocketBaseEventRequest
RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message.
Pairs []string `json:"pair,omitempty"` // Array of currency pairs (pair1,pair2,pair3).
ChannelID int64 `json:"channelID,omitempty"`
@@ -412,7 +417,7 @@ type WebsocketSubscriptionData struct {
// WebsocketEventResponse holds all data response types
type WebsocketEventResponse struct {
Event string `json:"event"`
WebsocketBaseEventRequest
Status string `json:"status"`
Pair currency.Pair `json:"pair,omitempty"`
RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message.

View File

@@ -1,14 +1,10 @@
package kraken
import (
"bytes"
"compress/flate"
"errors"
"fmt"
"io/ioutil"
"math"
"net/http"
"net/url"
"sort"
"strconv"
"sync"
@@ -17,8 +13,8 @@ import (
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"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"
)
@@ -44,9 +40,8 @@ const (
krakenWsSpread = "spread"
krakenWsOrderbook = "book"
// Only supported asset type
krakenWsAssetType = "SPOT"
orderbookBufferLimit = 3
krakenWsRateLimit = 50 * time.Millisecond
krakenWsRateLimit = 50
)
// orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time
@@ -66,53 +61,15 @@ var subscribeToDefaultChannels = true
// Format [[ticker,but-t4u],[orderbook,nce-btt]]
var defaultSubscribedChannels = []string{krakenWsTicker, krakenWsTrade, krakenWsOrderbook, krakenWsOHLC, krakenWsSpread}
// writeToWebsocket sends a message to the websocket endpoint
func (k *Kraken) writeToWebsocket(message []byte) error {
k.wsRequestMtx.Lock()
defer k.wsRequestMtx.Unlock()
if k.Verbose {
log.Debugf("%v Sending message to WS: %v",
k.Name,
string(message))
}
// Really basic WS rate limit
time.Sleep(krakenWsRateLimit)
return k.WebsocketConn.WriteMessage(websocket.TextMessage, message)
}
// WsConnect initiates a websocket connection
func (k *Kraken) WsConnect() error {
if !k.Websocket.IsEnabled() || !k.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if k.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(k.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
if k.Verbose {
log.Debugf("%v Attempting to connect to %v",
k.Name,
k.Websocket.GetWebsocketURL())
}
k.WebsocketConn, _, err = dialer.Dial(k.Websocket.GetWebsocketURL(),
http.Header{})
err := k.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return fmt.Errorf("%s Unable to connect to Websocket. Error: %s",
k.Name,
err)
}
if k.Verbose {
log.Debugf("%v Successful connection to %v",
k.Name,
k.Websocket.GetWebsocketURL())
return err
}
go k.WsHandleData()
go k.wsPingHandler()
@@ -123,35 +80,6 @@ func (k *Kraken) WsConnect() error {
return nil
}
// WsReadData reads data from the websocket connection
func (k *Kraken) WsReadData() (exchange.WebsocketResponse, error) {
mType, resp, err := k.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
k.Websocket.TrafficAlert <- struct{}{}
var standardMessage []byte
switch mType {
case websocket.TextMessage:
standardMessage = resp
case websocket.BinaryMessage:
reader := flate.NewReader(bytes.NewReader(resp))
standardMessage, err = ioutil.ReadAll(reader)
reader.Close()
if err != nil {
return exchange.WebsocketResponse{}, err
}
}
if k.Verbose {
log.Debugf("%v Websocket message received: %v",
k.Name,
string(standardMessage))
}
return exchange.WebsocketResponse{Raw: standardMessage}, nil
}
// wsPingHandler sends a message "ping" every 27 to maintain the connection to the websocket
func (k *Kraken) wsPingHandler() {
k.Websocket.Wg.Add(1)
@@ -163,14 +91,13 @@ func (k *Kraken) wsPingHandler() {
select {
case <-k.Websocket.ShutdownC:
return
case <-ticker.C:
pingEvent := fmt.Sprintf("{\"event\":\"%v\"}", krakenWsPing)
pingEvent := WebsocketBaseEventRequest{Event: krakenWsPing}
if k.Verbose {
log.Debugf("%v sending ping",
k.Name)
}
err := k.writeToWebsocket([]byte(pingEvent))
err := k.WebsocketConn.SendMessage(pingEvent)
if err != nil {
k.Websocket.DataHandler <- err
}
@@ -190,18 +117,19 @@ func (k *Kraken) WsHandleData() {
case <-k.Websocket.ShutdownC:
return
default:
resp, err := k.WsReadData()
resp, err := k.WebsocketConn.ReadMessage()
if err != nil {
k.Websocket.DataHandler <- fmt.Errorf("%v WsHandleData: %v",
k.Name,
err)
return
}
k.Websocket.TrafficAlert <- struct{}{}
// event response handling
var eventResponse WebsocketEventResponse
err = common.JSONDecode(resp.Raw, &eventResponse)
if err == nil && eventResponse.Event != "" {
k.WsHandleEventResponse(&eventResponse)
k.WsHandleEventResponse(&eventResponse, resp.Raw)
continue
}
// Data response handling
@@ -259,7 +187,7 @@ func (k *Kraken) WsHandleDataResponse(response WebsocketDataResponse) {
}
// WsHandleEventResponse classifies the WS response and sends to appropriate handler
func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse) {
func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse, rawResponse []byte) {
switch response.Event {
case krakenWsHeartbeat:
if k.Verbose {
@@ -285,19 +213,9 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse) {
k.Name, krakenWSSupportedVersion, response.WebsocketStatusResponse.Version)
}
case krakenWsSubscriptionStatus:
if k.Verbose {
log.Debugf("%v Websocket subscription status data received",
k.Name)
}
k.WebsocketConn.AddResponseWithID(response.RequestID, rawResponse)
if response.Status != "subscribed" {
if response.RequestID > 0 {
k.Websocket.DataHandler <- fmt.Errorf("%v requestID: '%v'. Error: %v",
k.Name,
response.RequestID,
response.WebsocketErrorResponse.ErrorMessage)
} else {
k.Websocket.DataHandler <- fmt.Errorf(response.WebsocketErrorResponse.ErrorMessage)
}
k.Websocket.DataHandler <- fmt.Errorf("%v %v %v", k.Name, response.RequestID, response.WebsocketErrorResponse.ErrorMessage)
return
}
addNewSubscriptionChannelData(response)
@@ -358,10 +276,10 @@ func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data interf
lowPrice, _ := strconv.ParseFloat(lowData[0].(string), 64)
quantity, _ := strconv.ParseFloat(volumeData[0].(string), 64)
k.Websocket.DataHandler <- exchange.TickerData{
k.Websocket.DataHandler <- wshandler.TickerData{
Timestamp: time.Now(),
Exchange: k.Name,
AssetType: krakenWsAssetType,
AssetType: orderbook.Spot,
Pair: channelData.Pair,
ClosePrice: closePrice,
OpenPrice: openPrice,
@@ -403,8 +321,8 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data interfa
price, _ := strconv.ParseFloat(trade[0].(string), 64)
amount, _ := strconv.ParseFloat(trade[1].(string), 64)
k.Websocket.DataHandler <- exchange.TradeData{
AssetType: krakenWsAssetType,
k.Websocket.DataHandler <- wshandler.TradeData{
AssetType: orderbook.Spot,
CurrencyPair: channelData.Pair,
EventTime: time.Now().Unix(),
Exchange: k.Name,
@@ -432,7 +350,7 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data inte
if len(orderbookBuffer[channelData.ChannelID]) >= orderbookBufferLimit {
err := k.wsProcessOrderBookUpdate(channelData)
if err != nil {
subscriptionToRemove := exchange.WebsocketChannelSubscription{
subscriptionToRemove := wshandler.WebsocketChannelSubscription{
Channel: krakenWsOrderbook,
Currency: channelData.Pair,
}
@@ -447,7 +365,7 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data inte
func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, obData map[string]interface{}) {
ob := orderbook.Base{
Pair: channelData.Pair,
AssetType: krakenWsAssetType,
AssetType: orderbook.Spot,
}
// Kraken ob data is timestamped per price, GCT orderbook data is timestamped per entry
// Using the highest last update time, we can attempt to respect both within a reasonable degree
@@ -495,9 +413,9 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
return
}
k.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: k.Name,
Asset: krakenWsAssetType,
Asset: orderbook.Spot,
Pair: channelData.Pair,
}
@@ -509,7 +427,7 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obData map[string]interface{}) {
ob := orderbook.Base{
AssetType: krakenWsAssetType,
AssetType: orderbook.Spot,
ExchangeName: k.Name,
Pair: channelData.Pair,
}
@@ -618,9 +536,9 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData) err
return err
}
k.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: k.Name,
Asset: krakenWsAssetType,
Asset: orderbook.Spot,
Pair: channelData.Pair,
}
// Reset the buffer
@@ -754,8 +672,8 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data interf
closePrice, _ := strconv.ParseFloat(candleData[5].(string), 64)
volume, _ := strconv.ParseFloat(candleData[7].(string), 64)
k.Websocket.DataHandler <- exchange.KlineData{
AssetType: krakenWsAssetType,
k.Websocket.DataHandler <- wshandler.KlineData{
AssetType: orderbook.Spot,
Pair: channelData.Pair,
Timestamp: time.Now(),
Exchange: k.Name,
@@ -774,11 +692,11 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data interf
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (k *Kraken) GenerateDefaultSubscriptions() {
enabledCurrencies := k.GetEnabledCurrencies()
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
for i := range defaultSubscribedChannels {
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = "/"
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: defaultSubscribedChannels[i],
Currency: enabledCurrencies[j],
})
@@ -788,39 +706,29 @@ func (k *Kraken) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (k *Kraken) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (k *Kraken) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
resp := WebsocketSubscriptionEventRequest{
Event: krakenWsSubscribe,
Pairs: []string{channelToSubscribe.Currency.String()},
Subscription: WebsocketSubscriptionData{
Name: channelToSubscribe.Channel,
},
RequestID: k.WebsocketConn.GenerateMessageID(true),
}
json, err := common.JSONEncode(resp)
if err != nil {
if k.Verbose {
log.Debugf("%v subscribe error: %v", k.Name, err)
}
return err
}
return k.writeToWebsocket(json)
_, err := k.WebsocketConn.SendMessageReturnResponse(resp.RequestID, resp)
return err
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (k *Kraken) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (k *Kraken) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
resp := WebsocketSubscriptionEventRequest{
Event: krakenWsUnsubscribe,
Pairs: []string{channelToSubscribe.Currency.String()},
Subscription: WebsocketSubscriptionData{
Name: channelToSubscribe.Channel,
},
RequestID: k.WebsocketConn.GenerateMessageID(true),
}
json, err := common.JSONEncode(resp)
if err != nil {
if k.Verbose {
log.Debugf("%v unsubscribe error: %v", k.Name, err)
}
return err
}
return k.writeToWebsocket(json)
_, err := k.WebsocketConn.SendMessageReturnResponse(resp.RequestID, resp)
return err
}

View File

@@ -12,6 +12,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"
)
@@ -305,7 +306,7 @@ func (k *Kraken) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.
}
// GetWebsocket returns a pointer to the exchange websocket
func (k *Kraken) GetWebsocket() (*exchange.Websocket, error) {
func (k *Kraken) GetWebsocket() (*wshandler.Websocket, error) {
return k.Websocket, nil
}
@@ -397,20 +398,20 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (k *Kraken) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (k *Kraken) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
k.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (k *Kraken) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
k.Websocket.UnsubscribeToChannels(channels)
func (k *Kraken) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
k.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (k *Kraken) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (k *Kraken) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return k.Websocket.GetSubscriptions(), nil
}

View File

@@ -14,6 +14,7 @@ import (
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"
)
@@ -65,7 +66,7 @@ func (l *LakeBTC) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
l.APIUrlDefault = lakeBTCAPIURL
l.APIUrl = l.APIUrlDefault
l.WebsocketInit()
l.Websocket = wshandler.New()
}
// Setup sets exchange configuration profile

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"
)
@@ -266,7 +267,7 @@ func (l *LakeBTC) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange
}
// GetWebsocket returns a pointer to the exchange websocket
func (l *LakeBTC) GetWebsocket() (*exchange.Websocket, error) {
func (l *LakeBTC) GetWebsocket() (*wshandler.Websocket, error) {
// Documents are too vague to implement
return nil, common.ErrFunctionNotSupported
}
@@ -351,18 +352,18 @@ func (l *LakeBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) (
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (l *LakeBTC) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (l *LakeBTC) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (l *LakeBTC) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (l *LakeBTC) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (l *LakeBTC) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (l *LakeBTC) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/thrasher-/gocryptotrader/config"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
log "github.com/thrasher-/gocryptotrader/logger"
)
@@ -134,7 +135,7 @@ func (l *LocalBitcoins) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
l.APIUrlDefault = localbitcoinsAPIURL
l.APIUrl = l.APIUrlDefault
l.WebsocketInit()
l.Websocket = wshandler.New()
}
// Setup sets exchange configuration parameters

View File

@@ -14,6 +14,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"
)
@@ -296,7 +297,7 @@ func (l *LocalBitcoins) WithdrawFiatFundsToInternationalBank(withdrawRequest *ex
}
// GetWebsocket returns a pointer to the exchange websocket
func (l *LocalBitcoins) GetWebsocket() (*exchange.Websocket, error) {
func (l *LocalBitcoins) GetWebsocket() (*wshandler.Websocket, error) {
return nil, common.ErrFunctionNotSupported
}
@@ -430,18 +431,18 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequ
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (l *LocalBitcoins) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (l *LocalBitcoins) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (l *LocalBitcoins) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (l *LocalBitcoins) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrFunctionNotSupported
}
// GetSubscriptions returns a copied list of subscriptions
func (l *LocalBitcoins) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (l *LocalBitcoins) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/thrasher-/gocryptotrader/exchanges/okgroup"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
)
const (
@@ -48,14 +49,17 @@ func (o *OKCoin) SetDefaults() {
o.APIUrlDefault = okCoinAPIURL
o.APIUrl = okCoinAPIURL
o.AssetTypes = []string{ticker.Spot}
o.WebsocketInit()
o.Websocket = wshandler.New()
o.WebsocketURL = okCoinWebsocketURL
o.APIVersion = okCoinAPIVersion
o.Websocket.Functionality = exchange.WebsocketTickerSupported |
exchange.WebsocketTradeDataSupported |
exchange.WebsocketKlineSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported
o.Websocket.Functionality = wshandler.WebsocketTickerSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketKlineSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported |
wshandler.WebsocketMessageCorrelationSupported
o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}

View File

@@ -14,6 +14,7 @@ import (
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/okgroup"
"github.com/thrasher-/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
)
// Please supply you own test keys here for due diligence testing.
@@ -804,25 +805,27 @@ func TestGetMarginTransactionDetails(t *testing.T) {
func TestSendWsMessages(t *testing.T) {
TestSetDefaults(t)
if !o.Websocket.IsEnabled() && !o.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
var ok bool
o.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: o.Name,
URL: o.Websocket.GetWebsocketURL(),
Verbose: o.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var dialer websocket.Dialer
var err error
var ok bool
o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(),
http.Header{})
err := o.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatalf("%s Unable to connect to Websocket. Error: %s",
o.Name,
err)
t.Fatal(err)
}
defer o.WebsocketConn.Close()
wg := sync.WaitGroup{}
wg.Add(1)
go o.WsHandleData(&wg)
wg.Wait()
subscription := exchange.WebsocketChannelSubscription{
subscription := wshandler.WebsocketChannelSubscription{
Channel: "badChannel",
}
o.Subscribe(subscription)

View File

@@ -11,6 +11,7 @@ import (
"github.com/thrasher-/gocryptotrader/exchanges/okgroup"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
)
const (
@@ -73,16 +74,20 @@ func (o *OKEX) SetDefaults() {
o.APIUrlDefault = okExAPIURL
o.APIUrl = okExAPIURL
o.AssetTypes = []string{ticker.Spot}
o.WebsocketInit()
o.Websocket = wshandler.New()
o.APIVersion = okExAPIVersion
o.WebsocketURL = OkExWebsocketURL
o.Websocket.Functionality = exchange.WebsocketTickerSupported |
exchange.WebsocketTradeDataSupported |
exchange.WebsocketKlineSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported
o.Websocket.Functionality = wshandler.WebsocketTickerSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketKlineSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported |
wshandler.WebsocketMessageCorrelationSupported
o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// GetFuturesPostions Get the information of all holding positions in futures trading.

View File

@@ -15,6 +15,7 @@ import (
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/okgroup"
"github.com/thrasher-/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
)
// Please supply you own test keys here for due diligence testing.
@@ -1569,25 +1570,27 @@ func TestGetETTSettlementPriceHistory(t *testing.T) {
func TestSendWsMessages(t *testing.T) {
TestSetDefaults(t)
if !o.Websocket.IsEnabled() && !o.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
var ok bool
o.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: o.Name,
URL: o.Websocket.GetWebsocketURL(),
Verbose: o.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var dialer websocket.Dialer
var err error
var ok bool
o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(),
http.Header{})
err := o.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatalf("%s Unable to connect to Websocket. Error: %s",
o.Name,
err)
t.Fatal(err)
}
defer o.WebsocketConn.Close()
wg := sync.WaitGroup{}
wg.Add(1)
go o.WsHandleData(&wg)
wg.Wait()
subscription := exchange.WebsocketChannelSubscription{
subscription := wshandler.WebsocketChannelSubscription{
Channel: "badChannel",
}
o.Subscribe(subscription)

View File

@@ -10,14 +10,13 @@ import (
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/google/go-querystring/query"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/wshandler"
log "github.com/thrasher-/gocryptotrader/logger"
)
@@ -87,8 +86,7 @@ var errMissValue = errors.New("warning - resp value is missing from exchange")
type OKGroup struct {
exchange.Base
ExchangeName string
WebsocketConn *websocket.Conn
wsRequestMtx sync.Mutex
WebsocketConn *wshandler.WebsocketConnection
// Spot and contract market error codes as per https://www.okex.com/rest_request.html
ErrorCodes map[string]error
// Stores for corresponding variable checks
@@ -141,17 +139,27 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = o.WebsocketSetup(o.WsConnect,
err = o.Websocket.Setup(o.WsConnect,
o.Subscribe,
o.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
o.WebsocketURL,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
o.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: o.Name,
URL: o.Websocket.GetWebsocketURL(),
ProxyURL: o.Websocket.GetProxyAddress(),
Verbose: o.Verbose,
RateLimit: okGroupWsRateLimit,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -1,34 +1,27 @@
package okgroup
import (
"bytes"
"compress/flate"
"errors"
"fmt"
"hash/crc32"
"io/ioutil"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"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"
)
// List of all websocket channels to subscribe to
const (
// If a checksum fails, then resubscribing to the channel fails, fatal after these attempts
okGroupWsResubscribeFailureLimit = 3
okGroupWsResubscribeDelayInSeconds = 3
// Orderbook events
okGroupWsOrderbookUpdate = "update"
okGroupWsOrderbookPartial = "partial"
@@ -144,50 +137,22 @@ const (
okGroupWsFuturesPosition = okGroupWsFuturesSubsection + okGroupWsPosition
okGroupWsFuturesOrder = okGroupWsFuturesSubsection + okGroupWsOrder
okGroupWsRateLimit = 30 * time.Millisecond
okGroupWsRateLimit = 30
)
// orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time
var orderbookMutex sync.Mutex
var defaultSubscribedChannels = []string{okGroupWsSpotDepth, okGroupWsSpotCandle300s, okGroupWsSpotTicker, okGroupWsSpotTrade}
// writeToWebsocket sends a message to the websocket endpoint
func (o *OKGroup) writeToWebsocket(message string) error {
o.wsRequestMtx.Lock()
defer o.wsRequestMtx.Unlock()
if o.Verbose {
log.Debugf("%v sending message to WS: %v", o.Name, message)
}
// Really basic WS rate limit
time.Sleep(okGroupWsRateLimit)
return o.WebsocketConn.WriteMessage(websocket.TextMessage, []byte(message))
}
// WsConnect initiates a websocket connection
func (o *OKGroup) WsConnect() error {
if !o.Websocket.IsEnabled() || !o.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if o.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(o.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
if o.Verbose {
log.Debugf("Attempting to connect to %v", o.Websocket.GetWebsocketURL())
}
o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(),
http.Header{})
err := o.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return fmt.Errorf("%s Unable to connect to Websocket. Error: %s",
o.Name,
err)
return err
}
if o.Verbose {
log.Debugf("Successful connection to %v",
@@ -210,34 +175,6 @@ func (o *OKGroup) WsConnect() error {
return nil
}
// WsReadData reads data from the websocket connection
func (o *OKGroup) WsReadData() (exchange.WebsocketResponse, error) {
mType, resp, err := o.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
o.Websocket.TrafficAlert <- struct{}{}
var standardMessage []byte
switch mType {
case websocket.TextMessage:
standardMessage = resp
case websocket.BinaryMessage:
reader := flate.NewReader(bytes.NewReader(resp))
standardMessage, err = ioutil.ReadAll(reader)
reader.Close()
if err != nil {
return exchange.WebsocketResponse{}, err
}
}
if o.Verbose {
log.Debugf("%v Websocket message received: %v", o.Name, string(standardMessage))
}
return exchange.WebsocketResponse{Raw: standardMessage}, nil
}
// wsPingHandler sends a message "ping" every 27 to maintain the connection to the websocket
func (o *OKGroup) wsPingHandler(wg *sync.WaitGroup) {
o.Websocket.Wg.Add(1)
@@ -254,7 +191,7 @@ func (o *OKGroup) wsPingHandler(wg *sync.WaitGroup) {
return
case <-ticker.C:
err := o.writeToWebsocket("ping")
err := o.WebsocketConn.SendMessage("ping")
if o.Verbose {
log.Debugf("%v sending ping", o.GetName())
}
@@ -280,11 +217,12 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) {
return
default:
resp, err := o.WsReadData()
resp, err := o.WebsocketConn.ReadMessage()
if err != nil {
time.Sleep(time.Second)
o.Websocket.DataHandler <- err
return
}
o.Websocket.TrafficAlert <- struct{}{}
var dataResponse WebsocketDataResponse
err = common.JSONDecode(resp.Raw, &dataResponse)
if err == nil && dataResponse.Table != "" {
@@ -326,16 +264,11 @@ func (o *OKGroup) WsLogin() error {
signPath := "/users/self/verify"
hmac := common.GetHMAC(common.HashSHA256, []byte(fmt.Sprintf("%v", unixTime)+http.MethodGet+signPath), []byte(o.APISecret))
base64 := common.Base64Encode(hmac)
resp := WebsocketEventRequest{
request := WebsocketEventRequest{
Operation: "login",
Arguments: []string{o.APIKey, o.ClientID, fmt.Sprintf("%v", unixTime), base64},
}
json, err := common.JSONEncode(resp)
if err != nil {
o.Websocket.SetCanUseAuthenticatedEndpoints(false)
return err
}
err = o.writeToWebsocket(string(json))
err := o.WebsocketConn.SendMessage(request)
if err != nil {
o.Websocket.SetCanUseAuthenticatedEndpoints(false)
return err
@@ -384,20 +317,14 @@ func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) {
case okGroupWsCandle60s, okGroupWsCandle180s, okGroupWsCandle300s, okGroupWsCandle900s,
okGroupWsCandle1800s, okGroupWsCandle3600s, okGroupWsCandle7200s, okGroupWsCandle14400s,
okGroupWsCandle21600s, okGroupWsCandle43200s, okGroupWsCandle86400s, okGroupWsCandle604900s:
if o.Verbose {
log.Debugf("%v Websocket candle data received", o.GetName())
}
o.wsProcessCandles(response)
case okGroupWsDepth, okGroupWsDepth5:
if o.Verbose {
log.Debugf("%v Websocket orderbook data received", o.GetName())
}
// Locking, orderbooks cannot be processed out of order
orderbookMutex.Lock()
err := o.WsProcessOrderBook(response)
if err != nil {
pair := currency.NewPairDelimiter(response.Data[0].InstrumentID, "-")
channelToResubscribe := exchange.WebsocketChannelSubscription{
channelToResubscribe := wshandler.WebsocketChannelSubscription{
Channel: response.Table,
Currency: pair,
}
@@ -405,14 +332,8 @@ func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) {
}
orderbookMutex.Unlock()
case okGroupWsTicker:
if o.Verbose {
log.Debugf("%v Websocket ticker data received", o.GetName())
}
o.wsProcessTickers(response)
case okGroupWsTrade:
if o.Verbose {
log.Debugf("%v Websocket trade data received", o.GetName())
}
o.wsProcessTrades(response)
default:
logDataResponse(response)
@@ -435,7 +356,7 @@ func logDataResponse(response *WebsocketDataResponse) {
func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) {
for i := range response.Data {
instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-")
o.Websocket.DataHandler <- exchange.TickerData{
o.Websocket.DataHandler <- wshandler.TickerData{
Timestamp: response.Data[i].Timestamp,
Exchange: o.GetName(),
AssetType: o.GetAssetTypeFromTableName(response.Table),
@@ -451,7 +372,7 @@ func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) {
func (o *OKGroup) wsProcessTrades(response *WebsocketDataResponse) {
for i := range response.Data {
instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-")
o.Websocket.DataHandler <- exchange.TradeData{
o.Websocket.DataHandler <- wshandler.TradeData{
Amount: response.Data[i].Size,
AssetType: o.GetAssetTypeFromTableName(response.Table),
CurrencyPair: instrument,
@@ -480,7 +401,7 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) {
candleInterval = response.Table[candleIndex+len(okGroupWsCandle) : secondIndex]
}
klineData := exchange.KlineData{
klineData := wshandler.KlineData{
AssetType: o.GetAssetTypeFromTableName(response.Table),
Pair: instrument,
Exchange: o.GetName(),
@@ -548,7 +469,7 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i
if err != nil {
return err
}
o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: o.GetName(),
Asset: o.GetAssetTypeFromTableName(tableName),
Pair: instrument,
@@ -591,7 +512,7 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, in
if err != nil {
log.Error(err)
}
o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: o.GetName(),
Asset: o.GetAssetTypeFromTableName(tableName),
Pair: instrument,
@@ -694,14 +615,14 @@ func (o *OKGroup) CalculateUpdateOrderbookChecksum(orderbookData *orderbook.Base
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (o *OKGroup) GenerateDefaultSubscriptions() {
enabledCurrencies := o.GetEnabledCurrencies()
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
defaultSubscribedChannels = append(defaultSubscribedChannels, okGroupWsSpotMarginAccount, okGroupWsSpotAccount, okGroupWsSpotOrder)
}
for i := range defaultSubscribedChannels {
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = "-"
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: defaultSubscribedChannels[i],
Currency: enabledCurrencies[j],
})
@@ -711,37 +632,23 @@ func (o *OKGroup) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (o *OKGroup) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
resp := WebsocketEventRequest{
func (o *OKGroup) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
request := WebsocketEventRequest{
Operation: "subscribe",
Arguments: []string{fmt.Sprintf("%v:%v", channelToSubscribe.Channel, channelToSubscribe.Currency.String())},
}
if strings.EqualFold(channelToSubscribe.Channel, okGroupWsSpotAccount) {
resp.Arguments = []string{fmt.Sprintf("%v:%v", channelToSubscribe.Channel, channelToSubscribe.Currency.Base.String())}
request.Arguments = []string{fmt.Sprintf("%v:%v", channelToSubscribe.Channel, channelToSubscribe.Currency.Base.String())}
}
json, err := common.JSONEncode(resp)
if err != nil {
if o.Verbose {
log.Debugf("%v subscribe error: %v", o.Name, err)
}
return err
}
return o.writeToWebsocket(string(json))
return o.WebsocketConn.SendMessage(request)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (o *OKGroup) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
resp := WebsocketEventRequest{
func (o *OKGroup) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
request := WebsocketEventRequest{
Operation: "unsubscribe",
Arguments: []string{fmt.Sprintf("%v:%v", channelToSubscribe.Channel, channelToSubscribe.Currency.String())},
}
json, err := common.JSONEncode(resp)
if err != nil {
if o.Verbose {
log.Debugf("%v unsubscribe error: %v", o.Name, err)
}
return err
}
return o.writeToWebsocket(string(json))
return o.WebsocketConn.SendMessage(request)
}

View File

@@ -11,6 +11,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"
)
@@ -414,7 +415,7 @@ func (o *OKGroup) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) (
}
// GetWebsocket returns a pointer to the exchange websocket
func (o *OKGroup) GetWebsocket() (*exchange.Websocket, error) {
func (o *OKGroup) GetWebsocket() (*wshandler.Websocket, error) {
return o.Websocket, nil
}
@@ -434,20 +435,20 @@ func (o *OKGroup) GetWithdrawCapabilities() uint32 {
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (o *OKGroup) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (o *OKGroup) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
o.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (o *OKGroup) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
o.Websocket.UnsubscribeToChannels(channels)
func (o *OKGroup) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
o.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (o *OKGroup) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (o *OKGroup) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return o.Websocket.GetSubscriptions(), nil
}

View File

@@ -8,16 +8,15 @@ import (
"net/http"
"net/url"
"strconv"
"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"
)
@@ -62,8 +61,7 @@ const (
// Poloniex is the overarching type across the poloniex package
type Poloniex struct {
exchange.Base
WebsocketConn *websocket.Conn
wsRequestMtx sync.Mutex
WebsocketConn *wshandler.WebsocketConnection
}
// SetDefaults sets default settings for poloniex
@@ -88,13 +86,15 @@ func (p *Poloniex) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
p.APIUrlDefault = poloniexAPIURL
p.APIUrl = p.APIUrlDefault
p.WebsocketInit()
p.Websocket.Functionality = exchange.WebsocketTradeDataSupported |
exchange.WebsocketOrderbookSupported |
exchange.WebsocketTickerSupported |
exchange.WebsocketSubscribeSupported |
exchange.WebsocketUnsubscribeSupported |
exchange.WebsocketAuthenticatedEndpointsSupported
p.Websocket = wshandler.New()
p.Websocket.Functionality = wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketTickerSupported |
wshandler.WebsocketSubscribeSupported |
wshandler.WebsocketUnsubscribeSupported |
wshandler.WebsocketAuthenticatedEndpointsSupported
p.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
p.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}
// Setup sets user exchange configuration settings
@@ -135,17 +135,26 @@ func (p *Poloniex) Setup(exch *config.ExchangeConfig) {
if err != nil {
log.Fatal(err)
}
err = p.WebsocketSetup(p.WsConnect,
err = p.Websocket.Setup(p.WsConnect,
p.Subscribe,
p.Unsubscribe,
exch.Name,
exch.Websocket,
exch.Verbose,
poloniexWebsocketAddress,
exch.WebsocketURL)
exch.WebsocketURL,
exch.AuthenticatedWebsocketAPISupport)
if err != nil {
log.Fatal(err)
}
p.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: p.Name,
URL: p.Websocket.GetWebsocketURL(),
ProxyURL: p.Websocket.GetProxyAddress(),
Verbose: p.Verbose,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
}
}

View File

@@ -11,6 +11,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"
)
var p Poloniex
@@ -443,22 +444,26 @@ func TestWsHandleAccountData(t *testing.T) {
func TestWsAuth(t *testing.T) {
TestSetup(t)
if !p.Websocket.IsEnabled() && !p.AuthenticatedWebsocketAPISupport || !areTestAPIKeysSet() {
t.Skip(exchange.WebsocketNotEnabled)
t.Skip(wshandler.WebsocketNotEnabled)
}
p.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: p.Name,
URL: p.Websocket.GetWebsocketURL(),
Verbose: p.Verbose,
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
}
var err error
var dialer websocket.Dialer
p.WebsocketConn, _, err = dialer.Dial(p.Websocket.GetWebsocketURL(),
http.Header{})
err := p.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
p.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
p.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
go p.WsHandleData()
defer p.WebsocketConn.Close()
err = p.wsSendAuthorisedCommand("subscribe")
if err != nil {
t.Error(err)
t.Fatal(err)
}
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
@@ -14,6 +13,7 @@ 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"
)
@@ -33,22 +33,10 @@ var (
// WsConnect initiates a websocket connection
func (p *Poloniex) WsConnect() error {
if !p.Websocket.IsEnabled() || !p.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if p.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(p.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
p.WebsocketConn, _, err = dialer.Dial(p.Websocket.GetWebsocketURL(),
http.Header{})
err := p.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
@@ -71,17 +59,6 @@ func (p *Poloniex) WsConnect() error {
return nil
}
// WsReadData reads data from the websocket connection
func (p *Poloniex) WsReadData() (exchange.WebsocketResponse, error) {
_, resp, err := p.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
p.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{Raw: resp}, nil
}
func getWSDataType(data interface{}) string {
subData := data.([]interface{})
dataType := subData[0].(string)
@@ -106,12 +83,12 @@ func (p *Poloniex) WsHandleData() {
return
default:
resp, err := p.WsReadData()
resp, err := p.WebsocketConn.ReadMessage()
if err != nil {
p.Websocket.DataHandler <- err
return
}
p.Websocket.TrafficAlert <- struct{}{}
var result interface{}
err = common.JSONDecode(resp.Raw, &result)
if err != nil {
@@ -171,7 +148,7 @@ func (p *Poloniex) WsHandleData() {
continue
}
p.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
p.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: p.GetName(),
Asset: "SPOT",
Pair: currency.NewPairFromString(currencyPair),
@@ -184,7 +161,7 @@ func (p *Poloniex) WsHandleData() {
continue
}
p.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
p.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: p.GetName(),
Asset: "SPOT",
Pair: currency.NewPairFromString(currencyPair),
@@ -204,7 +181,7 @@ func (p *Poloniex) WsHandleData() {
trade.Price, _ = strconv.ParseFloat(dataL3[4].(string), 64)
trade.Timestamp = int64(dataL3[5].(float64))
p.Websocket.DataHandler <- exchange.TradeData{
p.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: time.Unix(trade.Timestamp, 0),
CurrencyPair: currency.NewPairFromString(currencyPair),
Side: trade.Side,
@@ -237,7 +214,7 @@ func (p *Poloniex) wsHandleTickerData(data []interface{}) {
t.HighestTradeIn24H, _ = strconv.ParseFloat(tickerData[8].(string), 64)
t.LowestTradePrice24H, _ = strconv.ParseFloat(tickerData[9].(string), 64)
p.Websocket.DataHandler <- exchange.TickerData{
p.Websocket.DataHandler <- wshandler.TickerData{
Timestamp: time.Now(),
Exchange: p.GetName(),
AssetType: "SPOT",
@@ -494,14 +471,14 @@ var CurrencyPairID = map[int]string{
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (p *Poloniex) GenerateDefaultSubscriptions() {
var subscriptions []exchange.WebsocketChannelSubscription
var subscriptions []wshandler.WebsocketChannelSubscription
// Tickerdata is its own channel
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: fmt.Sprintf("%v", wsTickerDataID),
})
if p.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: fmt.Sprintf("%v", wsAccountNotificationID),
})
}
@@ -509,7 +486,7 @@ func (p *Poloniex) GenerateDefaultSubscriptions() {
enabledCurrencies := p.GetEnabledCurrencies()
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = "_"
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: "orderbook",
Currency: enabledCurrencies[j],
})
@@ -518,7 +495,7 @@ func (p *Poloniex) GenerateDefaultSubscriptions() {
}
// Subscribe sends a websocket message to receive data from the channel
func (p *Poloniex) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (p *Poloniex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
subscriptionRequest := WsCommand{
Command: "subscribe",
}
@@ -530,11 +507,11 @@ func (p *Poloniex) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscri
default:
subscriptionRequest.Channel = channelToSubscribe.Currency.String()
}
return p.wsSend(subscriptionRequest)
return p.WebsocketConn.SendMessage(subscriptionRequest)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (p *Poloniex) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
func (p *Poloniex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
unsubscriptionRequest := WsCommand{
Command: "unsubscribe",
}
@@ -546,21 +523,7 @@ func (p *Poloniex) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubsc
default:
unsubscriptionRequest.Channel = channelToSubscribe.Currency.String()
}
return p.wsSend(unsubscriptionRequest)
}
// WsSend sends data to the websocket server
func (p *Poloniex) wsSend(data interface{}) error {
p.wsRequestMtx.Lock()
defer p.wsRequestMtx.Unlock()
json, err := common.JSONEncode(data)
if err != nil {
return err
}
if p.Verbose {
log.Debugf("%v sending message to websocket %v", p.Name, data)
}
return p.WebsocketConn.WriteMessage(websocket.TextMessage, json)
return p.WebsocketConn.SendMessage(unsubscriptionRequest)
}
func (p *Poloniex) wsSendAuthorisedCommand(command string) error {
@@ -573,5 +536,5 @@ func (p *Poloniex) wsSendAuthorisedCommand(command string) error {
Key: p.APIKey,
Payload: nonce,
}
return p.wsSend(request)
return p.WebsocketConn.SendMessage(request)
}

View File

@@ -12,6 +12,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"
)
@@ -301,7 +302,7 @@ func (p *Poloniex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchang
}
// GetWebsocket returns a pointer to the exchange websocket
func (p *Poloniex) GetWebsocket() (*exchange.Websocket, error) {
func (p *Poloniex) GetWebsocket() (*wshandler.Websocket, error) {
return p.Websocket, nil
}
@@ -396,20 +397,20 @@ func (p *Poloniex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest)
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (p *Poloniex) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
func (p *Poloniex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
p.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (p *Poloniex) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
p.Websocket.UnsubscribeToChannels(channels)
func (p *Poloniex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
p.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (p *Poloniex) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
func (p *Poloniex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return p.Websocket.GetSubscriptions(), nil
}

View File

@@ -12,7 +12,7 @@ const (
WebsocketResponseExtendedTimeout = (15 * time.Second)
// WebsocketChannelOverrideCapacity used in websocket testing
// Defines channel capacity as defaults size can block tests
WebsocketChannelOverrideCapacity = 5
WebsocketChannelOverrideCapacity = 10
)
// GetWebsocketInterfaceChannelOverride returns a new interface based channel

View File

@@ -1,4 +1,4 @@
package exchange
package wshandler
import (
"errors"
@@ -13,9 +13,9 @@ import (
log "github.com/thrasher-/gocryptotrader/logger"
)
// WebsocketInit initialises the websocket struct
func (e *Base) WebsocketInit() {
e.Websocket = &Websocket{
// New initialises the websocket struct
func New() *Websocket {
return &Websocket{
defaultURL: "",
enabled: false,
proxyAddr: "",
@@ -24,37 +24,38 @@ func (e *Base) WebsocketInit() {
}
}
// WebsocketSetup sets main variables for websocket connection
func (e *Base) WebsocketSetup(connector func() error,
// Setup sets main variables for websocket connection
func (w *Websocket) Setup(connector func() error,
subscriber func(channelToSubscribe WebsocketChannelSubscription) error,
unsubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error,
exchangeName string,
wsEnabled,
verbose bool,
defaultURL,
runningURL string) error {
runningURL string,
authenticatedWebsocketAPISupport bool) error {
e.Websocket.DataHandler = make(chan interface{}, 1)
e.Websocket.Connected = make(chan struct{}, 1)
e.Websocket.Disconnected = make(chan struct{}, 1)
e.Websocket.TrafficAlert = make(chan struct{}, 1)
e.Websocket.verbose = verbose
w.DataHandler = make(chan interface{}, 1)
w.Connected = make(chan struct{}, 1)
w.Disconnected = make(chan struct{}, 1)
w.TrafficAlert = make(chan struct{}, 1)
w.verbose = verbose
e.Websocket.SetChannelSubscriber(subscriber)
e.Websocket.SetChannelUnsubscriber(unsubscriber)
err := e.Websocket.SetWsStatusAndConnection(wsEnabled)
w.SetChannelSubscriber(subscriber)
w.SetChannelUnsubscriber(unsubscriber)
err := w.SetWsStatusAndConnection(wsEnabled)
if err != nil {
return err
}
e.Websocket.SetDefaultURL(defaultURL)
e.Websocket.SetConnector(connector)
e.Websocket.SetWebsocketURL(runningURL)
e.Websocket.SetExchangeName(exchangeName)
e.Websocket.SetCanUseAuthenticatedEndpoints(e.AuthenticatedWebsocketAPISupport)
w.SetDefaultURL(defaultURL)
w.SetConnector(connector)
w.SetWebsocketURL(runningURL)
w.SetExchangeName(exchangeName)
w.SetCanUseAuthenticatedEndpoints(authenticatedWebsocketAPISupport)
e.Websocket.init = false
e.Websocket.noConnectionCheckLimit = 5
e.Websocket.reconnectionLimit = 10
w.init = false
w.noConnectionCheckLimit = 5
w.reconnectionLimit = 10
return nil
}
@@ -269,9 +270,6 @@ func (w *Websocket) trafficMonitor(wg *sync.WaitGroup) {
w.connected = true
}
w.m.Unlock()
if w.verbose {
log.Debugf("%v received a traffic alert", w.exchangeName)
}
trafficTimer.Reset(WebsocketTrafficLimitTime)
case <-trafficTimer.C: // Falls through when timer runs out
newtimer := time.NewTimer(10 * time.Second) // New secondary timer set
@@ -689,6 +687,15 @@ func (w *Websocket) FormatFunctionality() string {
case WebsocketWithdrawSupported:
functionality = append(functionality, WebsocketWithdrawSupportedText)
case WebsocketMessageCorrelationSupported:
functionality = append(functionality, WebsocketMessageCorrelationSupportedText)
case WebsocketSequenceNumberSupported:
functionality = append(functionality, WebsocketSequenceNumberSupportedText)
case WebsocketDeadMansSwitchSupported:
functionality = append(functionality, WebsocketDeadMansSwitchSupportedText)
default:
functionality = append(functionality,
fmt.Sprintf("%s[1<<%v]", UnknownWebsocketFunctionality, i))
@@ -808,6 +815,13 @@ func (w *Websocket) unsubscribeToChannels() error {
return nil
}
// RemoveSubscribedChannels removes supplied channels from channelsToSubscribe
func (w *Websocket) RemoveSubscribedChannels(channels []WebsocketChannelSubscription) {
for i := range channels {
w.removeChannelToSubscribe(channels[i])
}
}
// removeChannelToSubscribe removes an entry from w.channelsToSubscribe
// so an unsubscribe event can be triggered
func (w *Websocket) removeChannelToSubscribe(subscribedChannel WebsocketChannelSubscription) {
@@ -867,13 +881,6 @@ func (w *Websocket) SubscribeToChannels(channels []WebsocketChannelSubscription)
w.noConnectionChecks = 0
}
// UnsubscribeToChannels removes supplied channels from channelsToSubscribe
func (w *Websocket) UnsubscribeToChannels(channels []WebsocketChannelSubscription) {
for i := range channels {
w.removeChannelToSubscribe(channels[i])
}
}
// Equal two WebsocketChannelSubscription to determine equality
func (w *WebsocketChannelSubscription) Equal(subscribedChannel *WebsocketChannelSubscription) bool {
return strings.EqualFold(w.Channel, subscribedChannel.Channel) &&

View File

@@ -0,0 +1,177 @@
package wshandler
import (
"bytes"
"compress/flate"
"compress/gzip"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
log "github.com/thrasher-/gocryptotrader/logger"
)
// AddResponseWithID adds data to IDResponses with locks and a nil check
func (w *WebsocketConnection) AddResponseWithID(id int64, data []byte) {
w.Lock()
defer w.Unlock()
if w.IDResponses == nil {
w.IDResponses = make(map[int64][]byte)
}
w.IDResponses[id] = data
}
// Dial sets proxy urls and then connects to the websocket
func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header) error {
if w.ProxyURL != "" {
proxy, err := url.Parse(w.ProxyURL)
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
var conStatus *http.Response
w.Connection, conStatus, err = dialer.Dial(w.URL, headers)
if err != nil {
if conStatus != nil {
return fmt.Errorf("%v %v %v Error: %v", w.URL, conStatus, conStatus.StatusCode, err)
}
return fmt.Errorf("%v Error: %v", w.URL, err)
}
return nil
}
// SendMessage the one true message request. Sends message to WS
func (w *WebsocketConnection) SendMessage(data interface{}) error {
w.Lock()
defer w.Unlock()
json, err := common.JSONEncode(data)
if err != nil {
return err
}
if w.Verbose {
log.Debugf("%v sending message to websocket %v", w.ExchangeName, string(json))
}
if w.RateLimit > 0 {
time.Sleep(time.Duration(w.RateLimit) * time.Millisecond)
}
return w.Connection.WriteMessage(websocket.TextMessage, json)
}
// SendMessageReturnResponse will send a WS message to the connection
// It will then run a goroutine to await a JSON response
// If there is no response it will return an error
func (w *WebsocketConnection) SendMessageReturnResponse(id int64, request interface{}) ([]byte, error) {
err := w.SendMessage(request)
if err != nil {
return nil, err
}
var wg sync.WaitGroup
wg.Add(1)
go w.WaitForResult(id, &wg)
defer func() {
delete(w.IDResponses, id)
}()
wg.Wait()
if _, ok := w.IDResponses[id]; !ok {
return nil, fmt.Errorf("timeout waiting for response with ID %v", id)
}
return w.IDResponses[id], nil
}
// WaitForResult will keep checking w.IDResponses for a response ID
// If the timer expires, it will return without
func (w *WebsocketConnection) WaitForResult(id int64, wg *sync.WaitGroup) {
defer wg.Done()
timer := time.NewTimer(w.ResponseMaxLimit)
for {
select {
case <-timer.C:
return
default:
w.Lock()
for k := range w.IDResponses {
if k == id {
w.Unlock()
return
}
}
w.Unlock()
time.Sleep(w.ResponseCheckTimeout)
}
}
}
// ReadMessage reads messages, can handle text, gzip and binary
func (w *WebsocketConnection) ReadMessage() (WebsocketResponse, error) {
mType, resp, err := w.Connection.ReadMessage()
if err != nil {
return WebsocketResponse{}, err
}
var standardMessage []byte
switch mType {
case websocket.TextMessage:
standardMessage = resp
case websocket.BinaryMessage:
standardMessage, err = w.parseBinaryResponse(resp)
if err != nil {
return WebsocketResponse{}, err
}
}
if w.Verbose {
log.Debugf("%v Websocket message received: %v",
w.ExchangeName,
string(standardMessage))
}
return WebsocketResponse{Raw: standardMessage, Type: mType}, nil
}
// parseBinaryResponse parses a websocket binaray response into a usable byte array
func (w *WebsocketConnection) parseBinaryResponse(resp []byte) ([]byte, error) {
var standardMessage []byte
var err error
// Detect GZIP
if resp[0] == 31 && resp[1] == 139 {
b := bytes.NewReader(resp)
var gReader *gzip.Reader
gReader, err = gzip.NewReader(b)
if err != nil {
return standardMessage, err
}
standardMessage, err = ioutil.ReadAll(gReader)
if err != nil {
return standardMessage, err
}
err = gReader.Close()
if err != nil {
return standardMessage, err
}
} else {
reader := flate.NewReader(bytes.NewReader(resp))
standardMessage, err = ioutil.ReadAll(reader)
if err != nil {
return standardMessage, err
}
err = reader.Close()
if err != nil {
return standardMessage, err
}
}
return standardMessage, nil
}
// GenerateMessageID Creates a messageID to checkout
func (w *WebsocketConnection) GenerateMessageID(useNano bool) int64 {
if useNano {
return time.Now().UnixNano()
}
return time.Now().Unix()
}

View File

@@ -0,0 +1,203 @@
package wshandler
import (
"bytes"
"compress/flate"
"compress/gzip"
"errors"
"net/http"
"os"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency"
)
const (
websocketTestURL = "wss://www.bitmex.com/realtime"
returnResponseURL = "wss://ws.kraken.com"
useProxyTests = false // Disabled by default. Freely available proxy servers that work all the time are difficult to find
proxyURL = "http://212.186.171.4:80" // Replace with a usable proxy server
)
var wc *WebsocketConnection
var dialer websocket.Dialer
type testStruct struct {
Error error
WC WebsocketConnection
}
type testRequest struct {
Event string `json:"event"`
RequestID int64 `json:"reqid,omitempty"`
Pairs []string `json:"pair"`
Subscription testRequestData `json:"subscription,omitempty"`
}
// testRequestData contains details on WS channel
type testRequestData struct {
Name string `json:"name,omitempty"`
Interval int64 `json:"interval,omitempty"`
Depth int64 `json:"depth,omitempty"`
}
type testResponse struct {
RequestID int64 `json:"reqid,omitempty"`
}
// TestMain setup test
func TestMain(m *testing.M) {
wc = &WebsocketConnection{
ExchangeName: "test",
Verbose: true,
URL: returnResponseURL,
ResponseMaxLimit: 7000000000,
ResponseCheckTimeout: 30000000,
}
os.Exit(m.Run())
}
// TestDial logic test
func TestDial(t *testing.T) {
var testCases = []testStruct{
{Error: nil, WC: WebsocketConnection{ExchangeName: "test1", Verbose: true, URL: websocketTestURL, RateLimit: 10, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
{Error: errors.New(" Error: malformed ws or wss URL"), WC: WebsocketConnection{ExchangeName: "test2", Verbose: true, URL: "", ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
{Error: nil, WC: WebsocketConnection{ExchangeName: "test3", Verbose: true, URL: websocketTestURL, ProxyURL: proxyURL, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
}
for i := 0; i < len(testCases); i++ {
testData := &testCases[i]
t.Run(testData.WC.ExchangeName, func(t *testing.T) {
if testData.WC.ProxyURL != "" && !useProxyTests {
t.Skip("Proxy testing not enabled, skipping")
}
err := testData.WC.Dial(&dialer, http.Header{})
if err != nil {
if testData.Error != nil && err.Error() == testData.Error.Error() {
return
}
t.Fatal(err)
}
})
}
}
// TestSendMessage logic test
func TestSendMessage(t *testing.T) {
var testCases = []testStruct{
{Error: nil, WC: WebsocketConnection{ExchangeName: "test1", Verbose: true, URL: websocketTestURL, RateLimit: 10, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
{Error: errors.New(" Error: malformed ws or wss URL"), WC: WebsocketConnection{ExchangeName: "test2", Verbose: true, URL: "", ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
{Error: nil, WC: WebsocketConnection{ExchangeName: "test3", Verbose: true, URL: websocketTestURL, ProxyURL: proxyURL, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
}
for i := 0; i < len(testCases); i++ {
testData := &testCases[i]
t.Run(testData.WC.ExchangeName, func(t *testing.T) {
if testData.WC.ProxyURL != "" && !useProxyTests {
t.Skip("Proxy testing not enabled, skipping")
}
err := testData.WC.Dial(&dialer, http.Header{})
if err != nil {
if testData.Error != nil && err.Error() == testData.Error.Error() {
return
}
t.Fatal(err)
}
err = testData.WC.SendMessage("ping")
if err != nil {
t.Error(err)
}
})
}
}
// TestSendMessageWithResponse logic test
func TestSendMessageWithResponse(t *testing.T) {
if wc.ProxyURL != "" && !useProxyTests {
t.Skip("Proxy testing not enabled, skipping")
}
err := wc.Dial(&dialer, http.Header{})
if err != nil {
t.Fatal(err)
}
go readMesages(wc, t)
request := testRequest{
Event: "subscribe",
Pairs: []string{currency.NewPairWithDelimiter("XBT", "USD", "/").String()},
Subscription: testRequestData{
Name: "ticker",
},
RequestID: wc.GenerateMessageID(true),
}
_, err = wc.SendMessageReturnResponse(request.RequestID, request)
if err != nil {
t.Error(err)
}
}
// TestParseBinaryResponse logic test
func TestParseBinaryResponse(t *testing.T) {
var b bytes.Buffer
w := gzip.NewWriter(&b)
w.Write([]byte("hello"))
w.Close()
resp, err := wc.parseBinaryResponse(b.Bytes())
if err != nil {
t.Error(err)
}
if !strings.EqualFold(string(resp), "hello") {
t.Errorf("GZip conversion failed. Received: '%v', Expected: 'hello'", string(resp))
}
var b2 bytes.Buffer
w2, err2 := flate.NewWriter(&b2, 1)
if err2 != nil {
t.Error(err2)
}
w2.Write([]byte("hello"))
w2.Close()
resp2, err3 := wc.parseBinaryResponse(b2.Bytes())
if err3 != nil {
t.Error(err3)
}
if !strings.EqualFold(string(resp2), "hello") {
t.Errorf("GZip conversion failed. Received: '%v', Expected: 'hello'", string(resp2))
}
}
// TestAddResponseWithID logic test
func TestAddResponseWithID(t *testing.T) {
wc.IDResponses = nil
wc.AddResponseWithID(0, []byte("hi"))
wc.AddResponseWithID(1, []byte("hi"))
}
// readMesages helper func
func readMesages(wc *WebsocketConnection, t *testing.T) {
timer := time.NewTimer(20 * time.Second)
for {
select {
case <-timer.C:
return
default:
resp, err := wc.ReadMessage()
if err != nil {
t.Error(err)
return
}
var incoming testResponse
err = common.JSONDecode(resp.Raw, &incoming)
if err != nil {
t.Error(err)
return
}
if incoming.RequestID > 0 {
wc.AddResponseWithID(incoming.RequestID, resp.Raw)
return
}
}
}
}

View File

@@ -0,0 +1,25 @@
package wshandler
import (
"sync"
"time"
"github.com/gorilla/websocket"
)
// WebsocketConnection contains all the data needed to send a message to a WS
type WebsocketConnection struct {
sync.Mutex
Verbose bool
RateLimit float64
ExchangeName string
URL string
ProxyURL string
Wg sync.WaitGroup
Connection *websocket.Conn
Shutdown chan struct{}
// These are the request IDs and the corresponding response JSON
IDResponses map[int64][]byte
ResponseCheckTimeout time.Duration
ResponseMaxLimit time.Duration
}

View File

@@ -1,4 +1,4 @@
package exchange
package wshandler
import (
"fmt"
@@ -10,52 +10,48 @@ import (
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
)
var wsTest Base
var ws *Websocket
func TestWebsocketInit(t *testing.T) {
if wsTest.Websocket != nil {
t.Error("test failed - WebsocketInit() error")
}
wsTest.WebsocketInit()
if wsTest.Websocket == nil {
t.Error("test failed - WebsocketInit() error")
ws = New()
if ws == nil {
t.Error("test failed - Websocket New() error")
}
}
func TestWebsocket(t *testing.T) {
if err := wsTest.Websocket.SetProxyAddress("testProxy"); err != nil {
if err := ws.SetProxyAddress("testProxy"); err != nil {
t.Error("test failed - SetProxyAddress", err)
}
wsTest.WebsocketSetup(func() error { return nil },
ws.Setup(func() error { return nil },
func(test WebsocketChannelSubscription) error { return nil },
func(test WebsocketChannelSubscription) error { return nil },
"testName",
true,
false,
"testDefaultURL",
"testRunningURL")
"testRunningURL",
false)
// Test variable setting and retreival
if wsTest.Websocket.GetName() != "testName" {
if ws.GetName() != "testName" {
t.Error("test failed - WebsocketSetup")
}
if !wsTest.Websocket.IsEnabled() {
if !ws.IsEnabled() {
t.Error("test failed - WebsocketSetup")
}
if wsTest.Websocket.GetProxyAddress() != "testProxy" {
if ws.GetProxyAddress() != "testProxy" {
t.Error("test failed - WebsocketSetup")
}
if wsTest.Websocket.GetDefaultURL() != "testDefaultURL" {
if ws.GetDefaultURL() != "testDefaultURL" {
t.Error("test failed - WebsocketSetup")
}
if wsTest.Websocket.GetWebsocketURL() != "testRunningURL" {
if ws.GetWebsocketURL() != "testRunningURL" {
t.Error("test failed - WebsocketSetup")
}
@@ -69,53 +65,53 @@ func TestWebsocket(t *testing.T) {
return
}
select {
case <-wsTest.Websocket.Connected:
case <-ws.Connected:
count++
case <-wsTest.Websocket.Disconnected:
case <-ws.Disconnected:
count++
}
}
}()
// -- Not connected shutdown
err := wsTest.Websocket.Shutdown()
err := ws.Shutdown()
if err == nil {
t.Fatal("test failed - should not be connected to able to shut down")
}
wsTest.Websocket.Wg.Wait()
ws.Wg.Wait()
// -- Normal connect
err = wsTest.Websocket.Connect()
err = ws.Connect()
if err != nil {
t.Fatal("test failed - WebsocketSetup", err)
}
// -- Already connected connect
err = wsTest.Websocket.Connect()
err = ws.Connect()
if err == nil {
t.Fatal("test failed - should not connect, already connected")
}
wsTest.Websocket.SetWebsocketURL("")
ws.SetWebsocketURL("")
// -- Set true when already true
err = wsTest.Websocket.SetWsStatusAndConnection(true)
err = ws.SetWsStatusAndConnection(true)
if err == nil {
t.Fatal("test failed - setting enabled should not work")
}
// -- Set false normal
err = wsTest.Websocket.SetWsStatusAndConnection(false)
err = ws.SetWsStatusAndConnection(false)
if err != nil {
t.Fatal("test failed - setting enabled should not work")
}
// -- Set true normal
err = wsTest.Websocket.SetWsStatusAndConnection(true)
err = ws.SetWsStatusAndConnection(true)
if err != nil {
t.Fatal("test failed - setting enabled should not work")
}
// -- Normal shutdown
err = wsTest.Websocket.Shutdown()
err = ws.Shutdown()
if err != nil {
t.Fatal("test failed - WebsocketSetup", err)
}
@@ -163,7 +159,7 @@ func TestInsertingSnapShots(t *testing.T) {
snapShot1.AssetType = "SPOT"
snapShot1.Pair = currency.NewPairFromString("BTCUSD")
wsTest.Websocket.Orderbook.LoadSnapshot(&snapShot1, "ExchangeTest", false)
ws.Orderbook.LoadSnapshot(&snapShot1, "ExchangeTest", false)
var snapShot2 orderbook.Base
asks = []orderbook.Item{
@@ -199,7 +195,7 @@ func TestInsertingSnapShots(t *testing.T) {
snapShot2.AssetType = "SPOT"
snapShot2.Pair = currency.NewPairFromString("LTCUSD")
wsTest.Websocket.Orderbook.LoadSnapshot(&snapShot2, "ExchangeTest", false)
ws.Orderbook.LoadSnapshot(&snapShot2, "ExchangeTest", false)
var snapShot3 orderbook.Base
asks = []orderbook.Item{
@@ -235,9 +231,9 @@ func TestInsertingSnapShots(t *testing.T) {
snapShot3.AssetType = "FUTURES"
snapShot3.Pair = currency.NewPairFromString("LTCUSD")
wsTest.Websocket.Orderbook.LoadSnapshot(&snapShot3, "ExchangeTest", false)
ws.Orderbook.LoadSnapshot(&snapShot3, "ExchangeTest", false)
if len(wsTest.Websocket.Orderbook.ob) != 3 {
if len(ws.Orderbook.ob) != 3 {
t.Error("test failed - inserting orderbook data")
}
}
@@ -259,7 +255,7 @@ func TestUpdate(t *testing.T) {
{Price: 1337, Amount: 100}, // Append
{Price: 1336, Amount: 0}, // Ghost delete
}
err := wsTest.Websocket.Orderbook.Update(bidTargets,
err := ws.Orderbook.Update(bidTargets,
askTargets,
LTCUSDPAIR,
time.Now(),
@@ -270,7 +266,7 @@ func TestUpdate(t *testing.T) {
t.Error("test failed - OrderbookUpdate error", err)
}
err = wsTest.Websocket.Orderbook.Update(bidTargets,
err = ws.Orderbook.Update(bidTargets,
askTargets,
LTCUSDPAIR,
time.Now(),
@@ -295,7 +291,7 @@ func TestUpdate(t *testing.T) {
{Price: 1336, Amount: 0}, // Ghost delete
}
err = wsTest.Websocket.Orderbook.Update(bidTargets,
err = ws.Orderbook.Update(bidTargets,
askTargets,
BTCUSDPAIR,
time.Now(),

View File

@@ -1,4 +1,4 @@
package exchange
package wshandler
import (
"sync"
@@ -24,6 +24,9 @@ const (
WebsocketSubmitOrderSupported
WebsocketCancelOrderSupported
WebsocketWithdrawSupported
WebsocketMessageCorrelationSupported
WebsocketSequenceNumberSupported
WebsocketDeadMansSwitchSupported
WebsocketTickerSupportedText = "TICKER STREAMING SUPPORTED"
WebsocketOrderbookSupportedText = "ORDERBOOK STREAMING SUPPORTED"
@@ -40,6 +43,9 @@ const (
WebsocketSubmitOrderSupportedText = "WEBSOCKET SUBMIT ORDER SUPPORTED"
WebsocketCancelOrderSupportedText = "WEBSOCKET CANCEL ORDER SUPPORTED"
WebsocketWithdrawSupportedText = "WEBSOCKET WITHDRAW SUPPORTED"
WebsocketMessageCorrelationSupportedText = "WEBSOCKET MESSAGE CORRELATION SUPPORTED"
WebsocketSequenceNumberSupportedText = "WEBSOCKET SEQUENCE NUMBER SUPPORTED"
WebsocketDeadMansSwitchSupportedText = "WEBSOCKET DEAD MANS SWITCH SUPPORTED"
// WebsocketNotEnabled alerts of a disabled websocket
WebsocketNotEnabled = "exchange_websocket_not_enabled"
@@ -75,11 +81,10 @@ type Websocket struct {
noConnectionChecks int
reconnectionChecks int
noConnectionCheckLimit int
// Subscriptions stuff
subscribedChannels []WebsocketChannelSubscription
channelsToSubscribe []WebsocketChannelSubscription
channelSubscriber func(channelToSubscribe WebsocketChannelSubscription) error
channelUnsubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error
subscribedChannels []WebsocketChannelSubscription
channelsToSubscribe []WebsocketChannelSubscription
channelSubscriber func(channelToSubscribe WebsocketChannelSubscription) error
channelUnsubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error
// Connected denotes a channel switch for diversion of request flow
Connected chan struct{}
// Disconnected denotes a channel switch for diversion of request flow
@@ -87,11 +92,9 @@ type Websocket struct {
// DataHandler pipes websocket data to an exchange websocket data handler
DataHandler chan interface{}
// ShutdownC is the main shutdown channel which controls all websocket go funcs
ShutdownC chan struct{}
ShutdownConnectionMonitor chan struct{}
ShutdownC chan struct{}
// Orderbook is a local cache of orderbooks
Orderbook WebsocketOrderbookLocal
// Wg defines a wait group for websocket routines for cleanly shutting down
// routines
Wg sync.WaitGroup

Some files were not shown because too many files have changed in this diff Show More