mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"),
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) &&
|
||||
177
exchanges/wshandler/websocket_connection.go
Normal file
177
exchanges/wshandler/websocket_connection.go
Normal 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()
|
||||
}
|
||||
203
exchanges/wshandler/websocket_connection_test.go
Normal file
203
exchanges/wshandler/websocket_connection_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
exchanges/wshandler/websocket_connection_types.go
Normal file
25
exchanges/wshandler/websocket_connection_types.go
Normal 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
|
||||
}
|
||||
@@ -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(),
|
||||
@@ -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
Reference in New Issue
Block a user