From 6a15ecc65c32480cdb95ffc5a8d28e2be959d118 Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 18 Mar 2019 15:18:36 +1100 Subject: [PATCH] OKEX/OKCoin API combine via OKGroup (#252) * Initial commit * Successful authenticated request implementation. * Removes data from okcoin, okex. Implements some account okgroup endpoints. Adds tests * Finishes account API endpoint implementations. * Implements and adds tests for the following okgroup v3 API endpoints: GetSpotTradingAccounts, GetSpotTradingAccountForCurrency, GetSpotBillDetailsForCurrency, PlaceSpotOrder, PlaceMultipleSpotOrders, CancelSpotOrder, CancelMultipleSpotOrders, GetSpotOrders, GetSpotOpenOrders, GetSpotOrder, GetSpotTransactionDetails, GetSpotTokenPairDetails, GetSpotOrderBook, GetSpotAllTokenPairsInformation, GetSpotAllTokenPairsInformationForCurrency, GetSpotFilledOrdersInformation, GetSpotMarketData * Implements and adds tests for all margin api endpoints: GetMarginTradingAccounts, GetMarginTradingAccountsForCurrency, GetMarginBillDetails, GetMarginAccountSettings, GetMarginLoanHistory, OpenMarginLoan, RepayMarginLoan, PlaceMarginOrder, PlaceMultipleMarginOrders, CancelMarginOrder, CancelMultipleMarginOrders, GetMarginOrders, GetMarginOpenOrders, GetMarginOrder, GetMarginTransactionDetails. Simplifies some Spot endpoints combining ForCurrency funcs where possible * Adds all 29 Futures endpoints with tests. Updates comments and naming conventions. Adds standard realordertest func. Adds ability to make public API requests. Adds test purpose comments * Adds 29 endpoints for SWAP API support. Adds tests for each endpoint. Declares response variables in function declaration. Simplifies URL parameter formatting * Adds all ETT endpoints with tests * Creates OKCoin and OKEX struct types. Moves okgroup tests to okcoin and okex exchanges. Updates withdraw fee calculation. Updates exchange.go exchange declaration to point to new types. Streamlines wrapper tests. Begins websocket integration * Rebase fixes * Deletes okcoin_types.go, okcoin_wrapper.go, okex_types.go, okex_wrapper.go. Combines okex,okcoin wrappers in okgroup_wrapper.go. Removes boilerplate url.values with new request struct type parsing. Adds github.com/google/go-querystring to go modules. Replaces USDT with USD for OKCoin tests. Moves OKEX specific endpoints (futures, swap & ett) to okex.go. Fixes recieving receiving again * Maps the wrapper * Parses json into time.Time instead of string + conversion * Renames websocket.SetEnabled to websocket.SetWsStatusAndConnection. Maps main spot websocket functions for okgroup. Adds some basic ws tests * Updates websocket default URLS, adds checksum tests, removes setdefaults from okgroup, adds WebsocketDataWrapper to wrap all okgroup websocket data responses, handles spot, swap, index and futures ticker, candle, trade, orderbook, funding fee websocket responses. Partially implements checksum validation on orderbook data. Fixes all linting issues * Handles the calculation of okgroup websocket checksums. Adds tests * Now all orderbook checksums are validated. Cleans up implementations and removes invalid checksum calculator functions. Adds function to parse ordertype. Puts verbose logs behind verbose check * Removes parallel from okgroup tests. Removes unused code. Adds GetWsChannelWithoutOrderType. Improves handling of WS data types. Adds types for more ws channels. Simplifies update orderbook handling * updates btse func name * linting * Fixes websocket connection issue with tests. Removes test verbosity * Updates checksum calculation to handle more than 7 decimal places. Adds rate limiters. Uses != "" instead of len > 0. Adds new test to handle checksum calculation with 8 decimal places. * Removes logging. Fixes orderbook wrapper references * Adds slightly more robust resubscribe func. Adds websocket tests that can detect websocket errors * Fixes linting issues * Adds new WS func IsConnected() to expose ws status. Tests protect against channel timeout * Adds test comments. Fixes parallel issues for futures tests * Fixes error output for wrapper function --- exchanges/bitfinex/bitfinex.go | 2 +- exchanges/bitflyer/bitflyer.go | 2 +- exchanges/bithumb/bithumb.go | 2 +- exchanges/bitmex/bitmex.go | 2 +- exchanges/bitstamp/bitstamp.go | 2 +- exchanges/btcc/btcc.go | 2 +- exchanges/btse/btse.go | 2 +- exchanges/coinbasepro/coinbasepro.go | 2 +- exchanges/coinut/coinut.go | 2 +- exchanges/exchange.go | 4 +- exchanges/exchange_websocket.go | 12 +- exchanges/exchange_websocket_test.go | 6 +- exchanges/hitbtc/hitbtc.go | 2 +- exchanges/huobi/huobi.go | 2 +- exchanges/okcoin/okcoin.go | 1048 +----------- exchanges/okcoin/okcoin_test.go | 1202 ++++++++++++-- exchanges/okcoin/okcoin_types.go | 444 ----- exchanges/okcoin/okcoin_websocket.go | 348 ---- exchanges/okcoin/okcoin_wrapper.go | 396 ----- exchanges/okex/okex.go | 1539 +++++------------- exchanges/okex/okex_test.go | 2065 ++++++++++++++++++++---- exchanges/okex/okex_types.go | 494 ------ exchanges/okex/okex_websocket.go | 318 ---- exchanges/okex/okex_wrapper.go | 394 ----- exchanges/okgroup/README.md | 133 ++ exchanges/okgroup/okgroup.go | 840 ++++++++++ exchanges/okgroup/okgroup_types.go | 1514 +++++++++++++++++ exchanges/okgroup/okgroup_websocket.go | 759 +++++++++ exchanges/okgroup/okgroup_wrapper.go | 411 +++++ exchanges/orderbook/orderbook.go | 4 +- exchanges/poloniex/poloniex.go | 2 +- exchanges/yobit/yobit.go | 2 +- exchanges/zb/zb.go | 2 +- go.mod | 1 + go.sum | 2 + tools/exchange_template/main_file.tmpl | 2 +- 36 files changed, 6868 insertions(+), 5096 deletions(-) delete mode 100644 exchanges/okcoin/okcoin_types.go delete mode 100644 exchanges/okcoin/okcoin_websocket.go delete mode 100644 exchanges/okcoin/okcoin_wrapper.go delete mode 100644 exchanges/okex/okex_types.go delete mode 100644 exchanges/okex/okex_websocket.go delete mode 100644 exchanges/okex/okex_wrapper.go create mode 100644 exchanges/okgroup/README.md create mode 100644 exchanges/okgroup/okgroup.go create mode 100644 exchanges/okgroup/okgroup_types.go create mode 100644 exchanges/okgroup/okgroup_websocket.go create mode 100644 exchanges/okgroup/okgroup_wrapper.go diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 45ebcdef..970bc05a 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -129,7 +129,7 @@ func (b *Bitfinex) Setup(exch config.ExchangeConfig) { b.SetHTTPClientUserAgent(exch.HTTPUserAgent) b.RESTPollingDelay = exch.RESTPollingDelay b.Verbose = exch.Verbose - b.Websocket.SetEnabled(exch.Websocket) + b.Websocket.SetWsStatusAndConnection(exch.Websocket) b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/bitflyer/bitflyer.go b/exchanges/bitflyer/bitflyer.go index ce9aa633..00cfeb6b 100644 --- a/exchanges/bitflyer/bitflyer.go +++ b/exchanges/bitflyer/bitflyer.go @@ -114,7 +114,7 @@ func (b *Bitflyer) Setup(exch config.ExchangeConfig) { b.SetHTTPClientUserAgent(exch.HTTPUserAgent) b.RESTPollingDelay = exch.RESTPollingDelay b.Verbose = exch.Verbose - b.Websocket.SetEnabled(exch.Websocket) + b.Websocket.SetWsStatusAndConnection(exch.Websocket) b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go index bb63f441..d8edccb9 100644 --- a/exchanges/bithumb/bithumb.go +++ b/exchanges/bithumb/bithumb.go @@ -96,7 +96,7 @@ func (b *Bithumb) Setup(exch config.ExchangeConfig) { b.SetHTTPClientUserAgent(exch.HTTPUserAgent) b.RESTPollingDelay = exch.RESTPollingDelay b.Verbose = exch.Verbose - b.Websocket.SetEnabled(exch.Websocket) + b.Websocket.SetWsStatusAndConnection(exch.Websocket) b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index 258d1f6a..f5738045 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -148,7 +148,7 @@ func (b *Bitmex) Setup(exch config.ExchangeConfig) { b.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) b.RESTPollingDelay = exch.RESTPollingDelay b.Verbose = exch.Verbose - b.Websocket.SetEnabled(exch.Websocket) + b.Websocket.SetWsStatusAndConnection(exch.Websocket) b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 5cf036aa..a827150b 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -103,7 +103,7 @@ func (b *Bitstamp) Setup(exch config.ExchangeConfig) { b.SetHTTPClientUserAgent(exch.HTTPUserAgent) b.RESTPollingDelay = exch.RESTPollingDelay b.Verbose = exch.Verbose - b.Websocket.SetEnabled(exch.Websocket) + b.Websocket.SetWsStatusAndConnection(exch.Websocket) b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/btcc/btcc.go b/exchanges/btcc/btcc.go index 08f59ee4..2801603a 100644 --- a/exchanges/btcc/btcc.go +++ b/exchanges/btcc/btcc.go @@ -59,7 +59,7 @@ func (b *BTCC) Setup(exch config.ExchangeConfig) { b.SetHTTPClientUserAgent(exch.HTTPUserAgent) b.RESTPollingDelay = exch.RESTPollingDelay b.Verbose = exch.Verbose - b.Websocket.SetEnabled(exch.Websocket) + b.Websocket.SetWsStatusAndConnection(exch.Websocket) b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/btse/btse.go b/exchanges/btse/btse.go index d9db0cde..0203eb2a 100644 --- a/exchanges/btse/btse.go +++ b/exchanges/btse/btse.go @@ -81,7 +81,7 @@ func (b *BTSE) Setup(exch config.ExchangeConfig) { b.SetHTTPClientUserAgent(exch.HTTPUserAgent) b.RESTPollingDelay = exch.RESTPollingDelay b.Verbose = exch.Verbose - b.Websocket.SetEnabled(exch.Websocket) + b.Websocket.SetWsStatusAndConnection(exch.Websocket) b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 3880e871..ad4be0d9 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -103,7 +103,7 @@ func (c *CoinbasePro) Setup(exch config.ExchangeConfig) { c.SetHTTPClientUserAgent(exch.HTTPUserAgent) c.RESTPollingDelay = exch.RESTPollingDelay c.Verbose = exch.Verbose - c.Websocket.SetEnabled(exch.Websocket) + c.Websocket.SetWsStatusAndConnection(exch.Websocket) c.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") c.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") c.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 7f8db663..d3d057aa 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -94,7 +94,7 @@ func (c *COINUT) Setup(exch config.ExchangeConfig) { c.SetHTTPClientUserAgent(exch.HTTPUserAgent) c.RESTPollingDelay = exch.RESTPollingDelay c.Verbose = exch.Verbose - c.Websocket.SetEnabled(exch.Websocket) + c.Websocket.SetWsStatusAndConnection(exch.Websocket) c.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") c.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") c.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/exchange.go b/exchanges/exchange.go index fd04d2c5..577cbd85 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -240,9 +240,9 @@ type OrderDetail struct { type FundHistory struct { ExchangeName string Status string - TransferID int64 + TransferID string Description string - Timestamp int64 + Timestamp time.Time Currency string Amount float64 Fee float64 diff --git a/exchanges/exchange_websocket.go b/exchanges/exchange_websocket.go index e54f9400..b50b9377 100644 --- a/exchanges/exchange_websocket.go +++ b/exchanges/exchange_websocket.go @@ -66,7 +66,7 @@ func (e *Base) WebsocketSetup(connector func() error, e.Websocket.Disconnected = make(chan struct{}, 1) e.Websocket.TrafficAlert = make(chan struct{}, 1) - err := e.Websocket.SetEnabled(wsEnabled) + err := e.Websocket.SetWsStatusAndConnection(wsEnabled) if err != nil { return err } @@ -212,6 +212,11 @@ func (w *Websocket) Connect() error { return nil } +// IsConnected exposes websocket connection status +func (w *Websocket) IsConnected() bool { + return w.connected +} + // Shutdown attempts to shut down a websocket connection and associated routines // by using a package defined shutdown function func (w *Websocket) Shutdown() error { @@ -259,8 +264,9 @@ func (w *Websocket) GetWebsocketURL() string { return w.runningURL } -// SetEnabled sets if websocket is enabled -func (w *Websocket) SetEnabled(enabled bool) error { +// SetWsStatusAndConnection sets if websocket is enabled +// it will also connect/disconnect the websocket connection +func (w *Websocket) SetWsStatusAndConnection(enabled bool) error { if w.enabled == enabled { if w.init { return nil diff --git a/exchanges/exchange_websocket_test.go b/exchanges/exchange_websocket_test.go index 6ba5494c..ee7a0549 100644 --- a/exchanges/exchange_websocket_test.go +++ b/exchanges/exchange_websocket_test.go @@ -93,19 +93,19 @@ func TestWebsocket(t *testing.T) { wsTest.Websocket.SetWebsocketURL("") // -- Set true when already true - err = wsTest.Websocket.SetEnabled(true) + err = wsTest.Websocket.SetWsStatusAndConnection(true) if err == nil { t.Fatal("test failed - setting enabled should not work") } // -- Set false normal - err = wsTest.Websocket.SetEnabled(false) + err = wsTest.Websocket.SetWsStatusAndConnection(false) if err != nil { t.Fatal("test failed - setting enabled should not work") } // -- Set true normal - err = wsTest.Websocket.SetEnabled(true) + err = wsTest.Websocket.SetWsStatusAndConnection(true) if err != nil { t.Fatal("test failed - setting enabled should not work") } diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index 360ce145..8d5bb84f 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -95,7 +95,7 @@ func (h *HitBTC) Setup(exch config.ExchangeConfig) { h.SetHTTPClientUserAgent(exch.HTTPUserAgent) h.RESTPollingDelay = exch.RESTPollingDelay // Max 60000ms h.Verbose = exch.Verbose - h.Websocket.SetEnabled(exch.Websocket) + h.Websocket.SetWsStatusAndConnection(exch.Websocket) h.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") h.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") h.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index f8176aa6..81ab0f7f 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -112,7 +112,7 @@ func (h *HUOBI) Setup(exch config.ExchangeConfig) { h.SetHTTPClientUserAgent(exch.HTTPUserAgent) h.RESTPollingDelay = exch.RESTPollingDelay h.Verbose = exch.Verbose - h.Websocket.SetEnabled(exch.Websocket) + h.Websocket.SetWsStatusAndConnection(exch.Websocket) h.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") h.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") h.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/okcoin/okcoin.go b/exchanges/okcoin/okcoin.go index 6bf18b54..c541eb1f 100644 --- a/exchanges/okcoin/okcoin.go +++ b/exchanges/okcoin/okcoin.go @@ -1,1044 +1,58 @@ package okcoin import ( - "errors" - "fmt" - "net/http" - "net/url" - "strconv" - "strings" "time" - "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" - "github.com/thrasher-/gocryptotrader/currency/symbol" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/okgroup" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( - okcoinAPIURL = "https://www.okcoin.com/api/v1/" - okcoinAPIURLBase = "https://www.okcoin.com/api/" - okcoinAPIVersion = "1" - okcoinWebsocketURL = "wss://real.okcoin.com:10440/websocket/okcoinapi" - okcoinInstruments = "instruments" - okcoinTicker = "ticker.do" - okcoinDepth = "depth.do" - okcoinTrades = "trades.do" - okcoinKline = "kline.do" - okcoinUserInfo = "userinfo.do" - okcoinTrade = "trade.do" - okcoinTradeHistory = "trade_history.do" - okcoinTradeBatch = "batch_trade.do" - okcoinOrderCancel = "cancel_order.do" - okcoinOrderInfo = "order_info.do" - okcoinOrdersInfo = "orders_info.do" - okcoinOrderHistory = "order_history.do" - okcoinWithdraw = "withdraw.do" - okcoinWithdrawCancel = "cancel_withdraw.do" - okcoinWithdrawInfo = "withdraw_info.do" - okcoinOrderFee = "order_fee.do" - okcoinLendDepth = "lend_depth.do" - okcoinBorrowsInfo = "borrows_info.do" - okcoinBorrowMoney = "borrow_money.do" - okcoinBorrowCancel = "cancel_borrow.do" - okcoinBorrowOrderInfo = "borrow_order_info.do" - okcoinRepayment = "repayment.do" - okcoinUnrepaymentsInfo = "unrepayments_info.do" - okcoinAccountRecords = "account_records.do" - okcoinFuturesTicker = "future_ticker.do" - okcoinFuturesDepth = "future_depth.do" - okcoinFuturesTrades = "future_trades.do" - okcoinFuturesIndex = "future_index.do" - okcoinExchangeRate = "exchange_rate.do" - okcoinFuturesEstimatedPrice = "future_estimated_price.do" - okcoinFuturesKline = "future_kline.do" - okcoinFuturesHoldAmount = "future_hold_amount.do" - okcoinFuturesUserInfo = "future_userinfo.do" - okcoinFuturesPosition = "future_position.do" - okcoinFuturesTrade = "future_trade.do" - okcoinFuturesTradeHistory = "future_trades_history.do" - okcoinFuturesTradeBatch = "future_batch_trade.do" - okcoinFuturesCancel = "future_cancel.do" - okcoinFuturesOrderInfo = "future_order_info.do" - okcoinFuturesOrdersInfo = "future_orders_info.do" - okcoinFuturesUserInfo4Fix = "future_userinfo_4fix.do" - okcoinFuturesposition4Fix = "future_position_4fix.do" - okcoinFuturesExplosive = "future_explosive.do" - okcoinFuturesDevolve = "future_devolve.do" - - okcoinAuthRate = 0 - okcoinUnauthRate = 0 + okCoinAuthRate = 600 + okCoinUnauthRate = 600 + okCoinAPIPath = "api/" + okCoinAPIURL = "https://www.okcoin.com/" + okCoinAPIPath + okCoinAPIVersion = "/v3/" + okCoinExchangeName = "OKCOIN International" + okCoinWebsocketURL = "wss://real.okcoin.com:10442/ws/v3" ) -// OKCoin is the overarching type across this package +// OKCoin bases all methods off okgroup implementation type OKCoin struct { - exchange.Base - RESTErrors map[string]string - WebsocketErrors map[string]string - FuturesValues []string - WebsocketConn *websocket.Conn + okgroup.OKGroup } -// SetDefaults sets current default values for this package +// SetDefaults method assignes the default values for OKEX func (o *OKCoin) SetDefaults() { o.SetErrorDefaults() - o.SetWebsocketErrorDefaults() + o.SetCheckVarDefaults() + o.Name = okCoinExchangeName o.Enabled = false o.Verbose = false o.RESTPollingDelay = 10 - o.AssetTypes = []string{ticker.Spot} o.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.WithdrawFiatViaWebsiteOnly - o.SupportsAutoPairUpdating = false + exchange.NoFiatWithdrawals + o.RequestCurrencyPairFormat.Delimiter = "_" + o.RequestCurrencyPairFormat.Uppercase = false + o.ConfigCurrencyPairFormat.Delimiter = "_" + o.ConfigCurrencyPairFormat.Uppercase = true + o.SupportsAutoPairUpdating = true o.SupportsRESTTickerBatching = false + o.Requester = request.New(o.Name, + request.NewRateLimit(time.Second, okCoinAuthRate), + request.NewRateLimit(time.Second, okCoinUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + o.APIUrlDefault = okCoinAPIURL + o.APIUrl = okCoinAPIURL + o.AssetTypes = []string{ticker.Spot} o.WebsocketInit() + o.WebsocketURL = okCoinWebsocketURL + o.APIVersion = okCoinAPIVersion o.Websocket.Functionality = exchange.WebsocketTickerSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketKlineSupported -} - -// Setup sets exchange configuration parameters -func (o *OKCoin) Setup(exch config.ExchangeConfig) { - if !exch.Enabled { - o.SetEnabled(false) - } else { - o.Name = "OKCOIN International" - o.Enabled = true - o.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - o.AssetTypes = append(o.AssetTypes, o.FuturesValues...) - o.APIUrlDefault = okcoinAPIURL - o.APIUrl = o.APIUrlDefault - o.WebsocketURL = okcoinWebsocketURL - o.Requester = request.New(o.Name, - request.NewRateLimit(time.Second, okcoinAuthRate), - request.NewRateLimit(time.Second, okcoinUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - o.ConfigCurrencyPairFormat.Delimiter = "_" - o.ConfigCurrencyPairFormat.Uppercase = true - o.RequestCurrencyPairFormat.Uppercase = false - o.RequestCurrencyPairFormat.Delimiter = "_" - o.SupportsAutoPairUpdating = true - o.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - o.SetHTTPClientTimeout(exch.HTTPTimeout) - o.SetHTTPClientUserAgent(exch.HTTPUserAgent) - o.RESTPollingDelay = exch.RESTPollingDelay - o.Verbose = exch.Verbose - o.Websocket.SetEnabled(exch.Websocket) - o.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") - o.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") - o.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") - err := o.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = o.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = o.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = o.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = o.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = o.WebsocketSetup(o.WsConnect, - exch.Name, - exch.Websocket, - okcoinWebsocketURL, - o.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - -// GetSpotInstruments returns a list of tradable spot instruments and their properties -func (o *OKCoin) GetSpotInstruments() ([]SpotInstrument, error) { - var resp []SpotInstrument - - path := fmt.Sprintf("%sspot/v3/%s", okcoinAPIURLBase, okcoinInstruments) - err := o.SendHTTPRequest(path, &resp) - - if err != nil { - return nil, err - } - - return resp, nil -} - -// GetTicker returns the current ticker -func (o *OKCoin) GetTicker(symbol string) (Ticker, error) { - resp := TickerResponse{} - vals := url.Values{} - vals.Set("symbol", symbol) - path := common.EncodeURLValues(o.APIUrl+okcoinTicker, vals) - - return resp.Ticker, o.SendHTTPRequest(path, &resp) -} - -// GetOrderBook returns the current order book by size -func (o *OKCoin) GetOrderBook(symbol string, size int64, merge bool) (Orderbook, error) { - resp := Orderbook{} - vals := url.Values{} - vals.Set("symbol", symbol) - if size != 0 { - vals.Set("size", strconv.FormatInt(size, 10)) - } - if merge { - vals.Set("merge", "1") - } - - path := common.EncodeURLValues(o.APIUrl+okcoinDepth, vals) - return resp, o.SendHTTPRequest(path, &resp) -} - -// GetTrades returns historic trades since a timestamp -func (o *OKCoin) GetTrades(symbol string, since int64) ([]Trades, error) { - result := []Trades{} - vals := url.Values{} - vals.Set("symbol", symbol) - if since != 0 { - vals.Set("since", strconv.FormatInt(since, 10)) - } - - path := common.EncodeURLValues(o.APIUrl+okcoinTrades, vals) - return result, o.SendHTTPRequest(path, &result) -} - -// GetKline returns kline data -func (o *OKCoin) GetKline(symbol, klineType string, size, since int64) ([]interface{}, error) { - resp := []interface{}{} - vals := url.Values{} - vals.Set("symbol", symbol) - vals.Set("type", klineType) - - if size != 0 { - vals.Set("size", strconv.FormatInt(size, 10)) - } - - if since != 0 { - vals.Set("since", strconv.FormatInt(since, 10)) - } - - path := common.EncodeURLValues(o.APIUrl+okcoinKline, vals) - return resp, o.SendHTTPRequest(path, &resp) -} - -// GetFuturesTicker returns a current ticker for the futures market -func (o *OKCoin) GetFuturesTicker(symbol, contractType string) (FuturesTicker, error) { - resp := FuturesTickerResponse{} - vals := url.Values{} - vals.Set("symbol", symbol) - vals.Set("contract_type", contractType) - path := common.EncodeURLValues(o.APIUrl+okcoinFuturesTicker, vals) - - return resp.Ticker, o.SendHTTPRequest(path, &resp) -} - -// GetFuturesDepth returns current depth for the futures market -func (o *OKCoin) GetFuturesDepth(symbol, contractType string, size int64, merge bool) (Orderbook, error) { - result := Orderbook{} - vals := url.Values{} - vals.Set("symbol", symbol) - vals.Set("contract_type", contractType) - - if size != 0 { - vals.Set("size", strconv.FormatInt(size, 10)) - } - if merge { - vals.Set("merge", "1") - } - - path := common.EncodeURLValues(o.APIUrl+okcoinFuturesDepth, vals) - return result, o.SendHTTPRequest(path, &result) -} - -// GetFuturesTrades returns historic trades for the futures market -func (o *OKCoin) GetFuturesTrades(symbol, contractType string) ([]FuturesTrades, error) { - result := []FuturesTrades{} - vals := url.Values{} - vals.Set("symbol", symbol) - vals.Set("contract_type", contractType) - - path := common.EncodeURLValues(o.APIUrl+okcoinFuturesTrades, vals) - return result, o.SendHTTPRequest(path, &result) -} - -// GetFuturesIndex returns an index for the futures market -func (o *OKCoin) GetFuturesIndex(symbol string) (float64, error) { - type Response struct { - Index float64 `json:"future_index"` - } - - result := Response{} - vals := url.Values{} - vals.Set("symbol", symbol) - - path := common.EncodeURLValues(o.APIUrl+okcoinFuturesIndex, vals) - return result.Index, o.SendHTTPRequest(path, &result) -} - -// GetFuturesExchangeRate returns the exchange rate for the futures market -func (o *OKCoin) GetFuturesExchangeRate() (float64, error) { - type Response struct { - Rate float64 `json:"rate"` - } - - result := Response{} - return result.Rate, o.SendHTTPRequest(o.APIUrl+okcoinExchangeRate, &result) -} - -// GetFuturesEstimatedPrice returns a current estimated futures price for a -// currency -func (o *OKCoin) GetFuturesEstimatedPrice(symbol string) (float64, error) { - type Response struct { - Price float64 `json:"forecast_price"` - } - - result := Response{} - vals := url.Values{} - vals.Set("symbol", symbol) - path := common.EncodeURLValues(o.APIUrl+okcoinFuturesEstimatedPrice, vals) - - return result.Price, o.SendHTTPRequest(path, &result) -} - -// GetFuturesKline returns kline data for a specific currency on the futures -// market -func (o *OKCoin) GetFuturesKline(symbol, klineType, contractType string, size, since int64) ([]interface{}, error) { - resp := []interface{}{} - vals := url.Values{} - vals.Set("symbol", symbol) - vals.Set("type", klineType) - vals.Set("contract_type", contractType) - - if size != 0 { - vals.Set("size", strconv.FormatInt(size, 10)) - } - if since != 0 { - vals.Set("since", strconv.FormatInt(since, 10)) - } - - path := common.EncodeURLValues(o.APIUrl+okcoinFuturesKline, vals) - return resp, o.SendHTTPRequest(path, &resp) -} - -// GetFuturesHoldAmount returns the hold amount for a futures trade -func (o *OKCoin) GetFuturesHoldAmount(symbol, contractType string) ([]FuturesHoldAmount, error) { - resp := []FuturesHoldAmount{} - vals := url.Values{} - vals.Set("symbol", symbol) - vals.Set("contract_type", contractType) - - path := common.EncodeURLValues(o.APIUrl+okcoinFuturesHoldAmount, vals) - return resp, o.SendHTTPRequest(path, &resp) -} - -// GetFuturesExplosive returns the explosive for a futures contract -func (o *OKCoin) GetFuturesExplosive(symbol, contractType string, status, currentPage, pageLength int64) ([]FuturesExplosive, error) { - type Response struct { - Data []FuturesExplosive `json:"data"` - } - resp := Response{} - vals := url.Values{} - vals.Set("symbol", symbol) - vals.Set("contract_type", contractType) - vals.Set("status", strconv.FormatInt(status, 10)) - vals.Set("current_page", strconv.FormatInt(currentPage, 10)) - vals.Set("page_length", strconv.FormatInt(pageLength, 10)) - - path := common.EncodeURLValues(o.APIUrl+okcoinFuturesExplosive, vals) - - return resp.Data, o.SendHTTPRequest(path, &resp) -} - -// GetUserInfo returns user information associated with the calling APIkeys -func (o *OKCoin) GetUserInfo() (UserInfo, error) { - result := UserInfo{} - - return result, - o.SendAuthenticatedHTTPRequest(okcoinUserInfo, url.Values{}, &result) -} - -// Trade initiates a new trade -func (o *OKCoin) Trade(amount, price float64, symbol, orderType string) (int64, error) { - type Response struct { - Result bool `json:"result"` - OrderID int64 `json:"order_id"` - } - v := url.Values{} - v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) - v.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) - v.Set("symbol", symbol) - v.Set("type", orderType) - - result := Response{} - - err := o.SendAuthenticatedHTTPRequest(okcoinTrade, v, &result) - - if err != nil { - return 0, err - } - - if !result.Result { - return 0, errors.New("unable to place order") - } - - return result.OrderID, nil -} - -// GetTradeHistory returns client trade history -func (o *OKCoin) GetTradeHistory(symbol string, tradeID int64) ([]Trades, error) { - result := []Trades{} - v := url.Values{} - v.Set("symbol", symbol) - v.Set("since", strconv.FormatInt(tradeID, 10)) - - err := o.SendAuthenticatedHTTPRequest(okcoinTradeHistory, v, &result) - - if err != nil { - return nil, err - } - - return result, nil -} - -// BatchTrade initiates a trade by batch order -func (o *OKCoin) BatchTrade(orderData, symbol, orderType string) (BatchTrade, error) { - v := url.Values{} - v.Set("orders_data", orderData) - v.Set("symbol", symbol) - v.Set("type", orderType) - - result := BatchTrade{} - return result, o.SendAuthenticatedHTTPRequest(okcoinTradeBatch, v, &result) -} - -// CancelExistingOrder cancels a specific order or list of orders by orderID -func (o *OKCoin) CancelExistingOrder(orderID []int64, symbol string) (CancelOrderResponse, error) { - v := url.Values{} - orders := []string{} - result := CancelOrderResponse{} - - orderStr := strconv.FormatInt(orderID[0], 10) - - if len(orderID) > 1 { - for x := range orderID { - orders = append(orders, strconv.FormatInt(orderID[x], 10)) - } - orderStr = common.JoinStrings(orders, ",") - } - - v.Set("order_id", orderStr) - v.Set("symbol", symbol) - - return result, o.SendAuthenticatedHTTPRequest(okcoinOrderCancel, v, &result) -} - -// GetOrderInformation returns order information by orderID -func (o *OKCoin) GetOrderInformation(orderID int64, symbol string) ([]OrderInfo, error) { - type Response struct { - Result bool `json:"result"` - Orders []OrderInfo `json:"orders"` - } - v := url.Values{} - v.Set("symbol", symbol) - v.Set("order_id", strconv.FormatInt(orderID, 10)) - result := Response{} - - err := o.SendAuthenticatedHTTPRequest(okcoinOrderInfo, v, &result) - - if err != nil { - return nil, err - } - - if !result.Result { - return nil, errors.New("unable to retrieve order info") - } - - return result.Orders, nil -} - -// GetOrderInfoBatch returns order info on a batch of orders -func (o *OKCoin) GetOrderInfoBatch(orderID []int64, symbol string) ([]OrderInfo, error) { - type Response struct { - Result bool `json:"result"` - Orders []OrderInfo `json:"orders"` - } - - orders := []string{} - for x := range orderID { - orders = append(orders, strconv.FormatInt(orderID[x], 10)) - } - - v := url.Values{} - v.Set("symbol", symbol) - v.Set("order_id", common.JoinStrings(orders, ",")) - result := Response{} - - err := o.SendAuthenticatedHTTPRequest(okcoinOrderInfo, v, &result) - - if err != nil { - return nil, err - } - - if !result.Result { - return nil, errors.New("unable to retrieve order info") - } - - return result.Orders, nil -} - -// GetOrderHistoryForCurrency returns a history of orders -func (o *OKCoin) GetOrderHistoryForCurrency(pageLength, currentPage, status int64, symbol string) (OrderHistory, error) { - v := url.Values{} - v.Set("symbol", symbol) - v.Set("status", strconv.FormatInt(status, 10)) - v.Set("current_page", strconv.FormatInt(currentPage, 10)) - v.Set("page_length", strconv.FormatInt(pageLength, 10)) - - result := OrderHistory{} - return result, o.SendAuthenticatedHTTPRequest(okcoinOrderHistory, v, &result) -} - -// Withdrawal withdraws a cryptocurrency to a supplied address -func (o *OKCoin) Withdrawal(symbol string, fee float64, tradePWD, address string, amount float64) (int, error) { - v := url.Values{} - v.Set("symbol", symbol) - - if fee != 0 { - v.Set("chargefee", strconv.FormatFloat(fee, 'f', -1, 64)) - } - v.Set("trade_pwd", tradePWD) - v.Set("withdraw_address", address) - v.Set("withdraw_amount", strconv.FormatFloat(amount, 'f', -1, 64)) - v.Set("target", "address") - result := WithdrawalResponse{} - - err := o.SendAuthenticatedHTTPRequest(okcoinWithdraw, v, &result) - if err != nil { - return 0, err - } - - if !result.Result { - return 0, errors.New("unable to process withdrawal request") - } - - return result.WithdrawID, nil -} - -// CancelWithdrawal cancels a withdrawal -func (o *OKCoin) CancelWithdrawal(symbol string, withdrawalID int64) (int, error) { - v := url.Values{} - v.Set("symbol", symbol) - v.Set("withdrawal_id", strconv.FormatInt(withdrawalID, 10)) - result := WithdrawalResponse{} - - err := o.SendAuthenticatedHTTPRequest(okcoinWithdrawCancel, v, &result) - - if err != nil { - return 0, err - } - - if !result.Result { - return 0, errors.New("unable to process withdrawal cancel request") - } - - return result.WithdrawID, nil -} - -// GetWithdrawalInfo returns withdrawal information -func (o *OKCoin) GetWithdrawalInfo(symbol string, withdrawalID int64) ([]WithdrawInfo, error) { - type Response struct { - Result bool - Withdraw []WithdrawInfo `json:"withdraw"` - } - v := url.Values{} - v.Set("symbol", symbol) - v.Set("withdrawal_id", strconv.FormatInt(withdrawalID, 10)) - result := Response{} - - err := o.SendAuthenticatedHTTPRequest(okcoinWithdrawInfo, v, &result) - - if err != nil { - return nil, err - } - - if !result.Result { - return nil, errors.New("unable to process withdrawal cancel request") - } - - return result.Withdraw, nil -} - -// GetOrderFeeInfo returns order fee information -func (o *OKCoin) GetOrderFeeInfo(symbol string, orderID int64) (OrderFeeInfo, error) { - type Response struct { - Data OrderFeeInfo `json:"data"` - Result bool `json:"result"` - } - - v := url.Values{} - v.Set("symbol", symbol) - v.Set("order_id", strconv.FormatInt(orderID, 10)) - result := Response{} - - err := o.SendAuthenticatedHTTPRequest(okcoinOrderFee, v, &result) - - if err != nil { - return result.Data, err - } - - if !result.Result { - return result.Data, errors.New("unable to get order fee info") - } - - return result.Data, nil -} - -// GetLendDepth returns the depth of lends -func (o *OKCoin) GetLendDepth(symbol string) ([]LendDepth, error) { - type Response struct { - LendDepth []LendDepth `json:"lend_depth"` - } - - v := url.Values{} - v.Set("symbol", symbol) - result := Response{} - - err := o.SendAuthenticatedHTTPRequest(okcoinLendDepth, v, &result) - - if err != nil { - return nil, err - } - - return result.LendDepth, nil -} - -// GetBorrowInfo returns borrow information -func (o *OKCoin) GetBorrowInfo(symbol string) (BorrowInfo, error) { - v := url.Values{} - v.Set("symbol", symbol) - result := BorrowInfo{} - - err := o.SendAuthenticatedHTTPRequest(okcoinBorrowsInfo, v, &result) - - if err != nil { - return result, nil - } - - return result, nil -} - -// Borrow initiates a borrow request -func (o *OKCoin) Borrow(symbol, days string, amount, rate float64) (int, error) { - v := url.Values{} - v.Set("symbol", symbol) - v.Set("days", days) - v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) - v.Set("rate", strconv.FormatFloat(rate, 'f', -1, 64)) - result := BorrowResponse{} - - err := o.SendAuthenticatedHTTPRequest(okcoinBorrowMoney, v, &result) - - if err != nil { - return 0, err - } - - if !result.Result { - return 0, errors.New("unable to borrow") - } - - return result.BorrowID, nil -} - -// CancelBorrow cancels a borrow request -func (o *OKCoin) CancelBorrow(symbol string, borrowID int64) (bool, error) { - v := url.Values{} - v.Set("symbol", symbol) - v.Set("borrow_id", strconv.FormatInt(borrowID, 10)) - result := BorrowResponse{} - - err := o.SendAuthenticatedHTTPRequest(okcoinBorrowCancel, v, &result) - - if err != nil { - return false, err - } - - if !result.Result { - return false, errors.New("unable to cancel borrow") - } - - return true, nil -} - -// GetBorrowOrderInfo returns information about a borrow order -func (o *OKCoin) GetBorrowOrderInfo(borrowID int64) (BorrowInfo, error) { - type Response struct { - Result bool `json:"result"` - BorrowOrder BorrowInfo `json:"borrow_order"` - } - - v := url.Values{} - v.Set("borrow_id", strconv.FormatInt(borrowID, 10)) - result := Response{} - err := o.SendAuthenticatedHTTPRequest(okcoinBorrowOrderInfo, v, &result) - - if err != nil { - return result.BorrowOrder, err - } - - if !result.Result { - return result.BorrowOrder, errors.New("unable to get borrow info") - } - - return result.BorrowOrder, nil -} - -// GetRepaymentInfo returns information on a repayment -func (o *OKCoin) GetRepaymentInfo(borrowID int64) (bool, error) { - v := url.Values{} - v.Set("borrow_id", strconv.FormatInt(borrowID, 10)) - result := BorrowResponse{} - - err := o.SendAuthenticatedHTTPRequest(okcoinRepayment, v, &result) - - if err != nil { - return false, err - } - - if !result.Result { - return false, errors.New("unable to get repayment info") - } - - return true, nil -} - -// GetUnrepaymentsInfo returns information on an unrepayment -func (o *OKCoin) GetUnrepaymentsInfo(symbol string, currentPage, pageLength int) ([]BorrowOrder, error) { - type Response struct { - Unrepayments []BorrowOrder `json:"unrepayments"` - Result bool `json:"result"` - } - v := url.Values{} - v.Set("symbol", symbol) - v.Set("current_page", strconv.Itoa(currentPage)) - v.Set("page_length", strconv.Itoa(pageLength)) - result := Response{} - err := o.SendAuthenticatedHTTPRequest(okcoinUnrepaymentsInfo, v, &result) - - if err != nil { - return nil, err - } - - if !result.Result { - return nil, errors.New("unable to get unrepayments info") - } - - return result.Unrepayments, nil -} - -// GetAccountRecords returns account records -func (o *OKCoin) GetAccountRecords(symbol string, recType, currentPage, pageLength int) ([]AccountRecords, error) { - type Response struct { - Records []AccountRecords `json:"records"` - Symbol string `json:"symbol"` - } - v := url.Values{} - v.Set("symbol", symbol) - v.Set("type", strconv.Itoa(recType)) - v.Set("current_page", strconv.Itoa(currentPage)) - v.Set("page_length", strconv.Itoa(pageLength)) - result := Response{} - - err := o.SendAuthenticatedHTTPRequest(okcoinAccountRecords, v, &result) - - if err != nil { - return nil, err - } - - return result.Records, nil -} - -// GetFuturesUserInfo returns information on a users futures -func (o *OKCoin) GetFuturesUserInfo() { - err := o.SendAuthenticatedHTTPRequest(okcoinFuturesUserInfo, url.Values{}, nil) - - if err != nil { - log.Error(err) - } -} - -// GetFuturesPosition returns position on a futures contract -func (o *OKCoin) GetFuturesPosition(symbol, contractType string) { - v := url.Values{} - v.Set("symbol", symbol) - v.Set("contract_type", contractType) - err := o.SendAuthenticatedHTTPRequest(okcoinFuturesPosition, v, nil) - - if err != nil { - log.Error(err) - } -} - -// FuturesTrade initiates a new futures trade -func (o *OKCoin) FuturesTrade(amount, price float64, matchPrice, leverage int64, symbol, contractType, orderType string) { - v := url.Values{} - v.Set("symbol", symbol) - v.Set("contract_type", contractType) - v.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) - v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) - v.Set("type", orderType) - v.Set("match_price", strconv.FormatInt(matchPrice, 10)) - v.Set("lever_rate", strconv.FormatInt(leverage, 10)) - - err := o.SendAuthenticatedHTTPRequest(okcoinFuturesTrade, v, nil) - - if err != nil { - log.Error(err) - } -} - -// FuturesBatchTrade initiates a batch of futures contract trades -func (o *OKCoin) FuturesBatchTrade(orderData, symbol, contractType string, leverage int64, _ string) { - v := url.Values{} // to-do batch trade support for orders_data) - v.Set("symbol", symbol) - v.Set("contract_type", contractType) - v.Set("orders_data", orderData) - v.Set("lever_rate", strconv.FormatInt(leverage, 10)) - - err := o.SendAuthenticatedHTTPRequest(okcoinFuturesTradeBatch, v, nil) - - if err != nil { - log.Error(err) - } -} - -// CancelFuturesOrder cancels a futures contract order -func (o *OKCoin) CancelFuturesOrder(orderID int64, symbol, contractType string) { - v := url.Values{} - v.Set("symbol", symbol) - v.Set("contract_type", contractType) - v.Set("order_id", strconv.FormatInt(orderID, 10)) - - err := o.SendAuthenticatedHTTPRequest(okcoinFuturesCancel, v, nil) - - if err != nil { - log.Error(err) - } -} - -// GetFuturesOrderInfo returns information on a specific futures contract order -func (o *OKCoin) GetFuturesOrderInfo(orderID, status, currentPage, pageLength int64, symbol, contractType string) { - v := url.Values{} - v.Set("symbol", symbol) - v.Set("contract_type", contractType) - v.Set("status", strconv.FormatInt(status, 10)) - v.Set("order_id", strconv.FormatInt(orderID, 10)) - v.Set("current_page", strconv.FormatInt(currentPage, 10)) - v.Set("page_length", strconv.FormatInt(pageLength, 10)) - - err := o.SendAuthenticatedHTTPRequest(okcoinFuturesOrderInfo, v, nil) - - if err != nil { - log.Error(err) - } -} - -// GetFutureOrdersInfo returns information on a range of futures orders -func (o *OKCoin) GetFutureOrdersInfo(orderID int64, contractType, symbol string) { - v := url.Values{} - v.Set("order_id", strconv.FormatInt(orderID, 10)) - v.Set("contract_type", contractType) - v.Set("symbol", symbol) - - err := o.SendAuthenticatedHTTPRequest(okcoinFuturesOrdersInfo, v, nil) - - if err != nil { - log.Error(err) - } -} - -// GetFuturesUserInfo4Fix returns futures user info fix rate -func (o *OKCoin) GetFuturesUserInfo4Fix() { - v := url.Values{} - - err := o.SendAuthenticatedHTTPRequest(okcoinFuturesUserInfo4Fix, v, nil) - - if err != nil { - log.Error(err) - } -} - -// GetFuturesUserPosition4Fix returns futures user info on a fixed position -func (o *OKCoin) GetFuturesUserPosition4Fix(symbol, contractType string) { - v := url.Values{} - v.Set("symbol", symbol) - v.Set("contract_type", contractType) - v.Set("type", strconv.FormatInt(1, 10)) - - err := o.SendAuthenticatedHTTPRequest(okcoinFuturesUserInfo4Fix, v, nil) - - if err != nil { - log.Error(err) - } -} - -// SendHTTPRequest sends an unauthenticated HTTP request -func (o *OKCoin) SendHTTPRequest(path string, result interface{}) error { - return o.SendPayload(http.MethodGet, path, nil, nil, result, false, o.Verbose) -} - -// SendAuthenticatedHTTPRequest sends an authenticated HTTP request -func (o *OKCoin) SendAuthenticatedHTTPRequest(method string, v url.Values, result interface{}) (err error) { - if !o.AuthenticatedAPISupport { - return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, o.Name) - } - - v.Set("api_key", o.APIKey) - hasher := common.GetMD5([]byte(v.Encode() + "&secret_key=" + o.APISecret)) - v.Set("sign", strings.ToUpper(common.HexEncodeToString(hasher))) - - encoded := v.Encode() - path := o.APIUrl + method - - if o.Verbose { - log.Debugf("Sending POST request to %s with params %s\n", path, encoded) - } - - headers := make(map[string]string) - headers["Content-Type"] = "application/x-www-form-urlencoded" - - return o.SendPayload(http.MethodPost, path, headers, strings.NewReader(encoded), result, true, o.Verbose) -} - -// SetErrorDefaults sets default error map -func (o *OKCoin) SetErrorDefaults() { - o.RESTErrors = map[string]string{ - "10000": "Required field, can not be null", - "10001": "Request frequency too high", - "10002": "System error", - "10003": "Not in reqest list, please try again later", - "10004": "IP not allowed to access the resource", - "10005": "'secretKey' does not exist", - "10006": "'partner' does not exist", - "10007": "Signature does not match", - "10008": "Illegal parameter", - "10009": "Order does not exist", - "10010": "Insufficient funds", - "10011": "Amount too low", - "10012": "Only btc_usd/btc_cny ltc_usd,ltc_cny supported", - "10013": "Only support https request", - "10014": "Order price must be between 0 and 1,000,000", - "10015": "Order price differs from current market price too much", - "10016": "Insufficient coins balance", - "10017": "API authorization error", - "10018": "Borrow amount less than lower limit [usd/cny:100,btc:0.1,ltc:1]", - "10019": "Loan agreement not checked", - "10020": `Rate cannot exceed 1%`, - "10021": `Rate cannot less than 0.01%`, - "10023": "Fail to get latest ticker", - "10024": "Balance not sufficient", - "10025": "Quota is full, cannot borrow temporarily", - "10026": "Loan (including reserved loan) and margin cannot be withdrawn", - "10027": "Cannot withdraw within 24 hrs of authentication information modification", - "10028": "Withdrawal amount exceeds daily limit", - "10029": "Account has unpaid loan, please cancel/pay off the loan before withdraw", - "10031": "Deposits can only be withdrawn after 6 confirmations", - "10032": "Please enabled phone/google authenticator", - "10033": "Fee higher than maximum network transaction fee", - "10034": "Fee lower than minimum network transaction fee", - "10035": "Insufficient BTC/LTC", - "10036": "Withdrawal amount too low", - "10037": "Trade password not set", - "10040": "Withdrawal cancellation fails", - "10041": "Withdrawal address not approved", - "10042": "Admin password error", - "10043": "Account equity error, withdrawal failure", - "10044": "fail to cancel borrowing order", - "10047": "This function is disabled for sub-account", - "10100": "User account frozen", - "10216": "Non-available API", - "20001": "User does not exist", - "20002": "Account frozen", - "20003": "Account frozen due to liquidation", - "20004": "Futures account frozen", - "20005": "User futures account does not exist", - "20006": "Required field missing", - "20007": "Illegal parameter", - "20008": "Futures account balance is too low", - "20009": "Future contract status error", - "20010": "Risk rate ratio does not exist", - "20011": `Risk rate higher than 90% before opening position`, - "20012": `Risk rate higher than 90% after opening position`, - "20013": "Temporally no counter party price", - "20014": "System error", - "20015": "Order does not exist", - "20016": "Close amount bigger than your open positions", - "20017": "Not authorized/illegal operation", - "20018": `Order price differ more than 5% from the price in the last minute`, - "20019": "IP restricted from accessing the resource", - "20020": "secretKey does not exist", - "20021": "Index information does not exist", - "20022": "Wrong API interface (Cross margin mode shall call cross margin API, fixed margin mode shall call fixed margin API)", - "20023": "Account in fixed-margin mode", - "20024": "Signature does not match", - "20025": "Leverage rate error", - "20026": "API Permission Error", - "20027": "No transaction record", - "20028": "No such contract", - } -} - -// GetFee returns an estimate of fee based on type of transaction -func (o *OKCoin) GetFee(feeBuilder exchange.FeeBuilder) (float64, error) { - var fee float64 - switch feeBuilder.FeeType { - case exchange.CryptocurrencyTradeFee: - fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount, feeBuilder.IsMaker) - case exchange.InternationalBankWithdrawalFee: - fee = calculateInternationalBankWithdrawalFee(feeBuilder.CurrencyItem, feeBuilder.PurchasePrice, feeBuilder.Amount) - case exchange.CryptocurrencyWithdrawalFee: - fee = getWithdrawalFee(feeBuilder.FirstCurrency) - } - if fee < 0 { - fee = 0 - } - - return fee, nil -} - -func calculateTradingFee(purchasePrice, amount float64, isMaker bool) (fee float64) { - // TODO volume based fees - if isMaker { - fee = 0.0005 - } else { - fee = 0.0015 - } - return fee * amount * purchasePrice -} - -func calculateInternationalBankWithdrawalFee(currency string, purchasePrice, amount float64) (fee float64) { - if currency == symbol.USD { - if purchasePrice*amount*0.001 < 15 { - fee = 15 - } else { - fee = purchasePrice * amount * 0.001 - } - } - return fee -} - -func getWithdrawalFee(currency string) float64 { - return WithdrawalFees[currency] + exchange.WebsocketTradeDataSupported | + exchange.WebsocketKlineSupported | + exchange.WebsocketOrderbookSupported } diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index c7bff661..2c1c07d2 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -1,43 +1,1045 @@ package okcoin import ( + "strings" "testing" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/currency/symbol" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/okgroup" ) -var o OKCoin - -// Please supply your own APIKEYS here for due diligence testing - +// Please supply you own test keys here for due diligence testing. const ( apiKey = "" apiSecret = "" + passphrase = "" + OKGroupExchange = "OKCOIN International" canManipulateRealOrders = false ) +var o = OKCoin{} +var testSetupRan bool +var spotCurrency = pair.NewCurrencyPairWithDelimiter(symbol.BTC, symbol.USD, "-").Pair().Lower().String() + +// TestSetDefaults Sets standard default settings for running a test func TestSetDefaults(t *testing.T) { - o.SetDefaults() + if o.Name != OKGroupExchange { + o.SetDefaults() + } + if o.GetName() != OKGroupExchange { + t.Errorf("Test Failed - %v - SetDefaults() error", OKGroupExchange) + } + TestSetup(t) + t.Parallel() } +// TestSetWsDefaults Sets websocket defaults +func TestSetWsDefaults(t *testing.T) { + if o.Name != OKGroupExchange { + o.SetDefaults() + } + if o.GetName() != OKGroupExchange { + t.Errorf("Test Failed - %v - SetDefaults() error", OKGroupExchange) + } + TestSetup(t) +} + +// TestSetRealOrderDefaults Sets test defaults when test can impact real money/orders +func TestSetRealOrderDefaults(t *testing.T) { + TestSetDefaults(t) + if areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("Ensure canManipulateRealOrders is true and your API keys are set") + } +} + +// TestSetup Sets defaults for test environment func TestSetup(t *testing.T) { + if testSetupRan { + return + } + if o.APIKey == apiKey && o.APISecret == apiSecret && + o.ClientID == passphrase && apiKey != "" && apiSecret != "" && passphrase != "" { + return + } + o.ExchangeName = OKGroupExchange cfg := config.GetConfig() cfg.LoadConfig("../../testdata/configtest.json") - okcoinConfig, err := cfg.GetExchangeConfig("OKCOIN International") + + okcoinConfig, err := cfg.GetExchangeConfig(OKGroupExchange) if err != nil { - t.Error("Test Failed - OKCoin Setup() init error") + t.Fatalf("Test Failed - %v Setup() init error", OKGroupExchange) } + okcoinConfig.AuthenticatedAPISupport = true okcoinConfig.APIKey = apiKey okcoinConfig.APISecret = apiSecret - + okcoinConfig.ClientID = passphrase + okcoinConfig.WebsocketURL = o.WebsocketURL o.Setup(okcoinConfig) + testSetupRan = true } +func areTestAPIKeysSet() bool { + if o.APIKey != "" && o.APIKey != "Key" && + o.APISecret != "" && o.APISecret != "Secret" { + return true + } + return false +} + +func testStandardErrorHandling(t *testing.T, err error) { + if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } + if areTestAPIKeysSet() && err != nil { + t.Errorf("Encountered error: %v", err) + } +} + +// setupWSConnection Connect to WS, but pass back error so test can handle it if needed +func setupWSConnection() error { + o.Enabled = true + err := o.WebsocketSetup(o.WsConnect, + o.Name, + true, + o.WebsocketURL, + o.WebsocketURL) + o.Websocket.DataHandler = make(chan interface{}, 500) + if err != nil { + return err + } + o.Websocket.SetWsStatusAndConnection(true) + return nil +} + +// disconnectFromWS disconnect to WS, but pass back error so test can handle it if needed +func disconnectFromWS() error { + return o.Websocket.Shutdown() +} + +// TestGetAccountCurrencies API endpoint test +func TestGetAccountCurrencies(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetAccountCurrencies() + testStandardErrorHandling(t, err) +} + +// TestGetAccountWalletInformation API endpoint test +func TestGetAccountWalletInformation(t *testing.T) { + TestSetDefaults(t) + resp, err := o.GetAccountWalletInformation("") + if areTestAPIKeysSet() { + if err != nil { + t.Error(err) + } + if len(resp) == 0 { + t.Error("No wallets returned") + } + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +// TestGetAccountWalletInformationForCurrency API endpoint test +func TestGetAccountWalletInformationForCurrency(t *testing.T) { + TestSetDefaults(t) + resp, err := o.GetAccountWalletInformation(symbol.BTC) + if areTestAPIKeysSet() { + if err != nil { + t.Error(err) + } + if len(resp) != 1 { + t.Errorf("Error receiving wallet information for currency: %v", symbol.BTC) + } + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +// TestTransferAccountFunds API endpoint test +func TestTransferAccountFunds(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.TransferAccountFundsRequest{ + Amount: 10, + Currency: symbol.BTC, + From: 6, + To: 1, + } + _, err := o.TransferAccountFunds(request) + testStandardErrorHandling(t, err) +} + +// TestBaseWithdraw API endpoint test +func TestAccountWithdrawRequest(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.AccountWithdrawRequest{ + Amount: 10, + Currency: symbol.BTC, + TradePwd: "1234", + Destination: 4, + ToAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", + Fee: 1, + } + _, err := o.AccountWithdraw(request) + testStandardErrorHandling(t, err) +} + +// TestGetAccountWithdrawalFee API endpoint test +func TestGetAccountWithdrawalFee(t *testing.T) { + TestSetDefaults(t) + resp, err := o.GetAccountWithdrawalFee("") + if areTestAPIKeysSet() { + if err != nil { + t.Error(err) + } + if len(resp) == 0 { + t.Error("Expected fees") + } + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +// TestGetWithdrawalFeeForCurrency API endpoint test +func TestGetAccountWithdrawalFeeForCurrency(t *testing.T) { + TestSetDefaults(t) + resp, err := o.GetAccountWithdrawalFee(symbol.BTC) + if areTestAPIKeysSet() { + if err != nil { + t.Error(err) + } + if len(resp) != 1 { + t.Error("Expected fee for one currency") + } + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +// TestGetAccountWithdrawalHistory API endpoint test +func TestGetAccountWithdrawalHistory(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetAccountWithdrawalHistory("") + testStandardErrorHandling(t, err) +} + +// TestGetAccountWithdrawalHistoryForCurrency API endpoint test +func TestGetAccountWithdrawalHistoryForCurrency(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetAccountWithdrawalHistory(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestGetAccountBillDetails API endpoint test +func TestGetAccountBillDetails(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetAccountBillDetails(okgroup.GetAccountBillDetailsRequest{}) + testStandardErrorHandling(t, err) +} + +// TestGetAccountDepositAddressForCurrency API endpoint test +func TestGetAccountDepositAddressForCurrency(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetAccountDepositAddressForCurrency(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestGetAccountDepositHistory API endpoint test +func TestGetAccountDepositHistory(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetAccountDepositHistory("") + testStandardErrorHandling(t, err) +} + +// TestGetAccountDepositHistoryForCurrency API endpoint test +func TestGetAccountDepositHistoryForCurrency(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetAccountDepositHistory(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestGetSpotTradingAccounts API endpoint test +func TestGetSpotTradingAccounts(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetSpotTradingAccounts() + testStandardErrorHandling(t, err) +} + +// TestGetSpotTradingAccountsForCurrency API endpoint test +func TestGetSpotTradingAccountsForCurrency(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetSpotTradingAccountForCurrency(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestGetSpotBillDetailsForCurrency API endpoint test +func TestGetSpotBillDetailsForCurrency(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotBillDetailsForCurrencyRequest{ + Currency: symbol.BTC, + Limit: 100, + } + _, err := o.GetSpotBillDetailsForCurrency(request) + testStandardErrorHandling(t, err) +} + +// TestGetSpotBillDetailsForCurrencyBadLimit API logic test +func TestGetSpotBillDetailsForCurrencyBadLimit(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotBillDetailsForCurrencyRequest{ + Currency: symbol.BTC, + Limit: -1, + } + _, err := o.GetSpotBillDetailsForCurrency(request) + if areTestAPIKeysSet() && err == nil { + t.Errorf("Expecting an error when invalid request sent") + } +} + +// TestPlaceSpotOrderLimit API endpoint test +func TestPlaceSpotOrderLimit(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "limit", + Side: "buy", + MarginTrading: "1", + Price: "100", + Size: "100", + } + + _, err := o.PlaceSpotOrder(request) + testStandardErrorHandling(t, err) +} + +// TestPlaceSpotOrderMarket API endpoint test +func TestPlaceSpotOrderMarket(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + _, err := o.PlaceSpotOrder(request) + testStandardErrorHandling(t, err) +} + +// TestPlaceMultipleSpotOrders API endpoint test +func TestPlaceMultipleSpotOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + } + + _, errs := o.PlaceMultipleSpotOrders(request) + if len(errs) > 0 { + testStandardErrorHandling(t, errs[0]) + } +} + +// TestPlaceMultipleSpotOrdersOverCurrencyLimits API logic test +func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { + TestSetDefaults(t) + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + order, + order, + order, + order, + } + + _, errs := o.PlaceMultipleSpotOrders(request) + if errs[0].Error() != "maximum 4 orders for each pair" { + t.Error("Expecting an error when more than 4 orders for a pair supplied", errs[0]) + } +} + +// TestPlaceMultipleSpotOrdersOverPairLimits API logic test +func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { + TestSetDefaults(t) + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + } + + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.LTC, symbol.USD, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.DOGE, symbol.USD, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.XMR, symbol.USD, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.BCH, symbol.USD, "-").Pair().Lower().String() + request = append(request, order) + + _, errs := o.PlaceMultipleSpotOrders(request) + if errs[0].Error() != "up to 4 trading pairs" { + t.Error("Expecting an error when more than 4 trading pairs supplied", errs[0]) + } +} + +// TestCancelSpotOrder API endpoint test +func TestCancelSpotOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.CancelSpotOrderRequest{ + InstrumentID: spotCurrency, + OrderID: 1234, + } + + _, err := o.CancelSpotOrder(request) + testStandardErrorHandling(t, err) +} + +// TestCancelMultipleSpotOrders API endpoint test +func TestCancelMultipleSpotOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.CancelMultipleSpotOrdersRequest{ + InstrumentID: spotCurrency, + OrderIDs: []int64{1, 2, 3, 4}, + } + + cancellations, err := o.CancelMultipleSpotOrders(request) + testStandardErrorHandling(t, err) + for _, cancellationsPerCurrency := range cancellations { + for _, cancellation := range cancellationsPerCurrency { + if !cancellation.Result { + t.Error(cancellation.Error) + } + } + } +} + +// TestCancelMultipleSpotOrdersOverCurrencyLimits API logic test +func TestCancelMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.CancelMultipleSpotOrdersRequest{ + InstrumentID: spotCurrency, + OrderIDs: []int64{1, 2, 3, 4, 5}, + } + + _, err := o.CancelMultipleSpotOrders(request) + if err.Error() != "maximum 4 order cancellations for each pair" { + t.Error("Expecting an error when more than 4 orders for a pair supplied", err) + } +} + +// TestGetSpotOrders API endpoint test +func TestGetSpotOrders(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotOrdersRequest{ + InstrumentID: spotCurrency, + Status: "all", + } + _, err := o.GetSpotOrders(request) + testStandardErrorHandling(t, err) +} + +// TestGetSpotOpenOrders API endpoint test +func TestGetSpotOpenOrders(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotOpenOrdersRequest{} + _, err := o.GetSpotOpenOrders(request) + testStandardErrorHandling(t, err) +} + +// TestGetSpotOrder API endpoint test +func TestGetSpotOrder(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotOrderRequest{ + OrderID: "-1234", + InstrumentID: pair.NewCurrencyPairWithDelimiter(symbol.BTC, symbol.USD, "-").Pair().Upper().String(), + } + _, err := o.GetSpotOrder(request) + testStandardErrorHandling(t, err) +} + +// TestGetSpotTransactionDetails API endpoint test +func TestGetSpotTransactionDetails(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotTransactionDetailsRequest{ + OrderID: 1234, + InstrumentID: spotCurrency, + } + _, err := o.GetSpotTransactionDetails(request) + testStandardErrorHandling(t, err) +} + +// TestGetSpotTokenPairDetails API endpoint test +func TestGetSpotTokenPairDetails(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetSpotTokenPairDetails() + if err != nil { + t.Error(err) + } +} + +// TestGetSpotOrderBook API endpoint test +func TestGetSpotOrderBook(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotOrderBookRequest{ + InstrumentID: spotCurrency, + } + _, err := o.GetSpotOrderBook(request) + if err != nil { + t.Error(err) + } +} + +// TestGetSpotAllTokenPairsInformation API endpoint test +func TestGetSpotAllTokenPairsInformation(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetSpotAllTokenPairsInformation() + if err != nil { + t.Error(err) + } +} + +// TestGetSpotAllTokenPairsInformationForCurrency API endpoint test +func TestGetSpotAllTokenPairsInformationForCurrency(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetSpotAllTokenPairsInformationForCurrency(spotCurrency) + if err != nil { + t.Error(err) + } +} + +// TestGetSpotFilledOrdersInformation API endpoint test +func TestGetSpotFilledOrdersInformation(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotFilledOrdersInformationRequest{ + InstrumentID: spotCurrency, + } + _, err := o.GetSpotFilledOrdersInformation(request) + if err != nil { + t.Error(err) + } +} + +// TestGetSpotMarketData API endpoint test +func TestGetSpotMarketData(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotMarketDataRequest{ + InstrumentID: spotCurrency, + Granularity: 604800, + } + _, err := o.GetSpotMarketData(request) + if err != nil { + t.Error(err) + } +} + +// TestGetMarginTradingAccounts API endpoint test +func TestGetMarginTradingAccounts(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetMarginTradingAccounts() + testStandardErrorHandling(t, err) +} + +// TestGetMarginTradingAccountsForCurrency API endpoint test +func TestGetMarginTradingAccountsForCurrency(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetMarginTradingAccountsForCurrency(spotCurrency) + testStandardErrorHandling(t, err) +} + +// TestGetMarginBillDetails API endpoint test +func TestGetMarginBillDetails(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetMarginBillDetailsRequest{ + InstrumentID: spotCurrency, + Limit: 100, + } + _, err := o.GetMarginBillDetails(request) + testStandardErrorHandling(t, err) +} + +// TestGetMarginAccountSettings API endpoint test +func TestGetMarginAccountSettings(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetMarginAccountSettings("") + testStandardErrorHandling(t, err) +} + +// TestGetMarginAccountSettingsForCurrency API endpoint test +func TestGetMarginAccountSettingsForCurrency(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetMarginAccountSettings(spotCurrency) + testStandardErrorHandling(t, err) +} + +// TestOpenMarginLoan API endpoint test +func TestOpenMarginLoan(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.OpenMarginLoanRequest{ + Amount: 100, + InstrumentID: spotCurrency, + QuoteCurrency: symbol.USD, + } + + _, err := o.OpenMarginLoan(request) + testStandardErrorHandling(t, err) +} + +// TestRepayMarginLoan API endpoint test +func TestRepayMarginLoan(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.RepayMarginLoanRequest{ + Amount: 100, + InstrumentID: spotCurrency, + QuoteCurrency: symbol.USD, + BorrowID: 1, + } + + _, err := o.RepayMarginLoan(request) + testStandardErrorHandling(t, err) +} + +// TestPlaceMarginOrderLimit API endpoint test +func TestPlaceMarginOrderLimit(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "limit", + Side: "buy", + MarginTrading: "2", + Price: "100", + Size: "100", + } + + _, err := o.PlaceMarginOrder(request) + testStandardErrorHandling(t, err) +} + +// TestPlaceMarginOrderMarket API endpoint test +func TestPlaceMarginOrderMarket(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "2", + Size: "100", + Notional: "100", + } + + _, err := o.PlaceMarginOrder(request) + testStandardErrorHandling(t, err) +} + +// TestPlaceMultipleMarginOrders API endpoint test +func TestPlaceMultipleMarginOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + } + + _, errs := o.PlaceMultipleMarginOrders(request) + if len(errs) > 0 { + testStandardErrorHandling(t, errs[0]) + } +} + +// TestPlaceMultipleMarginOrdersOverCurrencyLimits API logic test +func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { + TestSetDefaults(t) + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + order, + order, + order, + order, + } + + _, errs := o.PlaceMultipleMarginOrders(request) + if errs[0].Error() != "maximum 4 orders for each pair" { + t.Error("Expecting an error when more than 4 orders for a pair supplied", errs[0]) + } +} + +// TestPlaceMultipleMarginOrdersOverPairLimits API logic test +func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { + TestSetDefaults(t) + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + } + + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.LTC, symbol.USD, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.DOGE, symbol.USD, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.XMR, symbol.USD, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.BCH, symbol.USD, "-").Pair().Lower().String() + request = append(request, order) + + _, errs := o.PlaceMultipleMarginOrders(request) + if errs[0].Error() != "up to 4 trading pairs" { + t.Error("Expecting an error when more than 4 trading pairs supplied", errs[0]) + } +} + +// TestCancelMarginOrder API endpoint test +func TestCancelMarginOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.CancelSpotOrderRequest{ + InstrumentID: spotCurrency, + OrderID: 1234, + } + + _, err := o.CancelMarginOrder(request) + testStandardErrorHandling(t, err) +} + +// TestCancelMultipleMarginOrders API endpoint test +func TestCancelMultipleMarginOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.CancelMultipleSpotOrdersRequest{ + InstrumentID: spotCurrency, + OrderIDs: []int64{1, 2, 3, 4}, + } + + _, errs := o.CancelMultipleMarginOrders(request) + if len(errs) > 0 { + testStandardErrorHandling(t, errs[0]) + } +} + +// TestCancelMultipleMarginOrdersOverCurrencyLimits API logic test +func TestCancelMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.CancelMultipleSpotOrdersRequest{ + InstrumentID: spotCurrency, + OrderIDs: []int64{1, 2, 3, 4, 5}, + } + + _, errs := o.CancelMultipleMarginOrders(request) + if errs[0].Error() != "maximum 4 order cancellations for each pair" { + t.Error("Expecting an error when more than 4 orders for a pair supplied", errs[0]) + } +} + +// TestGetMarginOrders API endpoint test +func TestGetMarginOrders(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotOrdersRequest{ + InstrumentID: spotCurrency, + Status: "all", + } + _, err := o.GetMarginOrders(request) + testStandardErrorHandling(t, err) +} + +// TestGetMarginOpenOrders API endpoint test +func TestGetMarginOpenOrders(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotOpenOrdersRequest{} + _, err := o.GetMarginOpenOrders(request) + testStandardErrorHandling(t, err) +} + +// TestGetMarginOrder API endpoint test +func TestGetMarginOrder(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotOrderRequest{ + OrderID: "1234", + InstrumentID: pair.NewCurrencyPairWithDelimiter(symbol.BTC, symbol.USD, "-").Pair().Upper().String(), + } + _, err := o.GetMarginOrder(request) + testStandardErrorHandling(t, err) +} + +// TestGetMarginTransactionDetails API endpoint test +func TestGetMarginTransactionDetails(t *testing.T) { + TestSetDefaults(t) + request := okgroup.GetSpotTransactionDetailsRequest{ + OrderID: 1234, + InstrumentID: spotCurrency, + } + _, err := o.GetMarginTransactionDetails(request) + testStandardErrorHandling(t, err) +} + +// Websocket tests ---------------------------------------------------------------------------------------------- + +// TestWsLogin API endpoint test +func TestWsLogin(t *testing.T) { + TestSetRealOrderDefaults(t) + if o.WebsocketConn == nil { + o.Websocket.Shutdown() + err := setupWSConnection() + if err != nil { + t.Error(err) + } + } + if !o.Websocket.IsConnected() { + t.Skip() + } + err := o.WsLogin() + if err != nil { + t.Error(err) + } + var errorReceived bool + for i := 0; i < 5; i++ { + response := <-o.Websocket.DataHandler + if err, ok := response.(error); ok && err != nil { + errorReceived = true + } + } + if errorReceived { + t.Error("Expecting no errors") + } +} + +// TestSubscribeToChannel API endpoint test +func TestSubscribeToChannel(t *testing.T) { + defer disconnectFromWS() + TestSetWsDefaults(t) + if o.WebsocketConn == nil { + o.Websocket.Shutdown() + err := setupWSConnection() + if err != nil { + t.Error(err) + } + } + if !o.Websocket.IsConnected() { + t.Skip() + } + channelName := "spot/depth:LTC-BTC" + err := o.WsSubscribeToChannel(channelName) + if err != nil { + t.Error(err) + return + } + var errorReceived bool + for i := 0; i < 5; i++ { + response := <-o.Websocket.DataHandler + if err, ok := response.(error); ok && err != nil { + t.Log(response) + if strings.Contains(response.(error).Error(), channelName) { + errorReceived = true + } + } + } + if errorReceived { + t.Error("Expecting subscription to channel") + } +} + +// TestSubscribeToNonExistantChannel Logic test +// Attempts to subscribe to a channel that doesn't exist +// Then captures the error response +func TestSubscribeToNonExistantChannel(t *testing.T) { + defer disconnectFromWS() + TestSetWsDefaults(t) + if o.WebsocketConn == nil { + o.Websocket.Shutdown() + err := setupWSConnection() + if err != nil { + t.Error(err) + } + } + if !o.Websocket.IsConnected() { + t.Skip("Could not connect to websocket. Skipping") + } + channelName := "badChannel" + err := o.WsSubscribeToChannel(channelName) + if err != nil { + t.Error(err) + return + } + var errorReceived bool + for i := 0; i < 5; i++ { + response := <-o.Websocket.DataHandler + if err, ok := response.(error); ok && err != nil { + t.Log(response) + if strings.Contains(response.(error).Error(), channelName) { + errorReceived = true + } + } + } + if !errorReceived { + t.Error("Expecting OKEX error - 30040 message: Channel badChannel doesn't exist") + } +} + +// TestGetAssetTypeFromTableName logic test +func TestGetAssetTypeFromTableName(t *testing.T) { + str := "spot/candle300s:BTC-USDT" + spot := o.GetAssetTypeFromTableName(str) + if spot != "SPOT" { + t.Errorf("Error, expected 'SPOT', received: '%v'", spot) + } +} + +// TestGetWsChannelWithoutOrderType logic test +func TestGetWsChannelWithoutOrderType(t *testing.T) { + TestSetDefaults(t) + str := "spot/depth5:BTC-USDT" + expected := "depth5" + resp := o.GetWsChannelWithoutOrderType(str) + if resp != expected { + t.Errorf("Logic change error %v should be %v", resp, expected) + } + str = "spot/depth" + resp = o.GetWsChannelWithoutOrderType(str) + expected = "depth" + if resp != expected { + t.Errorf("Logic change error %v should be %v", resp, expected) + } + str = "testWithBadData" + resp = o.GetWsChannelWithoutOrderType(str) + if resp != str { + t.Errorf("Logic change error %v should be %v", resp, str) + } +} + +// TestOrderBookUpdateChecksumCalculator logic test +func TestOrderBookUpdateChecksumCalculator(t *testing.T) { + TestSetDefaults(t) + if o.WebsocketConn == nil { + o.Websocket.Shutdown() + err := setupWSConnection() + if err != nil { + t.Error(err) + } + } + disconnectFromWS() + original := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"BTC-USDT","asks":[["3864.6786","0.145",1],["3864.7682","0.005",1],["3864.9851","0.57",1],["3864.9852","0.30137754",1],["3864.9986","2.81818419",1],["3864.9995","0.002",1],["3865","0.0597",1],["3865.0309","0.4",1],["3865.1995","0.004",1],["3865.3995","0.004",1],["3865.5995","0.004",1],["3865.7995","0.004",1],["3865.9995","0.004",1],["3866.0961","0.25865886",1],["3866.1995","0.004",1],["3866.3995","0.004",1],["3866.4004","0.3243",2],["3866.5995","0.004",1],["3866.7633","0.44247086",1],["3866.7995","0.004",1],["3866.9197","0.511",1],["3867.256","0.51716256",1],["3867.3951","0.02588112",1],["3867.4014","0.025",1],["3867.4566","0.02499999",1],["3867.4675","4.01155057",5],["3867.5515","1.1",1],["3867.6113","0.009",1],["3867.7349","0.026",1],["3867.7781","0.03738652",1],["3867.9163","0.0521",1],["3868.0381","0.34354941",1],["3868.0436","0.051",1],["3868.0657","0.90552172",3],["3868.1819","0.03863346",1],["3868.2013","0.194",1],["3868.346","0.051",1],["3868.3863","0.01155",1],["3868.7716","0.009",1],["3868.947","0.025",1],["3868.98","0.001",1],["3869.0764","1.03487931",1],["3869.2773","0.07724578",1],["3869.4039","0.025",1],["3869.4068","1.03",1],["3869.7068","2.06976398",1],["3870","0.5",1],["3870.0465","0.01",1],["3870.7042","0.02099651",1],["3870.9451","2.07047375",1],["3871.5254","1.2",1],["3871.5596","0.001",1],["3871.6605","0.01035032",1],["3871.7179","2.07047375",1],["3871.8816","0.51751625",1],["3872.1","0.75",1],["3872.2464","0.0646",1],["3872.3747","0.283",1],["3872.4039","0.2",1],["3872.7655","0.23179307",1],["3872.8005","2.06976398",1],["3873.1509","2",1],["3873.3215","0.26",1],["3874.1392","0.001",1],["3874.1487","3.88224364",4],["3874.1685","1.8",1],["3874.5571","0.08974762",1],["3874.734","2.06976398",1],["3874.99","0.3",1],["3875","1.001",2],["3875.0041","1.03505051",1],["3875.45","0.3",1],["3875.4766","0.15",1],["3875.7057","0.51751625",1],["3876","0.001",1],["3876.68","0.3",1],["3876.7188","0.001",1],["3877","0.75",1],["3877.31","0.035",1],["3877.38","0.3",1],["3877.7","0.3",1],["3877.88","0.3",1],["3878.0364","0.34770122",1],["3878.4525","0.48579748",1],["3878.4955","0.02812511",1],["3878.8855","0.00258579",1],["3878.9605","0.895",1],["3879","0.001",1],["3879.2984","0.002",2],["3879.432","0.001",1],["3879.6313","6",1],["3879.9999","0.002",2],["3880","1.25132834",5],["3880.2526","0.04075162",1],["3880.7145","0.0647",1],["3881.2469","1.883",1],["3881.878","0.002",2],["3884.4576","0.002",2],["3885","0.002",2],["3885.2233","0.28304103",1],["3885.7416","18",1],["3886","0.001",1],["3886.1554","5.4",1],["3887","0.001",1],["3887.0372","0.002",2],["3887.2559","0.05214011",1],["3887.9238","0.0019",1],["3888","0.15810538",4],["3889","0.001",1],["3889.5175","0.50510653",1],["3889.6168","0.002",2],["3889.9999","0.001",1],["3890","2.34968109",4],["3890.5222","0.00257806",1],["3891.2659","5",1],["3891.9999","0.00893897",1],["3892.1964","0.002",2],["3892.4358","0.0176",1],["3893.1388","1.4279",1],["3894","0.0026321",1],["3894.776","0.001",1],["3895","1.501",2],["3895.379","0.25881288",1],["3897","0.05",1],["3897.3556","0.001",1],["3897.8432","0.73708079",1],["3898","3.31353018",7],["3898.4462","4.757",1],["3898.6","0.47159638",1],["3898.8769","0.0129",1],["3899","6",2],["3899.6516","0.025",1],["3899.9352","0.001",1],["3899.9999","0.013",2],["3900","22.37447743",24],["3900.9999","0.07763916",1],["3901","0.10192487",1],["3902.1937","0.00257034",1],["3902.3991","1.5532141",1],["3902.5148","0.001",1],["3904","1.49331984",1],["3904.9999","0.95905447",1],["3905","0.501",2],["3905.0944","0.001",1],["3905.61","0.099",1],["3905.6801","0.54343686",1],["3906.2901","0.0258",1],["3907.674","0.001",1],["3907.85","1.35778084",1],["3908","0.03846153",1],["3908.23","1.95189531",1],["3908.906","0.03148978",1],["3909","0.001",1],["3909.9999","0.01398721",2],["3910","0.016",2],["3910.2536","0.001",1],["3912.5406","0.88270517",1],["3912.8332","0.001",1],["3913","1.2640608",1],["3913.87","1.69114184",1],["3913.9003","0.00256266",1],["3914","1.21766411",1],["3915","0.001",1],["3915.4128","0.001",1],["3915.7425","6.848",1],["3916","0.0050949",1],["3917.36","1.28658296",1],["3917.9924","0.001",1],["3919","0.001",1],["3919.9999","0.001",1],["3920","1.21171832",3],["3920.0002","0.20217038",1],["3920.572","0.001",1],["3921","0.128",1],["3923.0756","0.00148064",1],["3923.1516","0.001",1],["3923.86","1.38831714",1],["3925","0.01867801",2],["3925.642","0.00255499",1],["3925.7312","0.001",1],["3926","0.04290757",1],["3927","0.023",1],["3927.3175","0.01212865",1],["3927.65","1.51375612",1],["3928","0.5",1],["3928.3108","0.001",1],["3929","0.001",1],["3929.9999","0.01519338",2],["3930","0.0174985",3],["3930.21","1.49335799",1],["3930.8904","0.001",1],["3932.2999","0.01953",1],["3932.8962","7.96",1],["3933.0387","11.808",1],["3933.47","0.001",1],["3934","1.40839932",1],["3935","0.001",1],["3936.8","0.62879518",1],["3937.23","1.56977841",1],["3937.4189","0.00254735",1]],"bids":[["3864.5217","0.00540709",1],["3864.5216","0.14068758",2],["3864.2275","0.01033576",1],["3864.0989","0.00825047",1],["3864.0273","0.38",1],["3864.0272","0.4",1],["3863.9957","0.01083539",1],["3863.9184","0.01653723",1],["3863.8282","0.25588165",1],["3863.8153","0.154",1],["3863.7791","1.14122492",1],["3863.6866","0.01733662",1],["3863.6093","0.02645958",1],["3863.3775","0.02773862",1],["3863.0297","0.513",1],["3863.0286","1.1028564",2],["3862.8489","0.01",1],["3862.5972","0.01890179",1],["3862.3431","0.01152944",1],["3862.313","0.009",1],["3862.2445","0.90551002",3],["3862.0734","0.014",1],["3862.0539","0.64976067",1],["3861.8586","0.025",1],["3861.7888","0.025",1],["3861.7673","0.008",1],["3861.5785","0.01",1],["3861.3895","0.005",1],["3861.3338","0.25875855",1],["3861.161","0.01",1],["3861.1111","0.03863352",1],["3861.0732","0.51703882",1],["3860.9116","0.17754895",1],["3860.75","0.19",1],["3860.6554","0.015",1],["3860.6172","0.005",1],["3860.6088","0.008",1],["3860.4724","0.12940042",1],["3860.4424","0.25880084",1],["3860.42","0.01",1],["3860.3725","0.51760102",1],["3859.8449","0.005",1],["3859.8285","0.03738652",1],["3859.7638","0.07726703",1],["3859.4502","0.008",1],["3859.3772","0.05173471",1],["3859.3409","0.194",1],["3859","5",1],["3858.827","0.0521",1],["3858.8208","0.001",1],["3858.679","0.26",1],["3858.4814","0.07477305",1],["3858.1669","1.03503422",1],["3857.6005","0.006",1],["3857.4005","0.004",1],["3857.2005","0.004",1],["3857.1871","1.218",1],["3857.0005","0.004",1],["3856.8135","0.0646",1],["3856.8005","0.004",1],["3856.2412","0.001",1],["3856.2349","1.03503422",1],["3856.0197","0.01037339",1],["3855.8781","0.23178117",1],["3855.8005","0.004",1],["3855.7165","0.00259355",1],["3855.4858","0.25875855",1],["3854.4584","0.01",1],["3853.6616","0.001",1],["3853.1373","0.92",1],["3852.5072","0.48599702",1],["3851.3926","0.13008333",1],["3851.082","0.001",1],["3850.9317","2",1],["3850.6359","0.34770165",1],["3850.2058","0.51751624",1],["3850.0823","0.15",1],["3850.0042","0.5175171",1],["3850","0.001",1],["3849.6325","1.8",1],["3849.41","0.3",1],["3848.9686","1.85",1],["3848.7426","0.18511466",1],["3848.52","0.3",1],["3848.5024","0.001",1],["3848.42","0.3",1],["3848.1618","2.204",1],["3847.77","0.3",1],["3847.48","0.3",1],["3847.3581","2.05",1],["3846.8259","0.0646",1],["3846.59","0.3",1],["3846.49","0.3",1],["3845.9228","0.001",1],["3844.184","0.00260133",1],["3844.0092","6.3",1],["3843.3432","0.001",1],["3841","0.06300963",1],["3840.7636","0.001",1],["3840","0.201",3],["3839.7681","18",1],["3839.5328","0.05214011",1],["3838.184","0.001",1],["3837.2344","0.27589557",1],["3836.6479","5.2",1],["3836","2.37196773",3],["3835.6044","0.001",1],["3833.6053","0.25873556",1],["3833.0248","0.001",1],["3833","0.8726502",1],["3832.6859","0.00260913",1],["3832","0.007",1],["3831.637","6",1],["3831.0602","0.001",1],["3830.4452","0.001",1],["3830","0.20375718",4],["3829.7125","0.07833486",1],["3829.6283","0.3519681",1],["3829","0.0039261",1],["3827.8656","0.001",1],["3826.0001","0.53251232",1],["3826","0.0509",1],["3825.7834","0.00698562",1],["3825.286","0.001",1],["3823.0001","0.03010127",1],["3822.8014","0.00261588",1],["3822.7064","0.001",1],["3822.2","1",1],["3822.1121","0.35994101",1],["3821.2222","0.00261696",1],["3821","0.001",1],["3820.1268","0.001",1],["3820","1.12992803",4],["3819","0.01331195",2],["3817.5472","0.001",1],["3816","1.13807184",2],["3815.8343","0.32463428",1],["3815.7834","0.00525295",1],["3815","28.99386799",4],["3814.9676","0.001",1],["3813","0.91303023",4],["3812.388","0.002",2],["3811.2257","0.07",1],["3810","0.32573997",2],["3809.8084","0.001",1],["3809.7928","0.00262481",1],["3807.2288","0.001",1],["3806.8421","0.07003461",1],["3806","0.19",1],["3805.8041","0.05678805",1],["3805","1.01",2],["3804.6492","0.001",1],["3804.3551","0.1",1],["3803","0.005",1],["3802.22","2.05042631",1],["3802.0696","0.001",1],["3802","1.63290092",1],["3801.2257","0.07",1],["3801","57.4",3],["3800.9853","0.02492278",1],["3800.8421","0.06503533",1],["3800.7844","0.02812628",1],["3800.0001","0.00409473",1],["3800","17.91401074",15],["3799.49","0.001",1],["3799","0.1",1],["3796.9104","0.001",1],["3796","9.00128053",2],["3795.5441","0.0028",1],["3794.3308","0.001",1],["3791","55",1],["3790.7777","0.07",1],["3790","12.03238184",7],["3789","1",1],["3788","0.21110454",2],["3787.2959","9",1],["3786.592","0.001",1],["3786","9.01916822",2],["3785","12.87914268",5],["3784.0124","0.001",1],["3781.4328","0.002",2],["3781","56.3",2],["3780.7777","0.07",1],["3780","23.41537654",10],["3778.8532","0.002",2],["3776","9",1],["3774","0.003",1],["3772.2481","0.06901672",1],["3771","55.1",2],["3770.7777","0.07",1],["3770","7.30268416",5],["3769","0.25",1],["3768","1.3725",3],["3766.66","0.02",1],["3766","7.64837924",2],["3765.58","1.22775492",1],["3762.58","1.22873383",1],["3761","51.68262164",1],["3760.8031","0.0399",1],["3760.7777","0.07",1]],"timestamp":"2019-03-06T23:19:17.705Z","checksum":-1785549915}]}` + update := `{"table":"spot/depth","action":"update","data":[{"instrument_id":"BTC-USDT","asks":[["3864.6786","0",0],["3864.9852","0",0],["3865.9994","0.48402971",1],["3866.4004","0.001",1],["3866.7995","0.3273",2],["3867.4566","0",0],["3867.7031","0.025",1],["3868.0436","0",0],["3868.346","0",0],["3868.3695","0.051",1],["3870.9243","0.642",1],["3874.9942","0.51751796",1],["3875.7057","0",0],["3939","0.001",1]],"bids":[["3864.55","0.0565449",1],["3863.8282","0",0],["3863.8153","0",0],["3863.7898","0.01320077",1],["3863.4807","0.02112123",1],["3863.3002","0.04233533",1],["3863.1717","0.03379397",1],["3863.0685","0.04438179",1],["3863.0286","0.7362564",1],["3862.9912","0.06773651",1],["3862.8626","0.05407035",1],["3862.7595","0.07101087",1],["3862.313","0.3756",2],["3862.1848","0.012",1],["3862.0734","0",0],["3861.8391","0.025",1],["3861.7888","0",0],["3856.6716","0.38893641",1],["3768","0",0],["3766.66","0",0],["3766","0",0],["3765.58","0",0],["3762.58","0",0],["3761","0",0],["3760.8031","0",0],["3760.7777","0",0]],"timestamp":"2019-03-06T23:19:18.239Z","checksum":-1587788848}]}` + var dataResponse okgroup.WebsocketDataResponse + err := common.JSONDecode([]byte(original), &dataResponse) + if err != nil { + t.Error(err) + } + err = o.WsProcessOrderBook(&dataResponse) + if err != nil { + t.Error(err) + return + } + var updateResponse okgroup.WebsocketDataResponse + err = common.JSONDecode([]byte(update), &updateResponse) + if err != nil { + t.Error(err) + } + time.Sleep(2 * time.Second) + err = o.WsProcessOrderBook(&updateResponse) + if err != nil { + t.Error(err) + } +} + +// TestOrderBookUpdateChecksumCalculatorWithDash logic test +func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { + TestSetDefaults(t) + if o.WebsocketConn == nil { + o.Websocket.Shutdown() + err := setupWSConnection() + if err != nil { + t.Error(err) + } + } + disconnectFromWS() + original := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"WAVES-BTC","asks":[["0.000714","1.15414979",1],["0.000715","3.3",2],["0.000717","426.71348",2],["0.000719","140.84507042",1],["0.00072","590.77",1],["0.000721","991.77",1],["0.000724","0.3532032",1],["0.000725","58.82698567",1],["0.000726","1033.15469748",2],["0.000729","0.35320321",1],["0.00073","352.77",1],["0.000735","0.38469748",1],["0.000736","625.77",1],["0.00075191","152.44796961",1],["0.00075192","114.3359772",1],["0.00075193","85.7519829",1],["0.00075194","64.31398718",1],["0.00075195","48.23549038",1],["0.00075196","36.17661779",1],["0.00075199","61.04804253",1],["0.0007591","70.71318474",1],["0.0007621","53.03488855",1],["0.00076211","39.77616642",1],["0.00076212","29.83212481",1],["0.0007635","22.37409361",1],["0.00076351","29.36599786",2],["0.00076352","9.43907074",1],["0.00076353","7.07930306",1],["0.00076354","14.15860612",1],["0.00076355","3.53965153",1],["0.00076369","3.53965153",1],["0.0008","34.36841101",1],["0.00082858","1.69936503",1],["0.00083232","2.8",1],["0.00084","15.69220129",1],["0.00085","4.42785042",1],["0.00088","0.1",1],["0.000891","0.1",1],["0.0009","12.41486491",2],["0.00093","5",1],["0.0012","12.31486492",1],["0.00531314","6.91803114",1],["0.00799999","0.02",1],["0.0084","0.05989",1],["0.00931314","5.18852336",1],["0.0799999","0.02",1],["0.499","6.00423396",1],["0.5","0.4995",1],["0.799999","0.02",1],["4.99","2",1],["5","3.98583144",1],["7.99999999","0.02",1],["79.99999999","0.02",1],["799.99999999","0.02986704",1]],"bids":[["0.000709","222.91679881",3],["0.000703","0.47161952",1],["0.000701","140.73015789",2],["0.0007","0.3",1],["0.000699","401",1],["0.000698","232.61801667",2],["0.000689","0.71396896",1],["0.000688","0.69910125",1],["0.000613","227.54771052",1],["0.0005","0.01",1],["0.00026789","3.69905341",1],["0.000238","2.4",1],["0.00022","0.53",1],["0.0000055","374.09871696",1],["0.00000056","222",1],["0.00000055","736.84761363",1],["0.0000002","999",1],["0.00000009","1222.22222417",1],["0.00000008","20868.64520447",1],["0.00000002","110000",1],["0.00000001","10000",1]],"timestamp":"2019-03-12T22:22:42.274Z","checksum":1319037905}]}` + update := `{"table":"spot/depth","action":"update","data":[{"instrument_id":"WAVES-BTC","asks":[["0.000715","100.48199596",3],["0.000716","62.21679881",1]],"bids":[["0.000713","38.95772168",1]],"timestamp":"2019-03-12T22:22:42.938Z","checksum":-131160897}]}` + var dataResponse okgroup.WebsocketDataResponse + err := common.JSONDecode([]byte(original), &dataResponse) + if err != nil { + t.Error(err) + } + err = o.WsProcessOrderBook(&dataResponse) + if err != nil { + t.Error(err) + return + } + var updateResponse okgroup.WebsocketDataResponse + err = common.JSONDecode([]byte(update), &updateResponse) + if err != nil { + t.Error(err) + } + time.Sleep(2 * time.Second) + err = o.WsProcessOrderBook(&updateResponse) + if err != nil { + t.Error(err) + } +} + +// TestOrderBookPartialChecksumCalculator logic test +func TestOrderBookPartialChecksumCalculator(t *testing.T) { + orderbookPartialJSON := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"EOS-USDT","asks":[["3.5196","0.1077",1],["3.5198","21.71",1],["3.5199","51.1805",1],["3.5208","75.09",1],["3.521","196.3333",1],["3.5213","0.1",1],["3.5218","39.276",2],["3.5219","395.6334",1],["3.522","27.956",1],["3.5222","404.9595",1],["3.5225","300",1],["3.5227","143.5442",2],["3.523","42.4746",1],["3.5231","852.64",2],["3.5235","34.9602",1],["3.5237","442.0918",2],["3.5238","352.8404",2],["3.5239","341.6759",2],["3.524","84.9493",1],["3.5241","148.4882",1],["3.5242","261.64",1],["3.5243","142.045",1],["3.5246","10",1],["3.5247","284.0788",1],["3.5248","720",1],["3.5249","89.2518",2],["3.5251","1201.8965",2],["3.5254","426.2938",1],["3.5255","213.0863",1],["3.5257","568.1576",1],["3.5258","0.3",1],["3.5259","34.4602",1],["3.526","0.1",1],["3.5263","850.771",1],["3.5265","5.9",1],["3.5268","10.5064",2],["3.5272","1136.8965",1],["3.5274","255.1481",1],["3.5276","29.5374",1],["3.5278","50",1],["3.5282","284.1797",1],["3.5283","1136.8965",1],["3.5284","0.4275",1],["3.5285","100",1],["3.5292","90.9",1],["3.5298","0.2",1],["3.5303","568.1576",1],["3.5305","279.9999",1],["3.532","0.409",1],["3.5321","568.1576",1],["3.5326","6016.8756",1],["3.5328","4.9849",1],["3.533","92.88",2],["3.5343","1200.2383",2],["3.5344","100",1],["3.535","359.7047",1],["3.5354","100",1],["3.5355","100",1],["3.5356","10",1],["3.5358","200",2],["3.5362","435.139",1],["3.5365","2152",1],["3.5366","284.1756",1],["3.5367","568.4644",1],["3.5369","33.9878",1],["3.537","337.1191",2],["3.5373","0.4045",1],["3.5383","1136.7188",1],["3.5386","12.1614",1],["3.5387","90.89",1],["3.54","4.54",1],["3.5423","90.8",1],["3.5436","0.1",1],["3.5454","853.4156",1],["3.5468","142.0656",1],["3.5491","0.0008",1],["3.55","14478.8206",6],["3.5537","21521",1],["3.5555","11.53",1],["3.5573","50.6001",1],["3.5599","4591.4221",1],["3.56","1227.0002",4],["3.5603","2670",1],["3.5608","58.6638",1],["3.5613","0.1",1],["3.5621","45.9473",1],["3.57","2141.7274",3],["3.5712","2956.9816",1],["3.5717","27.9978",1],["3.5718","0.9285",1],["3.5739","299.73",1],["3.5761","864",1],["3.579","22.5225",1],["3.5791","38.26",2],["3.58","7618.4634",5],["3.5801","457.2184",1],["3.582","24.5",1],["3.5822","1572.6425",1],["3.5845","14.1438",1],["3.585","527.169",1],["3.5865","20",1],["3.5867","4490",1],["3.5876","39.0493",1],["3.5879","392.9083",1],["3.5888","436.42",2],["3.5896","50",1],["3.59","2608.9128",8],["3.5913","19.5246",1],["3.5938","7082",1],["3.597","0.1",1],["3.5979","399",1],["3.5995","315.1509",1],["3.5999","2566.2648",1],["3.6","18511.2292",35],["3.603","22.3379",2],["3.605","499.5",1],["3.6055","100",1],["3.6058","499.5",1],["3.608","1021.1485",1],["3.61","11755.4596",13],["3.611","42.8571",1],["3.6131","6690",1],["3.6157","19.5247",1],["3.618","2500",1],["3.6197","525.7146",1],["3.6198","0.4455",1],["3.62","6440.6295",8],["3.6219","0.4175",1],["3.6237","168",1],["3.6265","0.1001",1],["3.628","64.9345",1],["3.63","4435.4985",6],["3.6308","1.7815",1],["3.6331","0.1",1],["3.6338","355.527",2],["3.6358","50",1],["3.6363","2074.7096",1],["3.6376","4000",1],["3.6396","11090",1],["3.6399","0.4055",1],["3.64","4161.9805",4],["3.6437","117.6524",1],["3.648","190",1],["3.6488","200",1],["3.65","11740.5045",25],["3.6512","0.1",1],["3.6521","728",1],["3.6555","100",1],["3.6598","36.6914",1],["3.66","4331.2148",6],["3.6638","200",1],["3.6673","100",1],["3.6679","38",1],["3.6688","2",1],["3.6695","0.1",1],["3.67","7984.698",6],["3.672","300",1],["3.6777","257.8247",1],["3.6789","393.4217",2],["3.68","9202.3222",11],["3.6818","500",1],["3.6823","299.7",1],["3.6839","422.3748",1],["3.685","100",1],["3.6878","0.1",1],["3.6888","72.0958",2],["3.6889","2876",1],["3.689","28",1],["3.6891","28",1],["3.6892","28",1],["3.6895","28",1],["3.6898","28",1],["3.69","643.96",7],["3.6908","118",2],["3.691","28",1],["3.6916","28",1],["3.6918","28",1],["3.6926","28",1],["3.6928","28",1],["3.6932","28",1],["3.6933","200",1],["3.6935","28",1],["3.6936","28",1],["3.6938","28",1],["3.694","28",1],["3.698","1498.5",1],["3.6988","2014.2004",2],["3.7","21904.2689",22],["3.7029","71.95",1],["3.704","3690.1362",1],["3.7055","100",1],["3.7063","0.1",1],["3.71","4421.3468",4],["3.719","17.3491",1],["3.72","1304.5995",3],["3.7211","10",1],["3.7248","0.1",1],["3.725","1900",1],["3.73","31.1785",2],["3.7375","38",1]],"bids":[["3.5182","151.5343",6],["3.5181","0.3691",1],["3.518","271.3967",2],["3.5179","257.8352",1],["3.5178","12.3811",1],["3.5173","34.1921",2],["3.5171","1013.8256",2],["3.517","272.1119",2],["3.5168","395.3376",1],["3.5166","317.1756",2],["3.5165","348.302",3],["3.5164","142.0414",1],["3.5163","96.8933",2],["3.516","600.1034",3],["3.5159","27.481",1],["3.5158","27.33",1],["3.5157","583.1898",2],["3.5156","24.6819",2],["3.5154","25",1],["3.5153","0.429",1],["3.5152","453.9204",3],["3.5151","2131.592",4],["3.515","335",3],["3.5149","37.1586",1],["3.5147","41.6759",1],["3.5146","54.569",1],["3.5145","70.3515",1],["3.5143","68.206",3],["3.5142","359.4538",2],["3.5139","45.4123",2],["3.5137","71.673",2],["3.5136","25",1],["3.5135","300",1],["3.5134","442.57",2],["3.5132","83.3518",1],["3.513","1245.2529",3],["3.5127","20",1],["3.512","284.1353",1],["3.5119","1136.8319",1],["3.5113","56.9351",1],["3.5111","588.1898",2],["3.5109","255.0946",1],["3.5105","48.65",1],["3.5103","50.2",1],["3.5098","720",1],["3.5096","148.95",1],["3.5094","570.5758",2],["3.509","2.386",1],["3.5089","0.4065",1],["3.5087","282.3859",2],["3.5086","145.036",2],["3.5084","2.386",1],["3.5082","90.98",1],["3.5081","2.386",1],["3.5079","2.386",1],["3.5078","857.6229",2],["3.5075","2.386",1],["3.5074","284.1877",1],["3.5073","100",1],["3.5071","100",1],["3.507","768.4159",3],["3.5069","313.0863",2],["3.5068","426.2938",1],["3.5066","568.3594",1],["3.5063","1136.6865",1],["3.5059","0.3",1],["3.5054","9.9999",1],["3.5053","0.2",1],["3.5051","392.428",1],["3.505","13.79",1],["3.5048","99.5497",2],["3.5047","78.5331",2],["3.5046","2153",1],["3.5041","5983.999",1],["3.5037","668.5682",1],["3.5036","160.5948",1],["3.5024","534.8075",1],["3.5014","28.5604",1],["3.5011","91",1],["3.5","1058.8771",2],["3.4997","50.2",1],["3.4985","3430.0414",1],["3.4949","232.0591",1],["3.4942","21521",1],["3.493","2",1],["3.4928","2",1],["3.4925","0.44",1],["3.4917","142.0656",1],["3.49","2051.8826",4],["3.488","280.7459",1],["3.4852","643.4038",1],["3.4851","86.0807",1],["3.485","213.2436",1],["3.484","0.1",1],["3.4811","144.3399",1],["3.4808","89",1],["3.4803","12.1999",1],["3.4801","2390",1],["3.48","930.8453",9],["3.4791","310",1],["3.4768","206",1],["3.4767","0.9415",1],["3.4754","1.4387",1],["3.4728","20",1],["3.4701","1219.2873",1],["3.47","1904.3139",7],["3.468","0.4035",1],["3.4667","0.1",1],["3.4666","3020.0101",1],["3.465","10",1],["3.464","0.4485",1],["3.462","2119.6556",1],["3.46","1305.6113",8],["3.4589","8.0228",1],["3.457","100",1],["3.456","70.3859",2],["3.4538","20",1],["3.4536","4323.9486",2],["3.4531","827.0427",1],["3.4528","0.439",1],["3.4522","8.0381",1],["3.4513","441.1873",1],["3.4512","50.707",1],["3.451","87.0902",1],["3.4509","200",1],["3.4506","100",1],["3.4505","86.4045",2],["3.45","12409.4595",28],["3.4494","0.5365",2],["3.449","10761",1],["3.4482","8.0476",1],["3.4469","0.449",1],["3.445","2000",1],["3.4427","14",1],["3.4421","100",1],["3.4416","8.0631",1],["3.4404","1",1],["3.44","4580.733",11],["3.4388","1868.2085",1],["3.438","937.7246",2],["3.4367","1500",1],["3.4366","62",1],["3.436","29.8743",1],["3.4356","25.4801",1],["3.4349","4.3086",1],["3.4343","43.2402",1],["3.433","2.0688",1],["3.4322","2.7335",2],["3.432","93.3233",1],["3.4302","328.8301",2],["3.43","4440.8158",11],["3.4288","754.574",2],["3.4283","125.7043",2],["3.428","744.3154",2],["3.4273","5460",1],["3.4258","50",1],["3.4255","109.005",1],["3.4248","100",1],["3.4241","129.2048",2],["3.4233","5.3598",1],["3.4228","4498.866",1],["3.4222","3.5435",1],["3.4217","404.3252",2],["3.4211","1000",1],["3.4208","31",1],["3.42","1834.024",9],["3.4175","300",1],["3.4162","400",1],["3.4152","0.1",1],["3.4151","4.3336",1],["3.415","1.5974",1],["3.414","1146",1],["3.4134","306.4246",1],["3.4129","7.5556",1],["3.4111","198.5188",1],["3.4109","500",1],["3.4106","4305",1],["3.41","2150.7635",13],["3.4085","4.342",1],["3.4054","5.6985",1],["3.4019","5.438",1],["3.4015","1010.846",1],["3.4009","8610",1],["3.4005","1.9122",1],["3.4004","1",1],["3.4","27081.1806",67],["3.3955","3.2682",1],["3.3953","5.4486",1],["3.3937","1591.3805",1],["3.39","3221.4155",8],["3.3899","3.2736",1],["3.3888","1500",2],["3.3887","5.4592",1],["3.385","117.0969",2],["3.3821","5.4699",1],["3.382","100.0529",1],["3.3818","172.0164",1],["3.3815","165.6288",1],["3.381","887.3115",1],["3.3808","100",1]],"timestamp":"2019-03-04T00:15:04.155Z","checksum":-2036653089}]}` + var dataResponse okgroup.WebsocketDataResponse + err := common.JSONDecode([]byte(orderbookPartialJSON), &dataResponse) + if err != nil { + t.Error(err) + } + + calculatedChecksum := o.CalculatePartialOrderbookChecksum(&dataResponse.Data[0]) + if calculatedChecksum != dataResponse.Data[0].Checksum { + t.Errorf("Expected %v, Receieved %v", dataResponse.Data[0].Checksum, calculatedChecksum) + } +} + +// Function tests ---------------------------------------------------------------------------------------------- func setFeeBuilder() exchange.FeeBuilder { return exchange.FeeBuilder{ Amount: 1, @@ -52,23 +1054,14 @@ func setFeeBuilder() exchange.FeeBuilder { } } -func TestGetSpotInstruments(t *testing.T) { - t.Parallel() - _, err := o.GetSpotInstruments() - if err != nil { - t.Errorf("Test failed - okcoin GetSpotInstruments() failed: %s", err) - } -} - func TestGetFee(t *testing.T) { - o.SetDefaults() + TestSetDefaults(t) var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic if resp, err := o.GetFee(feeBuilder); resp != float64(0.0015) || err != nil { t.Error(err) t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) } - // CryptocurrencyTradeFee High quantity feeBuilder = setFeeBuilder() feeBuilder.Amount = 1000 @@ -77,15 +1070,13 @@ func TestGetFee(t *testing.T) { t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(1500), resp) t.Error(err) } - // CryptocurrencyTradeFee IsMaker feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true - if resp, err := o.GetFee(feeBuilder); resp != float64(0.0005) || err != nil { + if resp, err := o.GetFee(feeBuilder); resp != float64(0.00100) || err != nil { t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) t.Error(err) } - // CryptocurrencyTradeFee Negative purchase price feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 @@ -93,23 +1084,6 @@ func TestGetFee(t *testing.T) { t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } - // CryptocurrencyWithdrawalFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee - if resp, err := o.GetFee(feeBuilder); resp != float64(0.2) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.2), resp) - t.Error(err) - } - - // CryptocurrencyWithdrawalFee Invalid currency - feeBuilder = setFeeBuilder() - feeBuilder.FirstCurrency = "hello" - feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee - if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) - t.Error(err) - } - // CyptocurrencyDepositFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee @@ -117,7 +1091,6 @@ func TestGetFee(t *testing.T) { t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } - // InternationalBankDepositFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee @@ -125,80 +1098,31 @@ func TestGetFee(t *testing.T) { t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } - // InternationalBankWithdrawalFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.CurrencyItem = symbol.USD - if resp, err := o.GetFee(feeBuilder); resp != float64(15) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(15), resp) + if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } +// TestFormatWithdrawPermissions helper test func TestFormatWithdrawPermissions(t *testing.T) { - o.SetDefaults() - expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - + TestSetDefaults(t) + expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText withdrawPermissions := o.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } -func TestGetActiveOrders(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, - Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, - } - - _, err := o.GetActiveOrders(getOrdersRequest) - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } -} - -func TestGetOrderHistory(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, - Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, - } - - _, err := o.GetOrderHistory(getOrdersRequest) - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } -} - -// Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them -// ---------------------------------------------------------------------------------------------------------------------------- -func areTestAPIKeysSet() bool { - if o.APIKey != "" && o.APIKey != "Key" && - o.APISecret != "" && o.APISecret != "Secret" { - return true - } - return false -} +// Wrapper tests -------------------------------------------------------------------------------------------------- +// TestSubmitOrder Wrapper test func TestSubmitOrder(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + TestSetRealOrderDefaults(t) var p = pair.CurrencyPair{ Delimiter: "", FirstCurrency: symbol.BTC, @@ -212,15 +1136,10 @@ func TestSubmitOrder(t *testing.T) { } } +// TestCancelExchangeOrder Wrapper test func TestCancelExchangeOrder(t *testing.T) { - o.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + TestSetRealOrderDefaults(t) currencyPair := pair.NewCurrencyPair(symbol.LTC, symbol.BTC) - var orderCancellation = exchange.OrderCancellation{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -229,24 +1148,14 @@ func TestCancelExchangeOrder(t *testing.T) { } err := o.CancelOrder(orderCancellation) - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } + testStandardErrorHandling(t, err) + } +// TestCancelAllExchangeOrders Wrapper test func TestCancelAllExchangeOrders(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + TestSetRealOrderDefaults(t) currencyPair := pair.NewCurrencyPair(symbol.LTC, symbol.BTC) - var orderCancellation = exchange.OrderCancellation{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -255,77 +1164,56 @@ func TestCancelAllExchangeOrders(t *testing.T) { } resp, err := o.CancelAllOrders(orderCancellation) - - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } - + testStandardErrorHandling(t, err) if len(resp.OrderStatus) > 0 { t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) } } +// TestGetAccountInfo Wrapper test +func TestGetAccountInfo(t *testing.T) { + _, err := o.GetAccountInfo() + testStandardErrorHandling(t, err) +} + +// TestModifyOrder Wrapper test func TestModifyOrder(t *testing.T) { + TestSetRealOrderDefaults(t) _, err := o.ModifyOrder(exchange.ModifyOrder{}) - if err == nil { - t.Error("Test failed - ModifyOrder() error") + if err != common.ErrFunctionNotSupported { + t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) } } +// TestWithdraw Wrapper test func TestWithdraw(t *testing.T) { - o.SetDefaults() - TestSetup(t) + TestSetRealOrderDefaults(t) var withdrawCryptoRequest = exchange.WithdrawRequest{ Amount: 100, - Currency: "btc_usd", + Currency: symbol.BTC, Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", Description: "WITHDRAW IT ALL", TradePassword: "Password", FeeAmount: 1, } - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - _, err := o.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } + testStandardErrorHandling(t, err) } +// TestWithdrawFiat Wrapper test func TestWithdrawFiat(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + TestSetRealOrderDefaults(t) var withdrawFiatRequest = exchange.WithdrawRequest{} - _, err := o.WithdrawFiatFunds(withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) } } +// TestSubmitOrder Wrapper test func TestWithdrawInternationalBank(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + TestSetRealOrderDefaults(t) var withdrawFiatRequest = exchange.WithdrawRequest{} - _, err := o.WithdrawFiatFundsToInternationalBank(withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/okcoin/okcoin_types.go b/exchanges/okcoin/okcoin_types.go deleted file mode 100644 index 337390eb..00000000 --- a/exchanges/okcoin/okcoin_types.go +++ /dev/null @@ -1,444 +0,0 @@ -package okcoin - -import "github.com/thrasher-/gocryptotrader/currency/symbol" - -// SpotInstrument stores the spot instrument info -type SpotInstrument struct { - BaseCurrency string `json:"base_currency"` - BaseIncrement float64 `json:"base_increment,string"` - BaseMinSize float64 `json:"base_min_size,string"` - InstrumentID string `json:"instrument_id"` - MinSize float64 `json:"min_size,string"` - ProductID string `json:"product_id"` - QuoteCurrency string `json:"quote_currency"` - QuoteIncrement float64 `json:"quote_increment,string"` - SizeIncrement float64 `json:"size_increment,string"` - TickSize float64 `json:"tick_size,string"` -} - -// Ticker holds ticker data -type Ticker struct { - Buy float64 `json:",string"` - High float64 `json:",string"` - Last float64 `json:",string"` - Low float64 `json:",string"` - Sell float64 `json:",string"` - Vol float64 `json:",string"` -} - -// TickerResponse is the response type for ticker -type TickerResponse struct { - Date string - Ticker Ticker -} - -// FuturesTicker holds futures ticker data -type FuturesTicker struct { - Last float64 - Buy float64 - Sell float64 - High float64 - Low float64 - Vol float64 - ContractID int64 - UnitAmount float64 -} - -// Orderbook holds orderbook data -type Orderbook struct { - Asks [][]float64 `json:"asks"` - Bids [][]float64 `json:"bids"` -} - -// FuturesTickerResponse is a response type -type FuturesTickerResponse struct { - Date string - Ticker FuturesTicker -} - -// BorrowInfo holds borrowing amount data -type BorrowInfo struct { - BorrowBTC float64 `json:"borrow_btc"` - BorrowLTC float64 `json:"borrow_ltc"` - BorrowCNY float64 `json:"borrow_cny"` - CanBorrow float64 `json:"can_borrow"` - InterestBTC float64 `json:"interest_btc"` - InterestLTC float64 `json:"interest_ltc"` - Result bool `json:"result"` - DailyInterestBTC float64 `json:"today_interest_btc"` - DailyInterestLTC float64 `json:"today_interest_ltc"` - DailyInterestCNY float64 `json:"today_interest_cny"` -} - -// BorrowOrder holds order data -type BorrowOrder struct { - Amount float64 `json:"amount"` - BorrowDate int64 `json:"borrow_date"` - BorrowID int64 `json:"borrow_id"` - Days int64 `json:"days"` - TradeAmount float64 `json:"deal_amount"` - Rate float64 `json:"rate"` - Status int64 `json:"status"` - Symbol string `json:"symbol"` -} - -// Record hold record data -type Record struct { - Address string `json:"addr"` - Account int64 `json:"account,string"` - Amount float64 `json:"amount"` - Bank string `json:"bank"` - BenificiaryAddress string `json:"benificiary_addr"` - TransactionValue float64 `json:"transaction_value"` - Fee float64 `json:"fee"` - Date float64 `json:"date"` -} - -// AccountRecords holds account record data -type AccountRecords struct { - Records []Record `json:"records"` - Symbol string `json:"symbol"` -} - -// FuturesOrder holds information about a futures order -type FuturesOrder struct { - Amount float64 `json:"amount"` - ContractName string `json:"contract_name"` - DateCreated float64 `json:"create_date"` - TradeAmount float64 `json:"deal_amount"` - Fee float64 `json:"fee"` - LeverageRate float64 `json:"lever_rate"` - OrderID int64 `json:"order_id"` - Price float64 `json:"price"` - AvgPrice float64 `json:"avg_price"` - Status float64 `json:"status"` - Symbol string `json:"symbol"` - Type int64 `json:"type"` - UnitAmount int64 `json:"unit_amount"` -} - -// FuturesHoldAmount contains futures hold amount data -type FuturesHoldAmount struct { - Amount float64 `json:"amount"` - ContractName string `json:"contract_name"` -} - -// FuturesExplosive holds inforamtion about explosive futures -type FuturesExplosive struct { - Amount float64 `json:"amount,string"` - DateCreated string `json:"create_date"` - Loss float64 `json:"loss,string"` - Type int64 `json:"type"` -} - -// Trades holds trade data -type Trades struct { - Amount float64 `json:"amount,string"` - Date int64 `json:"date"` - DateMS int64 `json:"date_ms"` - Price float64 `json:"price,string"` - TradeID int64 `json:"tid"` - Type string `json:"type"` -} - -// FuturesTrades holds trade data for the futures market -type FuturesTrades struct { - Amount float64 `json:"amount"` - Date int64 `json:"date"` - DateMS int64 `json:"date_ms"` - Price float64 `json:"price"` - TradeID int64 `json:"tid"` - Type string `json:"type"` -} - -// UserInfo holds user account details -type UserInfo struct { - Info struct { - Funds struct { - Asset struct { - Net float64 `json:"net,string"` - Total float64 `json:"total,string"` - } `json:"asset"` - Borrow struct { - BTC float64 `json:"btc,string"` - LTC float64 `json:"ltc,string"` - USD float64 `json:"usd,string"` - CNY float64 `json:"cny,string"` - } `json:"borrow"` - Free struct { - BTC float64 `json:"btc,string"` - LTC float64 `json:"ltc,string"` - USD float64 `json:"usd,string"` - CNY float64 `json:"cny,string"` - } `json:"free"` - Freezed struct { - BTC float64 `json:"btc,string"` - LTC float64 `json:"ltc,string"` - USD float64 `json:"usd,string"` - CNY float64 `json:"cny,string"` - } `json:"freezed"` - UnionFund struct { - BTC float64 `json:"btc,string"` - LTC float64 `json:"ltc,string"` - } `json:"union_fund"` - } `json:"funds"` - } `json:"info"` - Result bool `json:"result"` -} - -// BatchTrade holds data on a batch of trades -type BatchTrade struct { - OrderInfo []struct { - OrderID int64 `json:"order_id"` - ErrorCode int64 `json:"error_code"` - } `json:"order_info"` - Result bool `json:"result"` -} - -// CancelOrderResponse is a response type for a cancelled order -type CancelOrderResponse struct { - Success string - ErrorCode string `json:"error_code"` - Result bool `json:"result"` -} - -// OrderInfo holds data on an order -type OrderInfo struct { - Amount float64 `json:"amount"` - AvgPrice float64 `json:"avg_price"` - Created int64 `json:"create_date"` - DealAmount float64 `json:"deal_amount"` - OrderID int64 `json:"order_id"` - OrdersID int64 `json:"orders_id"` - Price float64 `json:"price"` - Status int `json:"status"` - Symbol string `json:"symbol"` - Type string `json:"type"` -} - -// OrderHistory holds information on order history -type OrderHistory struct { - CurrentPage int `json:"current_page"` - Orders []OrderInfo `json:"orders"` - PageLength int `json:"page_length"` - Result bool `json:"result"` - Total int `json:"total"` -} - -// WithdrawalResponse is a response type for withdrawal -type WithdrawalResponse struct { - WithdrawID int `json:"withdraw_id"` - Result bool `json:"result"` -} - -// WithdrawInfo holds data on a withdraw -type WithdrawInfo struct { - Address string `json:"address"` - Amount float64 `json:"amount"` - Created int64 `json:"created_date"` - ChargeFee float64 `json:"chargefee"` - Status int `json:"status"` - WithdrawID int64 `json:"withdraw_id"` -} - -// OrderFeeInfo holds data on order fees -type OrderFeeInfo struct { - Fee float64 `json:"fee,string"` - OrderID int64 `json:"order_id"` - Type string `json:"type"` -} - -// LendDepth hold lend depths -type LendDepth struct { - Amount float64 `json:"amount"` - Days string `json:"days"` - Num int64 `json:"num"` - Rate float64 `json:"rate,string"` -} - -// BorrowResponse is a response type for borrow -type BorrowResponse struct { - Result bool `json:"result"` - BorrowID int `json:"borrow_id"` -} - -// WebsocketFutureIndex holds future index data for websocket -type WebsocketFutureIndex struct { - FutureIndex float64 `json:"futureIndex"` - Timestamp int64 `json:"timestamp,string"` -} - -// WebsocketFuturesTicker holds futures ticker data for websocket -type WebsocketFuturesTicker struct { - Buy float64 `json:"buy"` - ContractID string `json:"contractId"` - High float64 `json:"high"` - HoldAmount float64 `json:"hold_amount"` - Last float64 `json:"last,string"` - Low float64 `json:"low"` - Sell float64 `json:"sell"` - UnitAmount float64 `json:"unitAmount"` - Volume float64 `json:"vol,string"` -} - -// WebsocketUserinfo holds user info for websocket -type WebsocketUserinfo struct { - Info struct { - Funds struct { - Asset struct { - Net float64 `json:"net,string"` - Total float64 `json:"total,string"` - } `json:"asset"` - Free struct { - BTC float64 `json:"btc,string"` - LTC float64 `json:"ltc,string"` - USD float64 `json:"usd,string"` - CNY float64 `json:"cny,string"` - } `json:"free"` - Frozen struct { - BTC float64 `json:"btc,string"` - LTC float64 `json:"ltc,string"` - USD float64 `json:"usd,string"` - CNY float64 `json:"cny,string"` - } `json:"freezed"` - } `json:"funds"` - } `json:"info"` - Result bool `json:"result"` -} - -// WebsocketFuturesContract holds futures contract information for websocket -type WebsocketFuturesContract struct { - Available float64 `json:"available"` - Balance float64 `json:"balance"` - Bond float64 `json:"bond"` - ContractID float64 `json:"contract_id"` - ContractType string `json:"contract_type"` - Frozen float64 `json:"freeze"` - Profit float64 `json:"profit"` - Loss float64 `json:"unprofit"` -} - -// WebsocketFuturesUserInfo holds futures user information for websocket -type WebsocketFuturesUserInfo struct { - Info struct { - BTC struct { - Balance float64 `json:"balance"` - Contracts []WebsocketFuturesContract `json:"contracts"` - Rights float64 `json:"rights"` - } `json:"btc"` - LTC struct { - Balance float64 `json:"balance"` - Contracts []WebsocketFuturesContract `json:"contracts"` - Rights float64 `json:"rights"` - } `json:"ltc"` - } `json:"info"` - Result bool `json:"result"` -} - -// WebsocketOrder holds order data for websocket -type WebsocketOrder struct { - Amount float64 `json:"amount"` - AvgPrice float64 `json:"avg_price"` - DateCreated float64 `json:"create_date"` - TradeAmount float64 `json:"deal_amount"` - OrderID float64 `json:"order_id"` - OrdersID float64 `json:"orders_id"` - Price float64 `json:"price"` - Status int64 `json:"status"` - Symbol string `json:"symbol"` - OrderType string `json:"type"` -} - -// WebsocketFuturesOrder holds futures order data for websocket -type WebsocketFuturesOrder struct { - Amount float64 `json:"amount"` - ContractName string `json:"contract_name"` - DateCreated float64 `json:"createdDate"` - TradeAmount float64 `json:"deal_amount"` - Fee float64 `json:"fee"` - LeverageAmount int `json:"lever_rate"` - OrderID float64 `json:"order_id"` - Price float64 `json:"price"` - AvgPrice float64 `json:"avg_price"` - Status int `json:"status"` - Symbol string `json:"symbol"` - TradeType int `json:"type"` - UnitAmount float64 `json:"unit_amount"` -} - -// WebsocketRealtrades holds real trade data for WebSocket -type WebsocketRealtrades struct { - AveragePrice float64 `json:"averagePrice,string"` - CompletedTradeAmount float64 `json:"completedTradeAmount,string"` - DateCreated float64 `json:"createdDate"` - ID float64 `json:"id"` - OrderID float64 `json:"orderId"` - SigTradeAmount float64 `json:"sigTradeAmount,string"` - SigTradePrice float64 `json:"sigTradePrice,string"` - Status int64 `json:"status"` - Symbol string `json:"symbol"` - TradeAmount float64 `json:"tradeAmount,string"` - TradePrice float64 `json:"buy,string"` - TradeType string `json:"tradeType"` - TradeUnitPrice float64 `json:"tradeUnitPrice,string"` - UnTrade float64 `json:"unTrade,string"` -} - -// WebsocketFuturesRealtrades holds futures real trade data for websocket -type WebsocketFuturesRealtrades struct { - Amount float64 `json:"amount,string"` - ContractID float64 `json:"contract_id,string"` - ContractName string `json:"contract_name"` - ContractType string `json:"contract_type"` - TradeAmount float64 `json:"deal_amount,string"` - Fee float64 `json:"fee,string"` - OrderID float64 `json:"orderid"` - Price float64 `json:"price,string"` - AvgPrice float64 `json:"price_avg,string"` - Status int `json:"status,string"` - TradeType int `json:"type,string"` - UnitAmount float64 `json:"unit_amount,string"` - LeverageAmount int `json:"lever_rate,string"` -} - -// WebsocketEvent holds websocket events -type WebsocketEvent struct { - Event string `json:"event"` - Channel string `json:"channel"` -} - -// WebsocketResponse holds websocket responses -type WebsocketResponse struct { - Channel string `json:"channel"` - Data interface{} `json:"data"` -} - -// WebsocketEventAuth holds websocket authenticated events -type WebsocketEventAuth struct { - Event string `json:"event"` - Channel string `json:"channel"` - Parameters map[string]string `json:"parameters"` -} - -// WebsocketEventAuthRemove holds websocket remove authenticated events -type WebsocketEventAuthRemove struct { - Event string `json:"event"` - Channel string `json:"channel"` - Parameters map[string]string `json:"parameters"` -} - -// WebsocketTradeOrderResponse holds trade order responses for websocket -type WebsocketTradeOrderResponse struct { - OrderID int64 `json:"order_id,string"` - Result bool `json:"result,string"` -} - -// WithdrawalFees the large list of predefined withdrawal fees -// Prone to change, using highest value -var WithdrawalFees = map[string]float64{ - symbol.BTC: 0.005, - symbol.LTC: 0.2, - symbol.ETH: 0.01, - symbol.ETC: 0.2, - symbol.BCH: 0.002, -} diff --git a/exchanges/okcoin/okcoin_websocket.go b/exchanges/okcoin/okcoin_websocket.go deleted file mode 100644 index efc2c487..00000000 --- a/exchanges/okcoin/okcoin_websocket.go +++ /dev/null @@ -1,348 +0,0 @@ -package okcoin - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "strconv" - "time" - - "github.com/gorilla/websocket" - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/currency/pair" - exchange "github.com/thrasher-/gocryptotrader/exchanges" - log "github.com/thrasher-/gocryptotrader/logger" -) - -const ( - wsSubTicker = "ok_sub_spot_%s_ticker" - wsSubDepthIncrement = "ok_sub_spot_%s_depth" - wsSubDepthFull = "ok_sub_spot_%s_depth_%s" - wsSubTrades = "ok_sub_spot_%s_deals" - wsSubKline = "ok_sub_spot_%s_kline_%s" -) - -// PingHandler handles the keep alive -func (o *OKCoin) PingHandler(_ string) error { - return o.WebsocketConn.WriteControl(websocket.PingMessage, - []byte("{'event':'ping'}"), - time.Now().Add(time.Second)) -} - -// AddChannel adds a new channel on the websocket client -func (o *OKCoin) AddChannel(channel string) error { - event := WebsocketEvent{"addChannel", channel} - data, err := common.JSONEncode(event) - if err != nil { - return err - } - - return o.WebsocketConn.WriteMessage(websocket.TextMessage, data) -} - -// WsConnect initiates a websocket connection -func (o *OKCoin) WsConnect() error { - if !o.Websocket.IsEnabled() || !o.IsEnabled() { - return errors.New(exchange.WebsocketNotEnabled) - } - - klineValues := []string{"1min", "3min", "5min", "15min", "30min", "1hour", - "2hour", "4hour", "6hour", "12hour", "day", "3day", "week"} - - 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 - o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(), - http.Header{}) - if err != nil { - return err - } - - o.WebsocketConn.SetPingHandler(o.PingHandler) - - go o.WsHandleData() - - for _, p := range o.GetEnabledCurrencies() { - fPair := exchange.FormatExchangeCurrency(o.GetName(), p) - - o.AddChannel(fmt.Sprintf(wsSubDepthFull, fPair.String(), "20")) - o.AddChannel(fmt.Sprintf(wsSubKline, fPair.String(), klineValues[0])) - o.AddChannel(fmt.Sprintf(wsSubTicker, fPair.String())) - o.AddChannel(fmt.Sprintf(wsSubTrades, fPair.String())) - } - - return nil -} - -// WsReadData reads from the websocket connection -func (o *OKCoin) WsReadData() (exchange.WebsocketResponse, error) { - _, resp, err := o.WebsocketConn.ReadMessage() - if err != nil { - return exchange.WebsocketResponse{}, err - } - - o.Websocket.TrafficAlert <- struct{}{} - return exchange.WebsocketResponse{Raw: resp}, nil -} - -// WsHandleData handles stream data from the websocket connection -func (o *OKCoin) WsHandleData() { - o.Websocket.Wg.Add(1) - - defer func() { - err := o.WebsocketConn.Close() - if err != nil { - o.Websocket.DataHandler <- fmt.Errorf("okcoin_websocket.go - Unable to to close Websocket connection. Error: %s", - err) - } - o.Websocket.Wg.Done() - }() - - for { - select { - case <-o.Websocket.ShutdownC: - return - - default: - resp, err := o.WsReadData() - if err != nil { - o.Websocket.DataHandler <- err - } - - var init []WsResponse - err = common.JSONDecode(resp.Raw, &init) - if err != nil { - o.Websocket.DataHandler <- err - continue - } - - if init[0].ErrorCode != "" { - log.Error(o.WebsocketErrors[init[0].ErrorCode]) - } - - if init[0].Success { - if init[0].Data == nil { - continue - } - } - - if init[0].Channel == "addChannel" { - continue - } - - var currencyPairSlice []string - splitChar := common.SplitStrings(init[0].Channel, "_") - currencyPairSlice = append(currencyPairSlice, - common.StringToUpper(splitChar[3]), - common.StringToUpper(splitChar[4])) - currencyPair := common.JoinStrings(currencyPairSlice, "-") - - assetType := common.StringToUpper(splitChar[2]) - - switch { - case common.StringContains(init[0].Channel, "ticker") && - common.StringContains(init[0].Channel, "spot"): - var ticker WsTicker - - err = common.JSONDecode(init[0].Data, &ticker) - if err != nil { - o.Websocket.DataHandler <- err - continue - } - - o.Websocket.DataHandler <- exchange.TickerData{ - Timestamp: time.Unix(0, ticker.Timestamp), - Pair: pair.NewCurrencyPairFromString(currencyPair), - AssetType: assetType, - Exchange: o.GetName(), - ClosePrice: ticker.Close, - OpenPrice: ticker.Open, - HighPrice: ticker.Last, - LowPrice: ticker.Low, - Quantity: ticker.Volume, - } - - case common.StringContains(init[0].Channel, "depth"): - var orderbook WsOrderbook - - err = common.JSONDecode(init[0].Data, &orderbook) - if err != nil { - o.Websocket.DataHandler <- err - continue - } - - o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ - Pair: pair.NewCurrencyPairFromString(currencyPair), - Exchange: o.GetName(), - Asset: assetType, - } - - case common.StringContains(init[0].Channel, "kline"): - var klineData [][]interface{} - - err = common.JSONDecode(init[0].Data, &klineData) - if err != nil { - o.Websocket.DataHandler <- err - continue - } - - var klines []WsKlines - for _, data := range klineData { - var newKline WsKlines - - newKline.Timestamp, _ = strconv.ParseInt(data[0].(string), 10, 64) - newKline.Open, _ = strconv.ParseFloat(data[1].(string), 64) - newKline.High, _ = strconv.ParseFloat(data[1].(string), 64) - newKline.Low, _ = strconv.ParseFloat(data[1].(string), 64) - newKline.Close, _ = strconv.ParseFloat(data[1].(string), 64) - newKline.Volume, _ = strconv.ParseFloat(data[1].(string), 64) - - klines = append(klines, newKline) - } - - for _, data := range klines { - o.Websocket.DataHandler <- exchange.KlineData{ - Timestamp: time.Unix(0, data.Timestamp), - Pair: pair.NewCurrencyPairFromString(currencyPair), - AssetType: assetType, - Exchange: o.GetName(), - OpenPrice: data.Open, - ClosePrice: data.Close, - HighPrice: data.High, - LowPrice: data.Low, - Volume: data.Volume, - } - } - - case common.StringContains(init[0].Channel, "spot") && - common.StringContains(init[0].Channel, "deals"): - var dealsData [][]interface{} - err = common.JSONDecode(init[0].Data, &dealsData) - if err != nil { - o.Websocket.DataHandler <- err - continue - } - - var deals []WsDeals - for _, data := range dealsData { - var newDeal WsDeals - newDeal.TID, _ = strconv.ParseInt(data[0].(string), 10, 64) - newDeal.Price, _ = strconv.ParseFloat(data[1].(string), 64) - newDeal.Amount, _ = strconv.ParseFloat(data[2].(string), 64) - newDeal.Timestamp, _ = data[3].(string) - newDeal.Type, _ = data[4].(string) - deals = append(deals, newDeal) // nolint: staticcheck - // TODO: will need to link this up - } - } - } - } -} - -// SetWebsocketErrorDefaults sets default errors for websocket -func (o *OKCoin) SetWebsocketErrorDefaults() { - o.WebsocketErrors = map[string]string{ - "10001": "Illegal parameters", - "10002": "Authentication failure", - "10003": "This connection has requested other user data", - "10004": "This connection did not request this user data", - "10005": "System error", - "10009": "Order does not exist", - "10010": "Insufficient funds", - "10011": "Order quantity too low", - "10012": "Only support btc_usd/btc_cny ltc_usd/ltc_cny", - "10014": "Order price must be between 0 - 1,000,000", - "10015": "Channel subscription temporally not available", - "10016": "Insufficient coins", - "10017": "WebSocket authorization error", - "10100": "User frozen", - "10216": "Non-public API", - "20001": "User does not exist", - "20002": "User frozen", - "20003": "Frozen due to force liquidation", - "20004": "Future account frozen", - "20005": "User future account does not exist", - "20006": "Required field can not be null", - "20007": "Illegal parameter", - "20008": "Future account fund balance is zero", - "20009": "Future contract status error", - "20010": "Risk rate information does not exist", - "20011": `Risk rate bigger than 90% before opening position`, - "20012": `Risk rate bigger than 90% after opening position`, - "20013": "Temporally no counter party price", - "20014": "System error", - "20015": "Order does not exist", - "20016": "Liquidation quantity bigger than holding", - "20017": "Not authorized/illegal order ID", - "20018": `Order price higher than 105% or lower than 95% of the price of last minute`, - "20019": "IP restrained to access the resource", - "20020": "Secret key does not exist", - "20021": "Index information does not exist", - "20022": "Wrong API interface", - "20023": "Fixed margin user", - "20024": "Signature does not match", - "20025": "Leverage rate error", - } -} - -// WsOrderbook defines orderbook data from websocket connection -type WsOrderbook struct { - Asks [][]string `json:"asks"` - Bids [][]string `json:"bids"` - Timestamp int64 `json:"timestamp"` -} - -// WsResponse defines initial response stream -type WsResponse struct { - Channel string `json:"channel"` - Result bool `json:"result"` - Success bool `json:"success"` - ErrorCode string `json:"errorcode"` - Data json.RawMessage `json:"data"` -} - -// WsKlines defines a Kline response data from the websocket connection -type WsKlines struct { - Timestamp int64 - Open float64 - High float64 - Low float64 - Close float64 - Volume float64 -} - -// WsTicker holds ticker data for websocket -type WsTicker struct { - High float64 `json:"high,string"` - Volume float64 `json:"vol,string"` - Last float64 `json:"last,string"` - Low float64 `json:"low,string"` - Buy float64 `json:"buy,string"` - Change float64 `json:"change,string"` - Sell float64 `json:"sell,string"` - DayLow float64 `json:"dayLow,string"` - Close float64 `json:"close,string"` - DayHigh float64 `json:"dayHigh,string"` - Open float64 `json:"open,string"` - Timestamp int64 `json:"timestamp"` -} - -// WsDeals defines a deal response from the websocket connection -type WsDeals struct { - TID int64 - Price float64 - Amount float64 - Timestamp string - Type string -} diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go deleted file mode 100644 index b2776610..00000000 --- a/exchanges/okcoin/okcoin_wrapper.go +++ /dev/null @@ -1,396 +0,0 @@ -package okcoin - -import ( - "errors" - "fmt" - "strconv" - "strings" - "sync" - "time" - - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/currency/pair" - exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" -) - -// Start starts the OKCoin go routine -func (o *OKCoin) Start(wg *sync.WaitGroup) { - wg.Add(1) - go func() { - o.Run() - wg.Done() - }() -} - -// Run implements the OKCoin wrapper -func (o *OKCoin) Run() { - if o.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) - log.Debugf("%s polling delay: %ds.\n", o.GetName(), o.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", o.GetName(), len(o.EnabledPairs), o.EnabledPairs) - } - - if o.APIUrl == okcoinAPIURL { - // OKCoin International - forceUpgrade := false - if !common.StringDataContains(o.EnabledPairs, "_") || !common.StringDataContains(o.AvailablePairs, "_") { - forceUpgrade = true - } - - prods, err := o.GetSpotInstruments() - if err != nil { - log.Errorf("OKEX failed to obtain available spot instruments. Err: %s", err) - } else { - var pairs []string - for x := range prods { - pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency) - } - - err = o.UpdateCurrencies(pairs, false, forceUpgrade) - if err != nil { - log.Errorf("OKEX failed to update available currencies. Err: %s", err) - } - } - - if forceUpgrade { - enabledPairs := []string{"btc_usd"} - log.Warn("Available pairs for OKCoin International reset due to config upgrade, please enable the pairs you would like again.") - - err := o.UpdateCurrencies(enabledPairs, true, true) - if err != nil { - log.Errorf("%s failed to update currencies. Err: %s", o.Name, err) - } - } - } -} - -// UpdateTicker updates and returns the ticker for a currency pair -func (o *OKCoin) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { - currency := exchange.FormatExchangeCurrency(o.Name, p).String() - var tickerPrice ticker.Price - - if assetType != ticker.Spot && o.APIUrl == okcoinAPIURL { - tick, err := o.GetFuturesTicker(currency, assetType) - if err != nil { - return tickerPrice, err - } - tickerPrice.Pair = p - tickerPrice.Ask = tick.Sell - tickerPrice.Bid = tick.Buy - tickerPrice.Low = tick.Low - tickerPrice.Last = tick.Last - tickerPrice.Volume = tick.Vol - tickerPrice.High = tick.High - ticker.ProcessTicker(o.GetName(), p, tickerPrice, assetType) - } else { - tick, err := o.GetTicker(currency) - if err != nil { - return tickerPrice, err - } - tickerPrice.Pair = p - tickerPrice.Ask = tick.Sell - tickerPrice.Bid = tick.Buy - tickerPrice.Low = tick.Low - tickerPrice.Last = tick.Last - tickerPrice.Volume = tick.Vol - tickerPrice.High = tick.High - ticker.ProcessTicker(o.GetName(), p, tickerPrice, ticker.Spot) - - } - return ticker.GetTicker(o.Name, p, assetType) -} - -// GetTickerPrice returns the ticker for a currency pair -func (o *OKCoin) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(o.GetName(), p, assetType) - if err != nil { - return o.UpdateTicker(p, assetType) - } - return tickerNew, nil -} - -// GetOrderbookEx returns orderbook base on the currency pair -func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) { - ob, err := orderbook.GetOrderbook(o.GetName(), currency, assetType) - if err != nil { - return o.UpdateOrderbook(currency, assetType) - } - return ob, nil -} - -// UpdateOrderbook updates and returns the orderbook for a currency pair -func (o *OKCoin) UpdateOrderbook(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) { - var orderBook orderbook.Base - orderbookNew, err := o.GetOrderBook(exchange.FormatExchangeCurrency(o.Name, currency).String(), 200, false) - if err != nil { - return orderBook, err - } - - for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]}) - } - - for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]}) - } - - orderbook.ProcessOrderbook(o.GetName(), currency, orderBook, assetType) - return orderbook.GetOrderbook(o.Name, currency, assetType) -} - -// GetAccountInfo retrieves balances for all enabled currencies for the -// OKCoin exchange -func (o *OKCoin) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo - response.Exchange = o.GetName() - assets, err := o.GetUserInfo() - if err != nil { - return response, err - } - - var currencies = []exchange.AccountCurrencyInfo{ - { - CurrencyName: "BTC", - TotalValue: assets.Info.Funds.Free.BTC, - Hold: assets.Info.Funds.Freezed.BTC, - }, - { - CurrencyName: "LTC", - TotalValue: assets.Info.Funds.Free.LTC, - Hold: assets.Info.Funds.Freezed.LTC, - }, - { - CurrencyName: "USD", - TotalValue: assets.Info.Funds.Free.USD, - Hold: assets.Info.Funds.Freezed.USD, - }, - { - CurrencyName: "CNY", - TotalValue: assets.Info.Funds.Free.CNY, - Hold: assets.Info.Funds.Freezed.CNY, - }, - } - response.Accounts = append(response.Accounts, exchange.Account{ - Currencies: currencies, - }) - - return response, nil -} - -// GetFundingHistory returns funding history, deposits and -// withdrawals -func (o *OKCoin) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported -} - -// GetExchangeHistory returns historic trade data since exchange opening. -func (o *OKCoin) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented -} - -// SubmitOrder submits a new order -func (o *OKCoin) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - var oT string - - switch orderType { - case exchange.LimitOrderType: - oT = "sell" - if side == exchange.BuyOrderSide { - oT = "buy" - } - case exchange.MarketOrderType: - oT = "sell_market" - if side == exchange.BuyOrderSide { - oT = "buy_market" - } - default: - return submitOrderResponse, errors.New("unsupported order type") - } - - response, err := o.Trade(amount, price, p.Pair().String(), oT) - - if response > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response) - } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - - return submitOrderResponse, err -} - -// ModifyOrder will allow of changing orderbook placement and limit to -// market conversion -func (o *OKCoin) ModifyOrder(action exchange.ModifyOrder) (string, error) { - return "", common.ErrFunctionNotSupported -} - -// CancelOrder cancels an order by its corresponding ID number -func (o *OKCoin) CancelOrder(order exchange.OrderCancellation) error { - orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) - orders := []int64{orderIDInt} - - if err != nil { - return err - } - - resp, err := o.CancelExistingOrder(orders, exchange.FormatExchangeCurrency(o.Name, order.CurrencyPair).String()) - if !resp.Result { - return errors.New(resp.ErrorCode) - } - return err -} - -// CancelAllOrders cancels all orders associated with a currency pair -func (o *OKCoin) CancelAllOrders(orderCancellation exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), - } - orderInfo, err := o.GetOrderInformation(-1, exchange.FormatExchangeCurrency(o.Name, orderCancellation.CurrencyPair).String()) - if err != nil { - return cancelAllOrdersResponse, err - } - - var ordersToCancel []int64 - for _, order := range orderInfo { - ordersToCancel = append(ordersToCancel, order.OrderID) - } - - if len(ordersToCancel) > 0 { - resp, err := o.CancelExistingOrder(ordersToCancel, exchange.FormatExchangeCurrency(o.Name, orderCancellation.CurrencyPair).String()) - if err != nil { - return cancelAllOrdersResponse, err - } - - for _, order := range common.SplitStrings(resp.ErrorCode, ",") { - if err != nil { - cancelAllOrdersResponse.OrderStatus[order] = "Order could not be cancelled" - } - } - } - - return cancelAllOrdersResponse, nil -} - -// GetOrderInfo returns information on a current open order -func (o *OKCoin) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail - return orderDetail, common.ErrNotYetImplemented -} - -// GetDepositAddress returns a deposit address for a specified currency -func (o *OKCoin) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { - // NOTE needs API version update to access - return "", common.ErrNotYetImplemented -} - -// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is -// submitted -func (o *OKCoin) WithdrawCryptocurrencyFunds(withdrawRequest exchange.WithdrawRequest) (string, error) { - resp, err := o.Withdrawal(withdrawRequest.Currency.String(), withdrawRequest.FeeAmount, withdrawRequest.TradePassword, withdrawRequest.Address, withdrawRequest.Amount) - return fmt.Sprintf("%v", resp), err -} - -// WithdrawFiatFunds returns a withdrawal ID when a -// withdrawal is submitted -func (o *OKCoin) WithdrawFiatFunds(withdrawRequest exchange.WithdrawRequest) (string, error) { - return "", common.ErrFunctionNotSupported -} - -// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a -// withdrawal is submitted -func (o *OKCoin) WithdrawFiatFundsToInternationalBank(withdrawRequest exchange.WithdrawRequest) (string, error) { - return "", common.ErrFunctionNotSupported -} - -// GetWebsocket returns a pointer to the exchange websocket -func (o *OKCoin) GetWebsocket() (*exchange.Websocket, error) { - return o.Websocket, nil -} - -// GetFeeByType returns an estimate of fee based on type of transaction -func (o *OKCoin) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { - return o.GetFee(feeBuilder) -} - -// GetActiveOrders retrieves any orders that are active/open -func (o *OKCoin) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var allOrders []OrderInfo - for _, currency := range getOrdersRequest.Currencies { - resp, err := o.GetOrderHistoryForCurrency(200, 0, 0, exchange.FormatExchangeCurrency(o.Name, currency).String()) - if err != nil { - return nil, err - } - - allOrders = append(allOrders, resp.Orders...) - } - - var orders []exchange.OrderDetail - for _, order := range allOrders { - // Status 2 == Filled, -1 == Cancelled. - if order.Status == 2 || order.Status == -1 { - continue - } - - symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter) - orderDate := time.Unix(order.Created, 0) - side := exchange.OrderSide(strings.ToUpper(order.Type)) - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.OrderID), - Amount: order.Amount, - OrderDate: orderDate, - Price: order.Price, - OrderSide: side, - CurrencyPair: symbol, - Exchange: o.Name, - }) - } - - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - - return orders, nil -} - -// GetOrderHistory retrieves account order information -// Can Limit response to specific order status -func (o *OKCoin) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var allOrders []OrderInfo - for _, currency := range getOrdersRequest.Currencies { - resp, err := o.GetOrderInformation(-1, exchange.FormatExchangeCurrency(o.Name, currency).String()) - if err != nil { - return nil, err - } - allOrders = append(allOrders, resp...) - } - var orders []exchange.OrderDetail - for _, order := range allOrders { - symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter) - orderDate := time.Unix(order.Created, 0) - side := exchange.OrderSide(strings.ToUpper(order.Type)) - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.OrderID), - Amount: order.Amount, - OrderDate: orderDate, - Price: order.Price, - OrderSide: side, - CurrencyPair: symbol, - Exchange: o.Name, - }) - } - - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - - return orders, nil -} diff --git a/exchanges/okex/okex.go b/exchanges/okex/okex.go index 727dc86c..702670fc 100644 --- a/exchanges/okex/okex.go +++ b/exchanges/okex/okex.go @@ -1,110 +1,59 @@ package okex import ( - "encoding/json" - "errors" "fmt" "net/http" - "net/url" - "reflect" - "strconv" - "strings" - "sync" "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/okgroup" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( - // REST API information - apiURL = "https://www.okex.com/api/" - apiVersion = "v1/" - - // Contract requests - // Unauthenticated - contractPrice = "future_ticker" - contractFutureDepth = "future_depth" - contractTradeHistory = "future_trades" - contractFutureIndex = "future_index" - contractExchangeRate = "exchange_rate" - contractFutureEstPrice = "future_estimated_price" - contractCandleStick = "future_kline" - contractFutureHoldAmount = "future_hold_amount" - contractFutureLimits = "future_price_limit" - - // Authenticated - contractFutureUserInfo = "future_userinfo" - contractFuturePosition = "future_position" - contractFutureTrade = "future_trade" - contractFutureTradeHistory = "future_trades_history" - contractFutureBatchTrade = "future_batch_trade" - contractFutureCancel = "future_cancel" - contractFutureOrderInfo = "future_order_info" - contractFutureMultOrderInfo = "future_orders_info" - contractFutureUserInfo4fix = "future_userinfo_4fix" - contractFuturePosition4fix = "future_position_4fix" - contractFutureExplosive = "future_explosive" - contractFutureDevolve = "future_devolve" - - // Spot requests - // Unauthenticated - spotPrice = "ticker" - spotDepth = "depth" - spotTrades = "trades" - spotKline = "kline" - instruments = "instruments" - - // Authenticated - spotUserInfo = "userinfo" - spotTrade = "trade" - spotBatchTrade = "batch_trade" - spotCancelTrade = "cancel_order" - spotOrderInfo = "order_info.do" - spotOrderHistory = "order_history.do" - spotMultiOrderInfo = "orders_info" - spotWithdraw = "withdraw.do" - spotCancelWithdraw = "cancel_withdraw" - spotWithdrawInfo = "withdraw_info" - spotAccountRecords = "account_records" - - myWalletInfo = "wallet_info.do" - - // just your average return type from okex - returnTypeOne = "map[string]interface {}" - - okexAuthRate = 0 - okexUnauthRate = 0 + okExAuthRate = 600 + okExUnauthRate = 600 + okExAPIPath = "api/" + okExAPIURL = "https://www.okex.com/" + okExAPIPath + okExAPIVersion = "/v3/" + okExExchangeName = "OKEX" + // OkExWebsocketURL WebsocketURL + OkExWebsocketURL = "wss://real.okex.com:10442/ws/v3" + // API subsections + okGroupFuturesSubsection = "futures" + okGroupSwapSubsection = "swap" + okGroupETTSubsection = "ett" + // Futures based endpoints + okGroupFuturePosition = "position" + okGroupFutureLeverage = "leverage" + okGroupFutureOrder = "order" + okGroupFutureHolds = "holds" + okGroupIndices = "index" + okGroupRate = "rate" + okGroupEsimtatedPrice = "estimated_price" + okGroupOpenInterest = "open_interest" + // Perpetual swap based endpoints + okGroupSettings = "settings" + okGroupDepth = "depth" + okGroupFundingTime = "funding_time" + okGroupHistoricalFundingRate = "historical_funding_rate" + // ETT endpoints + okGroupConstituents = "constituents" + okGroupDefinePrice = "define-price" ) -var errMissValue = errors.New("warning - resp value is missing from exchange") - -// OKEX is the overaching type across the OKEX methods +// OKEX bases all account, spot and margin methods off okgroup implementation type OKEX struct { - exchange.Base - WebsocketConn *websocket.Conn - mu sync.Mutex - - // Spot and contract market error codes as per https://www.okex.com/rest_request.html - ErrorCodes map[string]error - - // Stores for corresponding variable checks - ContractTypes []string - CurrencyPairs []string - ContractPosition []string - Types []string + okgroup.OKGroup } -// SetDefaults method assignes the default values for Bittrex +// SetDefaults method assignes the default values for OKEX func (o *OKEX) SetDefaults() { o.SetErrorDefaults() o.SetCheckVarDefaults() - o.Name = "OKEX" + o.Name = okExExchangeName o.Enabled = false o.Verbose = false o.RESTPollingDelay = 10 @@ -117,1177 +66,425 @@ func (o *OKEX) SetDefaults() { o.SupportsAutoPairUpdating = true o.SupportsRESTTickerBatching = false o.Requester = request.New(o.Name, - request.NewRateLimit(time.Second, okexAuthRate), - request.NewRateLimit(time.Second, okexUnauthRate), + request.NewRateLimit(time.Second, okExAuthRate), + request.NewRateLimit(time.Second, okExUnauthRate), common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - o.APIUrlDefault = apiURL - o.APIUrl = o.APIUrlDefault + o.APIUrlDefault = okExAPIURL + o.APIUrl = okExAPIURL o.AssetTypes = []string{ticker.Spot} o.WebsocketInit() + o.APIVersion = okExAPIVersion + o.WebsocketURL = OkExWebsocketURL o.Websocket.Functionality = exchange.WebsocketTickerSupported | exchange.WebsocketTradeDataSupported | exchange.WebsocketKlineSupported | exchange.WebsocketOrderbookSupported } -// Setup method sets current configuration details if enabled -func (o *OKEX) Setup(exch config.ExchangeConfig) { - if !exch.Enabled { - o.SetEnabled(false) - } else { - o.Enabled = true - o.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - o.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, false) - o.SetHTTPClientTimeout(exch.HTTPTimeout) - o.SetHTTPClientUserAgent(exch.HTTPUserAgent) - o.RESTPollingDelay = exch.RESTPollingDelay - o.Verbose = exch.Verbose - o.Websocket.SetEnabled(exch.Websocket) - o.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") - o.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") - o.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") - err := o.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = o.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = o.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = o.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = o.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = o.WebsocketSetup(o.WsConnect, - exch.Name, - exch.Websocket, - okexDefaultWebsocketURL, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } +// GetFuturesPostions Get the information of all holding positions in futures trading.Due to high energy consumption, you are advised to capture data with the "Futures Account of a Currency" API instead. +func (o *OKEX) GetFuturesPostions() (resp okgroup.GetFuturesPositionsResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, okGroupFuturePosition, nil, &resp, true) } -// GetSpotInstruments returns a list of tradable spot instruments and their properties -func (o *OKEX) GetSpotInstruments() ([]SpotInstrument, error) { - var resp []SpotInstrument - - path := fmt.Sprintf("%sspot/v3/%s", o.APIUrl, instruments) - err := o.SendHTTPRequest(path, &resp) - - if err != nil { - return nil, err - } - - return resp, nil +// GetFuturesPostionsForCurrency Get the information of holding positions of a contract. +func (o *OKEX) GetFuturesPostionsForCurrency(instrumentID string) (resp okgroup.GetFuturesPositionsForCurrencyResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", instrumentID, okGroupFuturePosition) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// GetContractPrice returns current contract prices -// -// symbol e.g. "btc_usd" -// contractType e.g. "this_week" "next_week" "quarter" -func (o *OKEX) GetContractPrice(symbol, contractType string) (ContractPrice, error) { - resp := ContractPrice{} - - if err := o.CheckContractType(contractType); err != nil { - return resp, err - } - if err := o.CheckSymbol(symbol); err != nil { - return resp, err - } - - values := url.Values{} - values.Set("symbol", common.StringToLower(symbol)) - values.Set("contract_type", common.StringToLower(contractType)) - - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractPrice, values.Encode()) - - err := o.SendHTTPRequest(path, &resp) - if err != nil { - return resp, err - } - - if !resp.Result { - if resp.Error != nil { - return resp, o.GetErrorCode(resp.Error) - } - } - return resp, nil +// GetFuturesAccountOfAllCurrencies Get the futures account info of all token. +// Due to high energy consumption, you are advised to capture data with the "Futures Account of a Currency" API instead. +func (o *OKEX) GetFuturesAccountOfAllCurrencies() (resp okgroup.FuturesAccountForAllCurrenciesResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, okgroup.OKGroupAccounts, nil, &resp, true) } -// GetContractMarketDepth returns contract market depth -// -// symbol e.g. "btc_usd" -// contractType e.g. "this_week" "next_week" "quarter" -func (o *OKEX) GetContractMarketDepth(symbol, contractType string) (ActualContractDepth, error) { - resp := ContractDepth{} - fullDepth := ActualContractDepth{} - - if err := o.CheckContractType(contractType); err != nil { - return fullDepth, err - } - if err := o.CheckSymbol(symbol); err != nil { - return fullDepth, err - } - - values := url.Values{} - values.Set("symbol", common.StringToLower(symbol)) - values.Set("contract_type", common.StringToLower(contractType)) - - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractFutureDepth, values.Encode()) - - err := o.SendHTTPRequest(path, &resp) - if err != nil { - return fullDepth, err - } - - if !resp.Result { - if resp.Error != nil { - return fullDepth, o.GetErrorCode(resp.Error) - } - } - - for _, ask := range resp.Asks { - var askdepth struct { - Price float64 - Volume float64 - } - for i, depth := range ask.([]interface{}) { - if i == 0 { - askdepth.Price = depth.(float64) - } - if i == 1 { - askdepth.Volume = depth.(float64) - } - } - fullDepth.Asks = append(fullDepth.Asks, askdepth) - } - - for _, bid := range resp.Bids { - var bidDepth struct { - Price float64 - Volume float64 - } - for i, depth := range bid.([]interface{}) { - if i == 0 { - bidDepth.Price = depth.(float64) - } - if i == 1 { - bidDepth.Volume = depth.(float64) - } - } - fullDepth.Bids = append(fullDepth.Bids, bidDepth) - } - - return fullDepth, nil +// GetFuturesAccountOfACurrency Get the futures account info of a token. +func (o *OKEX) GetFuturesAccountOfACurrency(instrumentID string) (resp okgroup.FuturesCurrencyData, _ error) { + requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupAccounts, instrumentID) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// GetContractTradeHistory returns trade history for the contract market -func (o *OKEX) GetContractTradeHistory(symbol, contractType string) ([]ActualContractTradeHistory, error) { - actualTradeHistory := []ActualContractTradeHistory{} - var resp interface{} - - if err := o.CheckContractType(contractType); err != nil { - return actualTradeHistory, err - } - if err := o.CheckSymbol(symbol); err != nil { - return actualTradeHistory, err - } - - values := url.Values{} - values.Set("symbol", common.StringToLower(symbol)) - values.Set("contract_type", common.StringToLower(contractType)) - - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractTradeHistory, values.Encode()) - - err := o.SendHTTPRequest(path, &resp) - if err != nil { - return actualTradeHistory, err - } - - if reflect.TypeOf(resp).String() == returnTypeOne { - errorMap := resp.(map[string]interface{}) - return actualTradeHistory, o.GetErrorCode(errorMap["error_code"].(float64)) - } - - for _, tradeHistory := range resp.([]interface{}) { - quickHistory := ActualContractTradeHistory{} - tradeHistoryM := tradeHistory.(map[string]interface{}) - quickHistory.Date = tradeHistoryM["date"].(float64) - quickHistory.DateInMS = tradeHistoryM["date_ms"].(float64) - quickHistory.Amount = tradeHistoryM["amount"].(float64) - quickHistory.Price = tradeHistoryM["price"].(float64) - quickHistory.Type = tradeHistoryM["type"].(string) - quickHistory.TID = tradeHistoryM["tid"].(float64) - actualTradeHistory = append(actualTradeHistory, quickHistory) - } - return actualTradeHistory, nil +// GetFuturesLeverage Get the leverage of the futures account +func (o *OKEX) GetFuturesLeverage(instrumentID string) (resp okgroup.GetFuturesLeverageResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupAccounts, instrumentID, okGroupFutureLeverage) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// GetContractIndexPrice returns the current index price -// -// symbol e.g. btc_usd -func (o *OKEX) GetContractIndexPrice(symbol string) (float64, error) { - if err := o.CheckSymbol(symbol); err != nil { - return 0, err - } - - values := url.Values{} - values.Set("symbol", common.StringToLower(symbol)) - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractFutureIndex, values.Encode()) - var resp interface{} - - err := o.SendHTTPRequest(path, &resp) - if err != nil { - return 0, err - } - - futureIndex := resp.(map[string]interface{}) - if i, ok := futureIndex["error_code"].(float64); ok { - return 0, o.GetErrorCode(i) - } - - if _, ok := futureIndex["future_index"].(float64); ok { - return futureIndex["future_index"].(float64), nil - } - return 0, errMissValue +// SetFuturesLeverage Adjusting the leverage for futures account。 +// Cross margin request requirements: {"leverage":"10"} +// Fixed margin request requirements: {"instrument_id":"BTC-USD-180213","direction":"long","leverage":"10"} +func (o *OKEX) SetFuturesLeverage(request okgroup.SetFuturesLeverageRequest) (resp okgroup.SetFuturesLeverageResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupAccounts, request.Currency, okGroupFutureLeverage) + return resp, o.SendHTTPRequest(http.MethodPost, okGroupFuturesSubsection, requestURL, request, &resp, true) } -// GetContractExchangeRate returns the current exchange rate for the currency -// pair -// USD-CNY exchange rate used by OKEX, updated weekly -func (o *OKEX) GetContractExchangeRate() (float64, error) { - path := fmt.Sprintf("%s%s%s.do?", o.APIUrl, apiVersion, contractExchangeRate) - var resp interface{} - - if err := o.SendHTTPRequest(path, &resp); err != nil { - return 0, err - } - - exchangeRate := resp.(map[string]interface{}) - if i, ok := exchangeRate["error_code"].(float64); ok { - return 0, o.GetErrorCode(i) - } - - if _, ok := exchangeRate["rate"].(float64); ok { - return exchangeRate["rate"].(float64), nil - } - return 0, errMissValue +// GetFuturesBillDetails Shows the account’s historical coin in flow and out flow. +// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKEX) GetFuturesBillDetails(request okgroup.GetSpotBillDetailsForCurrencyRequest) (resp []okgroup.GetSpotBillDetailsForCurrencyResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupAccounts, request.Currency, okgroup.OKGroupLedger, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// GetContractFutureEstimatedPrice returns futures estimated price -// -// symbol e.g btc_usd -func (o *OKEX) GetContractFutureEstimatedPrice(symbol string) (float64, error) { - if err := o.CheckSymbol(symbol); err != nil { - return 0, err - } - - values := url.Values{} - values.Set("symbol", symbol) - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractFutureIndex, values.Encode()) - var resp interface{} - - if err := o.SendHTTPRequest(path, &resp); err != nil { - return 0, err - } - - futuresEstPrice := resp.(map[string]interface{}) - if i, ok := futuresEstPrice["error_code"].(float64); ok { - return 0, o.GetErrorCode(i) - } - - if _, ok := futuresEstPrice["future_index"].(float64); ok { - return futuresEstPrice["future_index"].(float64), nil - } - return 0, errMissValue +// PlaceFuturesOrder OKEx futures trading only supports limit orders. +// You can place an order only if you have enough funds. Once your order is placed, the amount will be put on hold in the order lifecycle. +// The assets and amount on hold depends on the order's specific type and parameters. +func (o *OKEX) PlaceFuturesOrder(request okgroup.PlaceFuturesOrderRequest) (resp okgroup.PlaceFuturesOrderResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodPost, okGroupFuturesSubsection, okGroupFutureOrder, request, &resp, true) } -// GetContractCandlestickData returns CandleStickData -// -// symbol e.g. btc_usd -// type e.g. 1min or 1 minute candlestick data -// contract_type e.g. this_week -// size: specify data size to be acquired -// since: timestamp(eg:1417536000000). data after the timestamp will be returned -func (o *OKEX) GetContractCandlestickData(symbol, typeInput, contractType string, size, since int) ([]CandleStickData, error) { - var candleData []CandleStickData - if err := o.CheckSymbol(symbol); err != nil { - return candleData, err - } - if err := o.CheckContractType(contractType); err != nil { - return candleData, err - } - if err := o.CheckType(typeInput); err != nil { - return candleData, err - } - - values := url.Values{} - values.Set("symbol", symbol) - values.Set("type", typeInput) - values.Set("contract_type", contractType) - values.Set("size", strconv.FormatInt(int64(size), 10)) - values.Set("since", strconv.FormatInt(int64(since), 10)) - - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractCandleStick, values.Encode()) - var resp interface{} - - if err := o.SendHTTPRequest(path, &resp); err != nil { - return candleData, err - } - - if reflect.TypeOf(resp).String() == returnTypeOne { - errorMap := resp.(map[string]interface{}) - return candleData, o.GetErrorCode(errorMap["error_code"].(float64)) - } - - for _, candleStickData := range resp.([]interface{}) { - var quickCandle CandleStickData - - for i, datum := range candleStickData.([]interface{}) { - switch i { - case 0: - quickCandle.Timestamp = datum.(float64) - case 1: - quickCandle.Open = datum.(float64) - case 2: - quickCandle.High = datum.(float64) - case 3: - quickCandle.Low = datum.(float64) - case 4: - quickCandle.Close = datum.(float64) - case 5: - quickCandle.Volume = datum.(float64) - case 6: - quickCandle.Amount = datum.(float64) - default: - return candleData, errors.New("incoming data out of range") - } - } - candleData = append(candleData, quickCandle) - } - - return candleData, nil +// PlaceFuturesOrderBatch Batch contract placing order operation. +func (o *OKEX) PlaceFuturesOrderBatch(request okgroup.PlaceFuturesOrderBatchRequest) (resp okgroup.PlaceFuturesOrderBatchResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodPost, okGroupFuturesSubsection, okgroup.OKGroupOrders, request, &resp, true) } -// GetContractHoldingsNumber returns current number of holdings -func (o *OKEX) GetContractHoldingsNumber(symbol, contractType string) (number float64, contract string, err error) { - err = o.CheckSymbol(symbol) - if err != nil { - return number, contract, err - } - - err = o.CheckContractType(contractType) - if err != nil { - return number, contract, err - } - - values := url.Values{} - values.Set("symbol", symbol) - values.Set("contract_type", contractType) - - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractFutureHoldAmount, values.Encode()) - var resp interface{} - - err = o.SendHTTPRequest(path, &resp) - if err != nil { - return number, contract, err - } - - if reflect.TypeOf(resp).String() == returnTypeOne { - errorMap := resp.(map[string]interface{}) - return number, contract, o.GetErrorCode(errorMap["error_code"].(float64)) - } - - for _, holdings := range resp.([]interface{}) { - if reflect.TypeOf(holdings).String() == returnTypeOne { - holdingMap := holdings.(map[string]interface{}) - number = holdingMap["amount"].(float64) - contract = holdingMap["contract_name"].(string) - } - } - return number, contract, err +// CancelFuturesOrder Cancelling an unfilled order. +func (o *OKEX) CancelFuturesOrder(request okgroup.CancelFuturesOrderRequest) (resp okgroup.CancelFuturesOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupCancelOrder, request.InstrumentID, request.OrderID) + return resp, o.SendHTTPRequest(http.MethodPost, okGroupFuturesSubsection, requestURL, request, &resp, true) } -// GetContractlimit returns upper and lower price limit -func (o *OKEX) GetContractlimit(symbol, contractType string) (map[string]float64, error) { - contractLimits := make(map[string]float64) - if err := o.CheckSymbol(symbol); err != nil { - return contractLimits, err - } - if err := o.CheckContractType(contractType); err != nil { - return contractLimits, err - } - - values := url.Values{} - values.Set("symbol", symbol) - values.Set("contract_type", contractType) - - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractFutureLimits, values.Encode()) - var resp interface{} - - if err := o.SendHTTPRequest(path, &resp); err != nil { - return contractLimits, err - } - - contractLimitMap := resp.(map[string]interface{}) - if i, ok := contractLimitMap["error_code"].(float64); ok { - return contractLimits, o.GetErrorCode(i) - } - - contractLimits["high"] = contractLimitMap["high"].(float64) - contractLimits["usdCnyRate"] = contractLimitMap["usdCnyRate"].(float64) - contractLimits["low"] = contractLimitMap["low"].(float64) - return contractLimits, nil +// CancelFuturesOrderBatch With best effort, cancel all open orders. +func (o *OKEX) CancelFuturesOrderBatch(request okgroup.CancelMultipleSpotOrdersRequest) (resp okgroup.CancelMultipleSpotOrdersResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupCancelBatchOrders, request.InstrumentID) + return resp, o.SendHTTPRequest(http.MethodPost, okGroupFuturesSubsection, requestURL, request, &resp, true) } -// GetContractUserInfo returns OKEX Contract Account Info(Cross-Margin Mode) -func (o *OKEX) GetContractUserInfo() error { - var resp interface{} - if err := o.SendAuthenticatedHTTPRequest(contractFutureUserInfo, url.Values{}, &resp); err != nil { - return err - } - - userInfoMap := resp.(map[string]interface{}) - if code, ok := userInfoMap["error_code"]; ok { - return o.GetErrorCode(code) - } - return nil +// GetFuturesOrderList List your orders. Cursor pagination is used. +// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKEX) GetFuturesOrderList(request okgroup.GetFuturesOrdersListRequest) (resp okgroup.GetFuturesOrderListResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v%v", okgroup.OKGroupOrders, request.InstrumentID, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// GetContractPosition returns User Contract Positions (Cross-Margin Mode) -func (o *OKEX) GetContractPosition(symbol, contractType string) error { - var resp interface{} - - if err := o.CheckSymbol(symbol); err != nil { - return err - } - if err := o.CheckContractType(contractType); err != nil { - return err - } - - values := url.Values{} - values.Set("symbol", symbol) - values.Set("contract_type", contractType) - - if err := o.SendAuthenticatedHTTPRequest(contractFuturePosition, values, &resp); err != nil { - return err - } - - userInfoMap := resp.(map[string]interface{}) - if code, ok := userInfoMap["error_code"]; ok { - return o.GetErrorCode(code) - } - return nil +// GetFuturesOrderDetails Get order details by order ID. +func (o *OKEX) GetFuturesOrderDetails(request okgroup.GetFuturesOrderDetailsRequest) (resp okgroup.GetFuturesOrderDetailsResponseData, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupOrders, request.InstrumentID, request.OrderID) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// PlaceContractOrders places orders -func (o *OKEX) PlaceContractOrders(symbol, contractType, position string, leverageRate int, price, amount float64, matchPrice bool) (float64, error) { - var resp interface{} - - if err := o.CheckSymbol(symbol); err != nil { - return 0, err - } - if err := o.CheckContractType(contractType); err != nil { - return 0, err - } - if err := o.CheckContractPosition(position); err != nil { - return 0, err - } - - values := url.Values{} - values.Set("symbol", symbol) - values.Set("contract_type", contractType) - values.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) - values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) - values.Set("type", position) - if matchPrice { - values.Set("match_price", "1") - } else { - values.Set("match_price", "0") - } - - if leverageRate != 10 && leverageRate != 20 { - return 0, errors.New("leverage rate can only be 10 or 20") - } - values.Set("lever_rate", strconv.FormatInt(int64(leverageRate), 10)) - - if err := o.SendAuthenticatedHTTPRequest(contractFutureTrade, values, &resp); err != nil { - return 0, err - } - - contractMap := resp.(map[string]interface{}) - if code, ok := contractMap["error_code"]; ok { - return 0, o.GetErrorCode(code) - } - - if orderID, ok := contractMap["order_id"]; ok { - return orderID.(float64), nil - } - - return 0, errors.New("orderID returned nil") +// GetFuturesTransactionDetails Get details of the recent filled orders. Cursor pagination is used. +// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKEX) GetFuturesTransactionDetails(request okgroup.GetFuturesTransactionDetailsRequest) (resp []okgroup.GetFuturesTransactionDetailsResponse, _ error) { + requestURL := fmt.Sprintf("%v%v", okgroup.OKGroupGetSpotTransactionDetails, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// GetContractFuturesTradeHistory returns OKEX Contract Trade History (Not for Personal) -func (o *OKEX) GetContractFuturesTradeHistory(symbol, date string, since int) error { - var resp interface{} - - if err := o.CheckSymbol(symbol); err != nil { - return err - } - - values := url.Values{} - values.Set("symbol", symbol) - values.Set("date", date) - values.Set("since", strconv.FormatInt(int64(since), 10)) - - if err := o.SendAuthenticatedHTTPRequest(contractFutureTradeHistory, values, &resp); err != nil { - return err - } - - respMap := resp.(map[string]interface{}) - if code, ok := respMap["error_code"]; ok { - return o.GetErrorCode(code) - } - return nil +// GetFuturesContractInformation Get market data. This endpoint provides the snapshots of market data and can be used without verifications. +func (o *OKEX) GetFuturesContractInformation() (resp []okgroup.GetFuturesContractInformationResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, okgroup.OKGroupInstruments, nil, &resp, false) } -// GetTokenOrders returns details for a single orderID or all open orders when orderID == -1 -func (o *OKEX) GetTokenOrders(symbol string, orderID int64) (TokenOrdersResponse, error) { - var resp TokenOrdersResponse - values := url.Values{} - values.Set("symbol", symbol) - values.Set("order_id", strconv.FormatInt(orderID, 10)) - return resp, o.SendAuthenticatedHTTPRequest(contractFutureTradeHistory, values, &resp) +// GetFuturesOrderBook List all contracts. This request does not support pagination. The full list will be returned for a request. +func (o *OKEX) GetFuturesOrderBook(request okgroup.GetFuturesOrderBookRequest) (resp okgroup.GetFuturesOrderBookResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupGetSpotOrderBook, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// GetUserInfo returns the user info -func (o *OKEX) GetUserInfo() (SpotUserInfo, error) { - var resp SpotUserInfo - return resp, o.SendAuthenticatedHTTPRequest(spotUserInfo, url.Values{}, &resp) +// GetAllFuturesTokenInfo Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all contracts. +func (o *OKEX) GetAllFuturesTokenInfo() (resp []okgroup.GetFuturesTokenInfoResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupInstruments, okgroup.OKGroupTicker) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// SpotNewOrder creates a new spot order -func (o *OKEX) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) { - type response struct { - Result bool `json:"result"` - OrderID int64 `json:"order_id"` - } - - var res response - params := url.Values{} - params.Set("symbol", arg.Symbol) - params.Set("type", string(arg.Type)) - params.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64)) - params.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64)) - - err := o.SendAuthenticatedHTTPRequest(spotTrade, params, &res) - if err != nil { - return res.OrderID, err - } - - return res.OrderID, nil +// GetFuturesTokenInfoForCurrency Get the last traded price, best bid/ask price, 24 hour trading volume and more info of a contract. +func (o *OKEX) GetFuturesTokenInfoForCurrency(instrumentID string) (resp okgroup.GetFuturesTokenInfoResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okgroup.OKGroupTicker) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// SpotCancelOrder cancels a spot order -// symbol such as ltc_btc -// orderID orderID -// returns orderID or an error -func (o *OKEX) SpotCancelOrder(symbol string, argOrderID int64) (int64, error) { - var res = struct { - Result bool `json:"result"` - OrderID string `json:"order_id"` - ErrorCode int `json:"error_code"` - }{} - - params := url.Values{} - params.Set("symbol", symbol) - params.Set("order_id", strconv.FormatInt(argOrderID, 10)) - var returnOrderID int64 - - err := o.SendAuthenticatedHTTPRequest(spotCancelTrade+".do", params, &res) - if err != nil { - return returnOrderID, err - } - - if res.ErrorCode != 0 { - return returnOrderID, fmt.Errorf("failed to cancel order. code: %d err: %s", - res.ErrorCode, - o.ErrorCodes[strconv.Itoa(res.ErrorCode)], - ) - } - - returnOrderID, _ = common.Int64FromString(res.OrderID) - return returnOrderID, nil +// GetFuturesFilledOrder Get the recent 300 transactions of all contracts. Pagination is not supported here. +// The whole book will be returned for one request. WebSocket is recommended here. +func (o *OKEX) GetFuturesFilledOrder(request okgroup.GetFuturesFilledOrderRequest) (resp []okgroup.GetFuturesFilledOrdersResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupTrades, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// GetLatestSpotPrice returns latest spot price of symbol -// -// symbol: string of currency pair -func (o *OKEX) GetLatestSpotPrice(symbol string) (float64, error) { - spotPrice, err := o.GetSpotTicker(symbol) - - if err != nil { - return 0, err - } - - return spotPrice.Ticker.Last, nil +// GetFuturesMarketData Get the charts of the trading pairs. Charts are returned in grouped buckets based on requested granularity. +func (o *OKEX) GetFuturesMarketData(request okgroup.GetFuturesMarketDateRequest) (resp okgroup.GetFuturesMarketDataResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupGetSpotMarketData, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// GetSpotTicker returns Price Ticker -func (o *OKEX) GetSpotTicker(symbol string) (SpotPrice, error) { - var resp SpotPrice - - values := url.Values{} - values.Set("symbol", symbol) - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, "ticker", values.Encode()) - - err := o.SendHTTPRequest(path, &resp) - if err != nil { - return resp, err - } - - if resp.Error != nil { - return resp, o.GetErrorCode(resp.Error.(float64)) - } - return resp, nil +// GetFuturesHoldAmount Get the number of futures with hold. +func (o *OKEX) GetFuturesHoldAmount(instrumentID string) (resp okgroup.GetFuturesHoldAmountResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupAccounts, instrumentID, okGroupFutureHolds) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) } -// GetSpotMarketDepth returns Market Depth -func (o *OKEX) GetSpotMarketDepth(asd ActualSpotDepthRequestParams) (ActualSpotDepth, error) { - resp := SpotDepth{} - fullDepth := ActualSpotDepth{} - - values := url.Values{} - values.Set("symbol", asd.Symbol) - values.Set("size", fmt.Sprintf("%d", asd.Size)) - - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, "depth", values.Encode()) - - err := o.SendHTTPRequest(path, &resp) - if err != nil { - return fullDepth, err - } - - if !resp.Result { - if resp.Error != nil { - return fullDepth, o.GetErrorCode(resp.Error) - } - } - - for _, ask := range resp.Asks { - var askdepth struct { - Price float64 - Volume float64 - } - for i, depth := range ask.([]interface{}) { - if i == 0 { - askdepth.Price = depth.(float64) - } - if i == 1 { - askdepth.Volume = depth.(float64) - } - } - fullDepth.Asks = append(fullDepth.Asks, askdepth) - } - - for _, bid := range resp.Bids { - var bidDepth struct { - Price float64 - Volume float64 - } - for i, depth := range bid.([]interface{}) { - if i == 0 { - bidDepth.Price = depth.(float64) - } - if i == 1 { - bidDepth.Volume = depth.(float64) - } - } - fullDepth.Bids = append(fullDepth.Bids, bidDepth) - } - - return fullDepth, nil +// GetFuturesIndices Get Indices of tokens. This is a public endpoint, no identity verification is needed. +func (o *OKEX) GetFuturesIndices(instrumentID string) (resp okgroup.GetFuturesIndicesResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okGroupIndices) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, false) } -// GetSpotRecentTrades returns recent trades -func (o *OKEX) GetSpotRecentTrades(ast ActualSpotTradeHistoryRequestParams) ([]ActualSpotTradeHistory, error) { - actualTradeHistory := []ActualSpotTradeHistory{} - var resp interface{} - - values := url.Values{} - values.Set("symbol", ast.Symbol) - values.Set("since", fmt.Sprintf("%d", ast.Since)) - - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, "trades", values.Encode()) - - err := o.SendHTTPRequest(path, &resp) - if err != nil { - return actualTradeHistory, err - } - - if reflect.TypeOf(resp).String() == returnTypeOne { - errorMap := resp.(map[string]interface{}) - return actualTradeHistory, o.GetErrorCode(errorMap["error_code"].(float64)) - } - - for _, tradeHistory := range resp.([]interface{}) { - quickHistory := ActualSpotTradeHistory{} - tradeHistoryM := tradeHistory.(map[string]interface{}) - quickHistory.Date = tradeHistoryM["date"].(float64) - quickHistory.DateInMS = tradeHistoryM["date_ms"].(float64) - quickHistory.Amount = tradeHistoryM["amount"].(float64) - quickHistory.Price = tradeHistoryM["price"].(float64) - quickHistory.Type = tradeHistoryM["type"].(string) - quickHistory.TID = tradeHistoryM["tid"].(float64) - actualTradeHistory = append(actualTradeHistory, quickHistory) - } - return actualTradeHistory, nil +// GetFuturesExchangeRates Get the fiat exchange rates. This is a public endpoint, no identity verification is needed. +func (o *OKEX) GetFuturesExchangeRates() (resp okgroup.GetFuturesExchangeRatesResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, okGroupRate, nil, &resp, false) } -// GetSpotKline returns candlestick data -func (o *OKEX) GetSpotKline(arg KlinesRequestParams) ([]CandleStickData, error) { - var candleData []CandleStickData - - values := url.Values{} - values.Set("symbol", arg.Symbol) - values.Set("type", string(arg.Type)) - if arg.Size != 0 { - values.Set("size", strconv.FormatInt(int64(arg.Size), 10)) - } - if arg.Since != 0 { - values.Set("since", strconv.FormatInt(arg.Since, 10)) - } - - path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, spotKline, values.Encode()) - var resp interface{} - - if err := o.SendHTTPRequest(path, &resp); err != nil { - return candleData, err - } - - if reflect.TypeOf(resp).String() == returnTypeOne { - errorMap := resp.(map[string]interface{}) - return candleData, o.GetErrorCode(errorMap["error_code"].(float64)) - } - - for _, candleStickData := range resp.([]interface{}) { - var quickCandle CandleStickData - - for i, datum := range candleStickData.([]interface{}) { - switch i { - case 0: - quickCandle.Timestamp = datum.(float64) - case 1: - quickCandle.Open, _ = strconv.ParseFloat(datum.(string), 64) - case 2: - quickCandle.High, _ = strconv.ParseFloat(datum.(string), 64) - case 3: - quickCandle.Low, _ = strconv.ParseFloat(datum.(string), 64) - case 4: - quickCandle.Close, _ = strconv.ParseFloat(datum.(string), 64) - case 5: - quickCandle.Volume, _ = strconv.ParseFloat(datum.(string), 64) - case 6: - quickCandle.Amount, _ = strconv.ParseFloat(datum.(string), 64) - default: - return candleData, errors.New("incoming data out of range") - } - } - candleData = append(candleData, quickCandle) - } - - return candleData, nil +// GetFuturesEstimatedDeliveryPrice the estimated delivery price. It is available 3 hours before delivery. +// This is a public endpoint, no identity verification is needed. +func (o *OKEX) GetFuturesEstimatedDeliveryPrice(instrumentID string) (resp okgroup.GetFuturesEstimatedDeliveryPriceResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okGroupEsimtatedPrice) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, false) } -// GetErrorCode finds the associated error code and returns its corresponding -// string -func (o *OKEX) GetErrorCode(code interface{}) error { - var assertedCode string - - switch reflect.TypeOf(code).String() { - case "float64": - assertedCode = strconv.FormatFloat(code.(float64), 'f', -1, 64) - case "string": - assertedCode = code.(string) - default: - return errors.New("unusual type returned") - } - - if i, ok := o.ErrorCodes[assertedCode]; ok { - return i - } - return errors.New("unable to find SPOT error code") +// GetFuturesOpenInterests Get the open interest of a contract. This is a public endpoint, no identity verification is needed. +func (o *OKEX) GetFuturesOpenInterests(instrumentID string) (resp okgroup.GetFuturesOpenInterestsResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okGroupOpenInterest) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, false) } -// SendHTTPRequest sends an unauthenticated HTTP request -func (o *OKEX) SendHTTPRequest(path string, result interface{}) error { - return o.SendPayload(http.MethodGet, path, nil, nil, result, false, o.Verbose) +// GetFuturesCurrentPriceLimit The maximum buying price and the minimum selling price of the contract. +// This is a public endpoint, no identity verification is needed. +func (o *OKEX) GetFuturesCurrentPriceLimit(instrumentID string) (resp okgroup.GetFuturesCurrentPriceLimitResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okgroup.OKGroupPriceLimit) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, false) } -// SendAuthenticatedHTTPRequest sends an authenticated http request to a desired -// path -func (o *OKEX) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) { - if !o.AuthenticatedAPISupport { - return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, o.Name) - } - - values.Set("api_key", o.APIKey) - hasher := common.GetMD5([]byte(values.Encode() + "&secret_key=" + o.APISecret)) - values.Set("sign", strings.ToUpper(common.HexEncodeToString(hasher))) - - encoded := values.Encode() - path := o.APIUrl + apiVersion + method - - if o.Verbose { - log.Debugf("Sending POST request to %s with params %s\n", path, encoded) - } - - headers := make(map[string]string) - headers["Content-Type"] = "application/x-www-form-urlencoded" - - var intermediary json.RawMessage - - errCap := struct { - Result bool `json:"result"` - Error int64 `json:"error_code"` - }{} - - err = o.SendPayload(http.MethodPost, path, headers, strings.NewReader(encoded), &intermediary, true, o.Verbose) - if err != nil { - return err - } - - err = common.JSONDecode(intermediary, &errCap) - if err == nil { - if !errCap.Result { - return fmt.Errorf("sendAuthenticatedHTTPRequest error - %s", - o.ErrorCodes[strconv.FormatInt(errCap.Error, 10)]) - } - } - - return common.JSONDecode(intermediary, result) +// GetFuturesCurrentMarkPrice The maximum buying price and the minimum selling price of the contract. +// This is a public endpoint, no identity verification is needed. +func (o *OKEX) GetFuturesCurrentMarkPrice(instrumentID string) (resp okgroup.GetFuturesCurrentMarkPriceResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okgroup.OKGroupMarkPrice) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, false) } -// SetErrorDefaults sets the full error default list -func (o *OKEX) SetErrorDefaults() { - o.ErrorCodes = map[string]error{ - // Spot Errors - "10000": errors.New("required field, can not be null"), - "10001": errors.New("request frequency too high to exceed the limit allowed"), - "10002": errors.New("system error"), - "10004": errors.New("request failed - Your API key might need to be recreated"), - "10005": errors.New("'secretKey' does not exist"), - "10006": errors.New("'api_key' does not exist"), - "10007": errors.New("signature does not match"), - "10008": errors.New("illegal parameter"), - "10009": errors.New("order does not exist"), - "10010": errors.New("insufficient funds"), - "10011": errors.New("amount too low"), - "10012": errors.New("only btc_usd ltc_usd supported"), - "10013": errors.New("only support https request"), - "10014": errors.New("order price must be between 0 and 1,000,000"), - "10015": errors.New("order price differs from current market price too much"), - "10016": errors.New("insufficient coins balance"), - "10017": errors.New("api authorization error"), - "10018": errors.New("borrow amount less than lower limit [usd:100,btc:0.1,ltc:1]"), - "10019": errors.New("loan agreement not checked"), - "10020": errors.New("rate cannot exceed 1%"), - "10021": errors.New("rate cannot less than 0.01%"), - "10023": errors.New("fail to get latest ticker"), - "10024": errors.New("balance not sufficient"), - "10025": errors.New("quota is full, cannot borrow temporarily"), - "10026": errors.New("loan (including reserved loan) and margin cannot be withdrawn"), - "10027": errors.New("cannot withdraw within 24 hrs of authentication information modification"), - "10028": errors.New("withdrawal amount exceeds daily limit"), - "10029": errors.New("account has unpaid loan, please cancel/pay off the loan before withdraw"), - "10031": errors.New("deposits can only be withdrawn after 6 confirmations"), - "10032": errors.New("please enabled phone/google authenticator"), - "10033": errors.New("fee higher than maximum network transaction fee"), - "10034": errors.New("fee lower than minimum network transaction fee"), - "10035": errors.New("insufficient BTC/LTC"), - "10036": errors.New("withdrawal amount too low"), - "10037": errors.New("trade password not set"), - "10040": errors.New("withdrawal cancellation fails"), - "10041": errors.New("withdrawal address not exsit or approved"), - "10042": errors.New("admin password error"), - "10043": errors.New("account equity error, withdrawal failure"), - "10044": errors.New("fail to cancel borrowing order"), - "10047": errors.New("this function is disabled for sub-account"), - "10048": errors.New("withdrawal information does not exist"), - "10049": errors.New("user can not have more than 50 unfilled small orders (amount<0.15BTC)"), - "10050": errors.New("can't cancel more than once"), - "10051": errors.New("order completed transaction"), - "10052": errors.New("not allowed to withdraw"), - "10064": errors.New("after a USD deposit, that portion of assets will not be withdrawable for the next 48 hours"), - "10100": errors.New("user account frozen"), - "10101": errors.New("order type is wrong"), - "10102": errors.New("incorrect ID"), - "10103": errors.New("the private otc order's key incorrect"), - "10216": errors.New("non-available API"), - "1002": errors.New("the transaction amount exceed the balance"), - "1003": errors.New("the transaction amount is less than the minimum requirement"), - "1004": errors.New("the transaction amount is less than 0"), - "1007": errors.New("no trading market information"), - "1008": errors.New("no latest market information"), - "1009": errors.New("no order"), - "1010": errors.New("different user of the cancelled order and the original order"), - "1011": errors.New("no documented user"), - "1013": errors.New("no order type"), - "1014": errors.New("no login"), - "1015": errors.New("no market depth information"), - "1017": errors.New("date error"), - "1018": errors.New("order failed"), - "1019": errors.New("undo order failed"), - "1024": errors.New("currency does not exist"), - "1025": errors.New("no chart type"), - "1026": errors.New("no base currency quantity"), - "1027": errors.New("incorrect parameter may exceeded limits"), - "1028": errors.New("reserved decimal failed"), - "1029": errors.New("preparing"), - "1030": errors.New("account has margin and futures, transactions can not be processed"), - "1031": errors.New("insufficient Transferring Balance"), - "1032": errors.New("transferring Not Allowed"), - "1035": errors.New("password incorrect"), - "1036": errors.New("google Verification code Invalid"), - "1037": errors.New("google Verification code incorrect"), - "1038": errors.New("google Verification replicated"), - "1039": errors.New("message Verification Input exceed the limit"), - "1040": errors.New("message Verification invalid"), - "1041": errors.New("message Verification incorrect"), - "1042": errors.New("wrong Google Verification Input exceed the limit"), - "1043": errors.New("login password cannot be same as the trading password"), - "1044": errors.New("old password incorrect"), - "1045": errors.New("2nd Verification Needed"), - "1046": errors.New("please input old password"), - "1048": errors.New("account Blocked"), - "1201": errors.New("account Deleted at 00: 00"), - "1202": errors.New("account Not Exist"), - "1203": errors.New("insufficient Balance"), - "1204": errors.New("invalid currency"), - "1205": errors.New("invalid Account"), - "1206": errors.New("cash Withdrawal Blocked"), - "1207": errors.New("transfer Not Support"), - "1208": errors.New("no designated account"), - "1209": errors.New("invalid api"), - "1216": errors.New("market order temporarily suspended. Please send limit order"), - "1217": errors.New("order was sent at ±5% of the current market price. Please resend"), - "1218": errors.New("place order failed. Please try again later"), - // Errors for both - "HTTP ERROR CODE 403": errors.New("too many requests, IP is shielded"), - "Request Timed Out": errors.New("too many requests, IP is shielded"), - // contract errors - "405": errors.New("method not allowed"), - "20001": errors.New("user does not exist"), - "20002": errors.New("account frozen"), - "20003": errors.New("account frozen due to liquidation"), - "20004": errors.New("contract account frozen"), - "20005": errors.New("user contract account does not exist"), - "20006": errors.New("required field missing"), - "20007": errors.New("illegal parameter"), - "20008": errors.New("contract account balance is too low"), - "20009": errors.New("contract status error"), - "20010": errors.New("risk rate ratio does not exist"), - "20011": errors.New("risk rate lower than 90%/80% before opening BTC position with 10x/20x leverage. or risk rate lower than 80%/60% before opening LTC position with 10x/20x leverage"), - "20012": errors.New("risk rate lower than 90%/80% after opening BTC position with 10x/20x leverage. or risk rate lower than 80%/60% after opening LTC position with 10x/20x leverage"), - "20013": errors.New("temporally no counter party price"), - "20014": errors.New("system error"), - "20015": errors.New("order does not exist"), - "20016": errors.New("close amount bigger than your open positions"), - "20017": errors.New("not authorized/illegal operation"), - "20018": errors.New("order price cannot be more than 103% or less than 97% of the previous minute price"), - "20019": errors.New("ip restricted from accessing the resource"), - "20020": errors.New("secretKey does not exist"), - "20021": errors.New("index information does not exist"), - "20022": errors.New("wrong API interface (Cross margin mode shall call cross margin API, fixed margin mode shall call fixed margin API)"), - "20023": errors.New("account in fixed-margin mode"), - "20024": errors.New("signature does not match"), - "20025": errors.New("leverage rate error"), - "20026": errors.New("api permission error"), - "20027": errors.New("no transaction record"), - "20028": errors.New("no such contract"), - "20029": errors.New("amount is large than available funds"), - "20030": errors.New("account still has debts"), - "20038": errors.New("due to regulation, this function is not available in the country/region your currently reside in"), - "20049": errors.New("request frequency too high"), - } +// GetFuturesForceLiquidatedOrders Get force liquidated orders. This is a public endpoint, no identity verification is needed. +func (o *OKEX) GetFuturesForceLiquidatedOrders(request okgroup.GetFuturesForceLiquidatedOrdersRequest) (resp []okgroup.GetFuturesForceLiquidatedOrdersResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupLiquidation, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, false) } -// SetCheckVarDefaults sets main variables that will be used in requests because -// api does not return an error if there are misspellings in strings. So better -// to check on this, this end. -func (o *OKEX) SetCheckVarDefaults() { - o.ContractTypes = []string{"this_week", "next_week", "quarter"} - o.CurrencyPairs = []string{"btc_usd", "ltc_usd", "eth_usd", "etc_usd", "bch_usd"} - o.Types = []string{"1min", "3min", "5min", "15min", "30min", "1day", "3day", - "1week", "1hour", "2hour", "4hour", "6hour", "12hour"} - o.ContractPosition = []string{"1", "2", "3", "4"} +// GetFuturesTagPrice Get the tag price. This is a public endpoint, no identity verification is needed. +func (o *OKEX) GetFuturesTagPrice(instrumentID string) (resp okgroup.GetFuturesTagPriceResponse, _ error) { + // OKEX documentation is missing for this endpoint. Guessing "tag_price" for the URL results in 404 + return okgroup.GetFuturesTagPriceResponse{}, common.ErrNotYetImplemented } -// CheckContractPosition checks to see if the string is a valid position for okex -func (o *OKEX) CheckContractPosition(position string) error { - if !common.StringDataCompare(o.ContractPosition, position) { - return errors.New("invalid position string - e.g. 1 = open long position, 2 = open short position, 3 = liquidate long position, 4 = liquidate short position") - } - return nil +// GetSwapPostions Get the information of all holding positions in swap trading. +// Due to high energy consumption, you are advised to capture data with the "Swap Account of a Currency" API instead. +func (o *OKEX) GetSwapPostions() (resp []okgroup.GetSwapPostionsResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, okGroupFuturePosition, nil, &resp, true) } -// CheckSymbol checks to see if the string is a valid symbol for okex -func (o *OKEX) CheckSymbol(symbol string) error { - if !common.StringDataCompare(o.CurrencyPairs, symbol) { - return errors.New("invalid symbol string") - } - return nil +// GetSwapPostionsForContract Get the information of holding positions of a contract. +func (o *OKEX) GetSwapPostionsForContract(instrumentID string) (resp okgroup.GetSwapPostionsResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", instrumentID, okGroupFuturePosition) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) } -// CheckContractType checks to see if the string is a correct asset -func (o *OKEX) CheckContractType(contractType string) error { - if !common.StringDataCompare(o.ContractTypes, contractType) { - return errors.New("invalid contract type string") - } - return nil +// GetSwapAccountOfAllCurrency Get the perpetual swap account info of a token. +// Margin ratio set as 10,000 when users have no open position. +func (o *OKEX) GetSwapAccountOfAllCurrency() (resp okgroup.GetSwapAccountOfAllCurrencyResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, okgroup.OKGroupAccounts, nil, &resp, true) } -// CheckType checks to see if the string is a correct type -func (o *OKEX) CheckType(typeInput string) error { - if !common.StringDataCompare(o.Types, typeInput) { - return errors.New("invalid type string") - } - return nil +// GetSwapAccountSettingsOfAContract Get leverage level and margin mode of a contract. +func (o *OKEX) GetSwapAccountSettingsOfAContract(instrumentID string) (resp okgroup.GetSwapAccountSettingsOfAContractResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupAccounts, instrumentID, okGroupSettings) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) } -// GetFee returns an estimate of fee based on type of transaction -func (o *OKEX) GetFee(feeBuilder exchange.FeeBuilder) (float64, error) { - var fee float64 - switch feeBuilder.FeeType { - case exchange.CryptocurrencyTradeFee: - fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount, feeBuilder.IsMaker) - case exchange.CryptocurrencyWithdrawalFee: - fee = getWithdrawalFee(feeBuilder.FirstCurrency) - } - if fee < 0 { - fee = 0 - } - - return fee, nil +// SetSwapLeverageLevelOfAContract Setting the leverage level of a contract +// TODO this returns invalid parameters, but matches spec. Unsure how to fix +func (o *OKEX) SetSwapLeverageLevelOfAContract(request okgroup.SetSwapLeverageLevelOfAContractRequest) (resp okgroup.SetSwapLeverageLevelOfAContractResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupAccounts, request.InstrumentID, okGroupFutureLeverage) + request.InstrumentID = "" + return resp, o.SendHTTPRequest(http.MethodPost, okGroupSwapSubsection, requestURL, request, &resp, true) } -func calculateTradingFee(purchasePrice, amount float64, isMaker bool) (fee float64) { - // TODO volume based fees - if isMaker { - fee = 0.001 - } else { - fee = 0.0015 - } - return fee * amount * purchasePrice +// GetSwapBillDetails Shows the account’s historical coin in flow and out flow. +// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKEX) GetSwapBillDetails(request okgroup.GetSpotBillDetailsForCurrencyRequest) (resp []okgroup.GetSwapBillDetailsResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupAccounts, request.Currency, okgroup.OKGroupLedger, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) } -func getWithdrawalFee(currency string) float64 { - return WithdrawalFees[currency] +// PlaceSwapOrder OKEx perpetual swap trading only supports limit orders,USD as quote currency for orders. +// You can place an order only if you have enough funds. Once your order is placed, the amount will be put on hold in the order lifecycle. +// The assets and amount on hold depends on the order's specific type and parameters. +func (o *OKEX) PlaceSwapOrder(request okgroup.PlaceSwapOrderRequest) (resp okgroup.PlaceSwapOrderResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodPost, okGroupSwapSubsection, okGroupFutureOrder, request, &resp, true) } -// GetBalance returns the full balance across all wallets -func (o *OKEX) GetBalance() ([]FullBalance, error) { - var resp Balance - var balances []FullBalance - - err := o.SendAuthenticatedHTTPRequest(myWalletInfo, url.Values{}, &resp) - if err != nil { - return balances, err - } - - for key, available := range resp.Info.Funds.Free { - free, err := strconv.ParseFloat(available, 64) - if err != nil { - return balances, err - } - - inUse, ok := resp.Info.Funds.Holds[key] - if !ok { - return balances, fmt.Errorf("hold currency %s not found in map", key) - } - - hold, err := strconv.ParseFloat(inUse, 64) - if err != nil { - return balances, err - } - - balances = append(balances, FullBalance{ - Currency: key, - Available: free, - Hold: hold, - }) - } - - return balances, nil +// PlaceMultipleSwapOrders Batch contract placing order operation. +func (o *OKEX) PlaceMultipleSwapOrders(request okgroup.PlaceMultipleSwapOrdersRequest) (resp okgroup.PlaceMultipleSwapOrdersResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodPost, okGroupSwapSubsection, okgroup.OKGroupOrders, request, &resp, true) } -// Withdrawal withdraws a cryptocurrency to a supplied address -func (o *OKEX) Withdrawal(symbol string, fee float64, tradePWD, address string, amount float64) (int, error) { - v := url.Values{} - v.Set("symbol", symbol) - - if fee != 0 { - v.Set("chargefee", strconv.FormatFloat(fee, 'f', -1, 64)) - } - v.Set("trade_pwd", tradePWD) - v.Set("withdraw_address", address) - v.Set("withdraw_amount", strconv.FormatFloat(amount, 'f', -1, 64)) - v.Set("target", "address") - resp := WithdrawalResponse{} - - err := o.SendAuthenticatedHTTPRequest(spotWithdraw, v, &resp) - if err != nil { - return 0, err - } - - if !resp.Result { - return 0, errors.New("unable to process withdrawal request") - } - - return resp.WithdrawID, nil +// CancelSwapOrder Cancelling an unfilled order +func (o *OKEX) CancelSwapOrder(request okgroup.CancelSwapOrderRequest) (resp okgroup.CancelSwapOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupCancelOrder, request.InstrumentID, request.OrderID) + return resp, o.SendHTTPRequest(http.MethodPost, okGroupSwapSubsection, requestURL, nil, &resp, true) } -// GetOrderInformation withdraws a cryptocurrency to a supplied address -func (o *OKEX) GetOrderInformation(orderID int64, symbol string) ([]OrderInfo, error) { - type Response struct { - Result bool `json:"result"` - Orders []OrderInfo `json:"orders"` - } - - v := url.Values{} - v.Set("symbol", symbol) - v.Set("order_id", strconv.FormatInt(orderID, 10)) - result := Response{} - - err := o.SendAuthenticatedHTTPRequest(spotOrderInfo, v, &result) - if err != nil { - return nil, err - } - - if !result.Result { - return nil, errors.New("unable to retrieve order info") - } - - return result.Orders, nil +// CancelMultipleSwapOrders With best effort, cancel all open orders. +func (o *OKEX) CancelMultipleSwapOrders(request okgroup.CancelMultipleSwapOrdersRequest) (resp okgroup.CancelMultipleSwapOrdersResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupCancelBatchOrders, request.InstrumentID) + request.InstrumentID = "" + return resp, o.SendHTTPRequest(http.MethodPost, okGroupSwapSubsection, requestURL, request, &resp, true) } -// GetOrderHistoryForCurrency returns a history of orders -func (o *OKEX) GetOrderHistoryForCurrency(pageLength, currentPage, status int64, symbol string) (OrderHistory, error) { - v := url.Values{} - v.Set("symbol", symbol) - v.Set("status", strconv.FormatInt(status, 10)) - v.Set("current_page", strconv.FormatInt(currentPage, 10)) - v.Set("page_length", strconv.FormatInt(pageLength, 10)) - result := OrderHistory{} - return result, o.SendAuthenticatedHTTPRequest(spotOrderHistory, v, &result) +// GetSwapOrderList List your orders. Cursor pagination is used. +// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKEX) GetSwapOrderList(request okgroup.GetSwapOrderListRequest) (resp okgroup.GetSwapOrderListResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v%v", okgroup.OKGroupOrders, request.InstrumentID, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapOrderDetails Get order details by order ID. +func (o *OKEX) GetSwapOrderDetails(request okgroup.GetSwapOrderDetailsRequest) (resp okgroup.GetSwapOrderListResponseData, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupOrders, request.InstrumentID, request.OrderID) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapTransactionDetails Get details of the recent filled orders +func (o *OKEX) GetSwapTransactionDetails(request okgroup.GetSwapTransactionDetailsRequest) (resp []okgroup.GetSwapTransactionDetailsResponse, _ error) { + requestURL := fmt.Sprintf("%v%v", okgroup.OKGroupGetSpotTransactionDetails, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapContractInformation Get market data. +func (o *OKEX) GetSwapContractInformation() (resp []okgroup.GetSwapContractInformationResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, okgroup.OKGroupInstruments, nil, &resp, true) +} + +// GetSwapOrderBook Get the charts of the trading pairs. +func (o *OKEX) GetSwapOrderBook(request okgroup.GetSwapOrderBookRequest) (resp okgroup.GetSwapOrderBookResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okGroupDepth, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetAllSwapTokensInformation Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all contracts. +func (o *OKEX) GetAllSwapTokensInformation() (resp []okgroup.GetAllSwapTokensInformationResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupInstruments, okgroup.OKGroupTicker) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapTokensInformationForCurrency Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all contracts. +func (o *OKEX) GetSwapTokensInformationForCurrency(instrumentID string) (resp okgroup.GetAllSwapTokensInformationResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okgroup.OKGroupTicker) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapFilledOrdersData Get details of the recent filled orders +func (o *OKEX) GetSwapFilledOrdersData(request *okgroup.GetSwapFilledOrdersDataRequest) (resp []okgroup.GetSwapFilledOrdersDataResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupTrades, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapMarketData Get the charts of the trading pairs. +func (o *OKEX) GetSwapMarketData(request okgroup.GetSwapMarketDataRequest) (resp []okgroup.GetSwapMarketDataResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupGetSpotMarketData, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapIndeces Get Indices of tokens. +func (o *OKEX) GetSwapIndeces(instrumentID string) (resp okgroup.GetSwapIndecesResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okGroupIndices) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapExchangeRates Get the fiat exchange rates. +func (o *OKEX) GetSwapExchangeRates() (resp okgroup.GetSwapExchangeRatesResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, okGroupRate, nil, &resp, true) +} + +// GetSwapOpenInterest Get the open interest of a contract. +func (o *OKEX) GetSwapOpenInterest(instrumentID string) (resp okgroup.GetSwapExchangeRatesResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okGroupOpenInterest) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapCurrentPriceLimits Get the open interest of a contract. +func (o *OKEX) GetSwapCurrentPriceLimits(instrumentID string) (resp okgroup.GetSwapCurrentPriceLimitsResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okgroup.OKGroupPriceLimit) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapForceLiquidatedOrders Get force liquidated orders. +func (o *OKEX) GetSwapForceLiquidatedOrders(request okgroup.GetSwapForceLiquidatedOrdersRequest) (resp []okgroup.GetSwapForceLiquidatedOrdersResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupLiquidation, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, false) +} + +// GetSwapOnHoldAmountForOpenOrders Get On Hold Amount for Open Orders. +func (o *OKEX) GetSwapOnHoldAmountForOpenOrders(instrumentID string) (resp okgroup.GetSwapOnHoldAmountForOpenOrdersResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupAccounts, instrumentID, okGroupFutureHolds) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapNextSettlementTime Get the time of next settlement. +func (o *OKEX) GetSwapNextSettlementTime(instrumentID string) (resp okgroup.GetSwapNextSettlementTimeResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okGroupFundingTime) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapMarkPrice Get the time of next settlement. +func (o *OKEX) GetSwapMarkPrice(instrumentID string) (resp okgroup.GetSwapMarkPriceResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okgroup.OKGroupMarkPrice) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, true) +} + +// GetSwapFundingRateHistory Get Funding Rate History. +func (o *OKEX) GetSwapFundingRateHistory(request okgroup.GetSwapFundingRateHistoryRequest) (resp []okgroup.GetSwapFundingRateHistoryResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okGroupHistoricalFundingRate, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, false) +} + +// GetETT List the assets in ETT account. Get information such as balance, amount on hold/ available. +func (o *OKEX) GetETT() (resp []okgroup.GetETTResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupETTSubsection, okgroup.OKGroupAccounts, nil, &resp, true) +} + +// GetETTAccountInformationForCurrency Getting the balance, amount available/on hold of a token in ETT account. +func (o *OKEX) GetETTAccountInformationForCurrency(currency string) (resp okgroup.GetETTResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupAccounts, currency) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupETTSubsection, requestURL, nil, &resp, true) +} + +// GetETTBillsDetails Bills details. All paginated requests return the latest information (newest) +// as the first page sorted by newest (in chronological time) first +func (o *OKEX) GetETTBillsDetails(currency string) (resp []okgroup.GetETTBillsDetailsResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupAccounts, currency, okgroup.OKGroupLedger) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupETTSubsection, requestURL, nil, &resp, true) +} + +// PlaceETTOrder You can place subscription or redemption orders under ETT trading. +// You can place an order only if you have enough funds. Once your order is placed, +// the amount will be put on hold in the order lifecycle. +// The assets and amount on hold depends on the order's specific type and parameters. +func (o *OKEX) PlaceETTOrder(request okgroup.PlaceETTOrderRequest) (resp okgroup.PlaceETTOrderResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodPost, okGroupETTSubsection, okgroup.OKGroupOrders, nil, &resp, true) +} + +// CancelETTOrder Cancel an unfilled order. +func (o *OKEX) CancelETTOrder(orderID string) (resp okgroup.PlaceETTOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupOrders, orderID) + return resp, o.SendHTTPRequest(http.MethodDelete, okGroupETTSubsection, requestURL, nil, &resp, true) +} + +// GetETTOrderList List your orders. Cursor pagination is used. All paginated requests return the latest information +// (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKEX) GetETTOrderList(request okgroup.GetETTOrderListRequest) (resp []okgroup.GetETTOrderListResponse, _ error) { + requestURL := fmt.Sprintf("%v%v", okgroup.OKGroupOrders, okgroup.FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupETTSubsection, requestURL, nil, &resp, true) +} + +// GetETTOrderDetails Get order details by order ID. +func (o *OKEX) GetETTOrderDetails(orderID string) (resp okgroup.GetETTOrderListResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupOrders, orderID) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupETTSubsection, requestURL, nil, &resp, true) +} + +// GetETTConstituents Get ETT Constituents.This is a public endpoint, no identity verification is needed. +func (o *OKEX) GetETTConstituents(ett string) (resp okgroup.GetETTConstituentsResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", okGroupConstituents, ett) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupETTSubsection, requestURL, nil, &resp, false) +} + +// GetETTSettlementPriceHistory Get ETT settlement price history. This is a public endpoint, no identity verification is needed. +func (o *OKEX) GetETTSettlementPriceHistory(ett string) (resp []okgroup.GetETTSettlementPriceHistoryResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", okGroupDefinePrice, ett) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupETTSubsection, requestURL, nil, &resp, false) } diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index 2eb9efef..8792fc06 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -1,300 +1,1803 @@ package okex import ( + "fmt" + "strings" "testing" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/currency/symbol" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/okgroup" ) -var o OKEX - // Please supply you own test keys here for due diligence testing. const ( apiKey = "" apiSecret = "" + passphrase = "" + OKGroupExchange = "OKEX" canManipulateRealOrders = false ) +var testSetupRan bool +var o = OKEX{} +var spotCurrency = pair.NewCurrencyPairWithDelimiter(symbol.BTC, symbol.USDT, "-").Pair().Lower().String() + +// TestSetDefaults Sets standard default settings for running a test func TestSetDefaults(t *testing.T) { - o.SetDefaults() - if o.GetName() != "OKEX" { - t.Error("Test Failed - Bittrex - SetDefaults() error") + if o.Name != OKGroupExchange { + o.SetDefaults() + } + if o.GetName() != OKGroupExchange { + t.Errorf("Test Failed - %v - SetDefaults() error", OKGroupExchange) + } + TestSetup(t) +} + +// TestSetRealOrderDefaults Sets test defaults when test can impact real money/orders +func TestSetRealOrderDefaults(t *testing.T) { + TestSetDefaults(t) + if areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("Ensure canManipulateRealOrders is true and your API keys are set") } } +// TestSetup Sets defaults for test environment func TestSetup(t *testing.T) { + if testSetupRan { + return + } + if o.APIKey == apiKey && o.APISecret == apiSecret && + o.ClientID == passphrase && apiKey != "" && apiSecret != "" && passphrase != "" { + return + } + o.ExchangeName = OKGroupExchange cfg := config.GetConfig() cfg.LoadConfig("../../testdata/configtest.json") - okexConfig, err := cfg.GetExchangeConfig("OKEX") + + okexConfig, err := cfg.GetExchangeConfig(OKGroupExchange) if err != nil { - t.Error("Test Failed - Okex Setup() init error") + t.Fatalf("Test Failed - %v Setup() init error", OKGroupExchange) } + okexConfig.AuthenticatedAPISupport = true okexConfig.APIKey = apiKey okexConfig.APISecret = apiSecret - + okexConfig.ClientID = passphrase + okexConfig.WebsocketURL = o.WebsocketURL o.Setup(okexConfig) + testSetupRan = true } -func TestGetSpotInstruments(t *testing.T) { +func areTestAPIKeysSet() bool { + if o.APIKey != "" && o.APIKey != "Key" && + o.APISecret != "" && o.APISecret != "Secret" { + return true + } + return false +} + +func testStandardErrorHandling(t *testing.T, err error) { + if !areTestAPIKeysSet() && err == nil { + t.Errorf("Expecting an error when no keys are set") + } + if areTestAPIKeysSet() && err != nil { + t.Errorf("Encountered error: %v", err) + } +} + +// setupWSConnection Connect to WS, but pass back error so test can handle it if needed +func setupWSConnection() error { + if !o.Websocket.IsEnabled() { + err := o.WebsocketSetup(o.WsConnect, + o.Name, + true, + o.WebsocketURL, + o.WebsocketURL) + o.Websocket.DataHandler = make(chan interface{}, 500) + if err != nil { + return err + } + o.Websocket.SetWsStatusAndConnection(true) + } + return nil +} + +// disconnectFromWS disconnect to WS, but pass back error so test can handle it if needed +func disconnectFromWS() error { + return o.Websocket.Shutdown() +} + +// TestGetAccountCurrencies API endpoint test +func TestGetAccountCurrencies(t *testing.T) { + TestSetDefaults(t) t.Parallel() - _, err := o.GetSpotInstruments() + _, err := o.GetAccountCurrencies() + testStandardErrorHandling(t, err) +} + +// TestGetAccountWalletInformation API endpoint test +func TestGetAccountWalletInformation(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + resp, err := o.GetAccountWalletInformation("") + if areTestAPIKeysSet() { + if err != nil { + t.Error(err) + } + if len(resp) == 0 { + t.Error("No wallets returned") + } + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +// TestGetAccountWalletInformationForCurrency API endpoint test +func TestGetAccountWalletInformationForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + resp, err := o.GetAccountWalletInformation(symbol.BTC) + if areTestAPIKeysSet() { + if err != nil { + t.Error(err) + } + if len(resp) != 1 { + t.Errorf("Error receiving wallet information for currency: %v", symbol.BTC) + } + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +// TestTransferAccountFunds API endpoint test +func TestTransferAccountFunds(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.TransferAccountFundsRequest{ + Amount: 10, + Currency: symbol.BTC, + From: 6, + To: 1, + } + + _, err := o.TransferAccountFunds(request) + testStandardErrorHandling(t, err) +} + +// TestBaseWithdraw API endpoint test +func TestAccountWithdrawRequest(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.AccountWithdrawRequest{ + Amount: 10, + Currency: symbol.BTC, + TradePwd: "1234", + Destination: 4, + ToAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", + Fee: 1, + } + + _, err := o.AccountWithdraw(request) + testStandardErrorHandling(t, err) +} + +// TestGetAccountWithdrawalFee API endpoint test +func TestGetAccountWithdrawalFee(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + resp, err := o.GetAccountWithdrawalFee("") + if areTestAPIKeysSet() { + if err != nil { + t.Error(err) + } + if len(resp) == 0 { + t.Error("Expected fees") + } + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +// TestGetWithdrawalFeeForCurrency API endpoint test +func TestGetAccountWithdrawalFeeForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + resp, err := o.GetAccountWithdrawalFee(symbol.BTC) + if areTestAPIKeysSet() { + if err != nil { + t.Error(err) + } + if len(resp) != 1 { + t.Error("Expected fee for one currency") + } + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +// TestGetAccountWithdrawalHistory API endpoint test +func TestGetAccountWithdrawalHistory(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetAccountWithdrawalHistory("") + testStandardErrorHandling(t, err) +} + +// TestGetAccountWithdrawalHistoryForCurrency API endpoint test +func TestGetAccountWithdrawalHistoryForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetAccountWithdrawalHistory(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestGetAccountBillDetails API endpoint test +func TestGetAccountBillDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetAccountBillDetails(okgroup.GetAccountBillDetailsRequest{}) + testStandardErrorHandling(t, err) +} + +// TestGetAccountDepositAddressForCurrency API endpoint test +func TestGetAccountDepositAddressForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetAccountDepositAddressForCurrency(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestGetAccountDepositHistory API endpoint test +func TestGetAccountDepositHistory(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetAccountDepositHistory("") + testStandardErrorHandling(t, err) +} + +// TestGetAccountDepositHistoryForCurrency API endpoint test +func TestGetAccountDepositHistoryForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetAccountDepositHistory(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestGetSpotTradingAccounts API endpoint test +func TestGetSpotTradingAccounts(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSpotTradingAccounts() + testStandardErrorHandling(t, err) +} + +// TestGetSpotTradingAccountsForCurrency API endpoint test +func TestGetSpotTradingAccountsForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSpotTradingAccountForCurrency(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestGetSpotBillDetailsForCurrency API endpoint test +func TestGetSpotBillDetailsForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSpotBillDetailsForCurrencyRequest{ + Currency: symbol.BTC, + Limit: 100, + } + _, err := o.GetSpotBillDetailsForCurrency(request) + testStandardErrorHandling(t, err) +} + +// TestGetSpotBillDetailsForCurrencyBadLimit API logic test +func TestGetSpotBillDetailsForCurrencyBadLimit(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSpotBillDetailsForCurrencyRequest{ + Currency: symbol.BTC, + Limit: -1, + } + _, err := o.GetSpotBillDetailsForCurrency(request) + if areTestAPIKeysSet() && err == nil { + t.Errorf("Expecting an error when invalid request sent") + } +} + +// TestPlaceSpotOrderLimit API endpoint test +func TestPlaceSpotOrderLimit(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "limit", + Side: "buy", + MarginTrading: "1", + Price: "100", + Size: "100", + } + + _, err := o.PlaceSpotOrder(request) + testStandardErrorHandling(t, err) +} + +// TestPlaceSpotOrderMarket API endpoint test +func TestPlaceSpotOrderMarket(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + _, err := o.PlaceSpotOrder(request) + testStandardErrorHandling(t, err) +} + +// TestPlaceMultipleSpotOrders API endpoint test +func TestPlaceMultipleSpotOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + } + + _, errs := o.PlaceMultipleSpotOrders(request) + if len(errs) > 0 { + testStandardErrorHandling(t, errs[0]) + } +} + +// TestPlaceMultipleSpotOrdersOverCurrencyLimits API logic test +func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + order, + order, + order, + order, + } + + _, errs := o.PlaceMultipleSpotOrders(request) + if errs[0].Error() != "maximum 4 orders for each pair" { + t.Error("Expecting an error when more than 4 orders for a pair supplied", errs[0]) + } +} + +// TestPlaceMultipleSpotOrdersOverPairLimits API logic test +func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + } + + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.LTC, symbol.USDT, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.DOGE, symbol.USDT, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.XMR, symbol.USDT, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.BCH, symbol.USDT, "-").Pair().Lower().String() + request = append(request, order) + + _, errs := o.PlaceMultipleSpotOrders(request) + if errs[0].Error() != "up to 4 trading pairs" { + t.Error("Expecting an error when more than 4 trading pairs supplied", errs[0]) + } +} + +// TestCancelSpotOrder API endpoint test +func TestCancelSpotOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.CancelSpotOrderRequest{ + InstrumentID: spotCurrency, + OrderID: 1234, + } + + _, err := o.CancelSpotOrder(request) + testStandardErrorHandling(t, err) +} + +// TestCancelMultipleSpotOrders API endpoint test +func TestCancelMultipleSpotOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.CancelMultipleSpotOrdersRequest{ + InstrumentID: spotCurrency, + OrderIDs: []int64{1, 2, 3, 4}, + } + + cancellations, err := o.CancelMultipleSpotOrders(request) + testStandardErrorHandling(t, err) + for _, cancellationsPerCurrency := range cancellations { + for _, cancellation := range cancellationsPerCurrency { + if !cancellation.Result { + t.Error(cancellation.Error) + } + } + } +} + +// TestCancelMultipleSpotOrdersOverCurrencyLimits API logic test +func TestCancelMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.CancelMultipleSpotOrdersRequest{ + InstrumentID: spotCurrency, + OrderIDs: []int64{1, 2, 3, 4, 5}, + } + + _, err := o.CancelMultipleSpotOrders(request) + if err.Error() != "maximum 4 order cancellations for each pair" { + t.Error("Expecting an error when more than 4 orders for a pair supplied", err) + } +} + +// TestGetSpotOrders API endpoint test +func TestGetSpotOrders(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSpotOrdersRequest{ + InstrumentID: spotCurrency, + Status: "all", + Limit: 1, + } + _, err := o.GetSpotOrders(request) + testStandardErrorHandling(t, err) +} + +// TestGetSpotOpenOrders API endpoint test +func TestGetSpotOpenOrders(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSpotOpenOrdersRequest{} + _, err := o.GetSpotOpenOrders(request) + testStandardErrorHandling(t, err) +} + +// TestGetSpotOrder API endpoint test +func TestGetSpotOrder(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSpotOrderRequest{ + OrderID: "-1234", + InstrumentID: pair.NewCurrencyPairWithDelimiter(symbol.BTC, symbol.USDT, "-").Pair().Upper().String(), + } + _, err := o.GetSpotOrder(request) + testStandardErrorHandling(t, err) +} + +// TestGetSpotTransactionDetails API endpoint test +func TestGetSpotTransactionDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSpotTransactionDetailsRequest{ + OrderID: 1234, + InstrumentID: spotCurrency, + } + _, err := o.GetSpotTransactionDetails(request) + testStandardErrorHandling(t, err) +} + +// TestGetSpotTokenPairDetails API endpoint test +func TestGetSpotTokenPairDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSpotTokenPairDetails() if err != nil { - t.Errorf("Test failed - okex GetSpotInstruments() failed: %s", err) + t.Error(err) } } -func TestGetContractPrice(t *testing.T) { +// TestGetSpotOrderBook API endpoint test +func TestGetSpotOrderBook(t *testing.T) { + TestSetDefaults(t) t.Parallel() - _, err := o.GetContractPrice("btc_usd", "this_week") + request := okgroup.GetSpotOrderBookRequest{ + InstrumentID: spotCurrency, + } + _, err := o.GetSpotOrderBook(request) if err != nil { - t.Error("Test failed - okex GetContractPrice() error", err) - } - _, err = o.GetContractPrice("btc_bla", "123525") - if err == nil { - t.Error("Test failed - okex GetContractPrice() error", err) - } - _, err = o.GetContractPrice("btc_bla", "this_week") - if err == nil { - t.Error("Test failed - okex GetContractPrice() error", err) + t.Error(err) } } -func TestGetContractMarketDepth(t *testing.T) { +// TestGetSpotAllTokenPairsInformation API endpoint test +func TestGetSpotAllTokenPairsInformation(t *testing.T) { + TestSetDefaults(t) t.Parallel() - _, err := o.GetContractMarketDepth("btc_usd", "this_week") + _, err := o.GetSpotAllTokenPairsInformation() if err != nil { - t.Error("Test failed - okex GetContractMarketDepth() error", err) - } - _, err = o.GetContractMarketDepth("btc_bla", "123525") - if err == nil { - t.Error("Test failed - okex GetContractMarketDepth() error", err) - } - _, err = o.GetContractMarketDepth("btc_bla", "this_week") - if err == nil { - t.Error("Test failed - okex GetContractMarketDepth() error", err) + t.Error(err) } } -func TestGetContractTradeHistory(t *testing.T) { +// TestGetSpotAllTokenPairsInformationForCurrency API endpoint test +func TestGetSpotAllTokenPairsInformationForCurrency(t *testing.T) { + TestSetDefaults(t) t.Parallel() - _, err := o.GetContractTradeHistory("btc_usd", "this_week") + _, err := o.GetSpotAllTokenPairsInformationForCurrency(spotCurrency) if err != nil { - t.Error("Test failed - okex GetContractTradeHistory() error", err) - } - _, err = o.GetContractTradeHistory("btc_bla", "123525") - if err == nil { - t.Error("Test failed - okex GetContractTradeHistory() error", err) - } - _, err = o.GetContractTradeHistory("btc_bla", "this_week") - if err == nil { - t.Error("Test failed - okex GetContractTradeHistory() error", err) + t.Error(err) } } -func TestGetContractIndexPrice(t *testing.T) { +// TestGetSpotFilledOrdersInformation API endpoint test +func TestGetSpotFilledOrdersInformation(t *testing.T) { + TestSetDefaults(t) t.Parallel() - _, err := o.GetContractIndexPrice("btc_usd") + request := okgroup.GetSpotFilledOrdersInformationRequest{ + InstrumentID: spotCurrency, + } + _, err := o.GetSpotFilledOrdersInformation(request) if err != nil { - t.Error("Test failed - okex GetContractIndexPrice() error", err) - } - _, err = o.GetContractIndexPrice("lol123") - if err == nil { - t.Error("Test failed - okex GetContractTradeHistory() error", err) + t.Error(err) } } -func TestGetContractExchangeRate(t *testing.T) { +// TestGetSpotMarketData API endpoint test +func TestGetSpotMarketData(t *testing.T) { + TestSetDefaults(t) t.Parallel() - _, err := o.GetContractExchangeRate() + request := okgroup.GetSpotMarketDataRequest{ + InstrumentID: spotCurrency, + Granularity: 604800, + } + _, err := o.GetSpotMarketData(request) if err != nil { - t.Error("Test failed - okex GetContractExchangeRate() error", err) + t.Error(err) } } -func TestGetContractCandlestickData(t *testing.T) { +// TestGetMarginTradingAccounts API endpoint test +func TestGetMarginTradingAccounts(t *testing.T) { + TestSetDefaults(t) t.Parallel() - _, err := o.GetContractCandlestickData("btc_usd", "1min", "this_week", 1, 2) + _, err := o.GetMarginTradingAccounts() + testStandardErrorHandling(t, err) +} + +// TestGetMarginTradingAccountsForCurrency API endpoint test +func TestGetMarginTradingAccountsForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetMarginTradingAccountsForCurrency(spotCurrency) + testStandardErrorHandling(t, err) +} + +// TestGetMarginBillDetails API endpoint test +func TestGetMarginBillDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetMarginBillDetailsRequest{ + InstrumentID: spotCurrency, + Limit: 100, + } + + _, err := o.GetMarginBillDetails(request) + testStandardErrorHandling(t, err) +} + +// TestGetMarginAccountSettings API endpoint test +func TestGetMarginAccountSettings(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetMarginAccountSettings("") + testStandardErrorHandling(t, err) +} + +// TestGetMarginAccountSettingsForCurrency API endpoint test +func TestGetMarginAccountSettingsForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetMarginAccountSettings(spotCurrency) + testStandardErrorHandling(t, err) +} + +// TestOpenMarginLoan API endpoint test +func TestOpenMarginLoan(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.OpenMarginLoanRequest{ + Amount: 100, + InstrumentID: spotCurrency, + QuoteCurrency: symbol.USDT, + } + + _, err := o.OpenMarginLoan(request) + testStandardErrorHandling(t, err) +} + +// TestRepayMarginLoan API endpoint test +func TestRepayMarginLoan(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.RepayMarginLoanRequest{ + Amount: 100, + InstrumentID: spotCurrency, + QuoteCurrency: symbol.USDT, + BorrowID: 1, + } + + _, err := o.RepayMarginLoan(request) + testStandardErrorHandling(t, err) +} + +// TestPlaceMarginOrderLimit API endpoint test +func TestPlaceMarginOrderLimit(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "limit", + Side: "buy", + MarginTrading: "2", + Price: "100", + Size: "100", + } + + _, err := o.PlaceMarginOrder(request) + testStandardErrorHandling(t, err) +} + +// TestPlaceMarginOrderMarket API endpoint test +func TestPlaceMarginOrderMarket(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "2", + Size: "100", + Notional: "100", + } + + _, err := o.PlaceMarginOrder(request) + testStandardErrorHandling(t, err) +} + +// TestPlaceMultipleMarginOrders API endpoint test +func TestPlaceMultipleMarginOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + } + + _, errs := o.PlaceMultipleMarginOrders(request) + if len(errs) > 0 { + testStandardErrorHandling(t, errs[0]) + } +} + +// TestPlaceMultipleMarginOrdersOverCurrencyLimits API logic test +func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + order, + order, + order, + order, + } + + _, errs := o.PlaceMultipleMarginOrders(request) + if errs[0].Error() != "maximum 4 orders for each pair" { + t.Error("Expecting an error when more than 4 orders for a pair supplied", errs[0]) + } +} + +// TestPlaceMultipleMarginOrdersOverPairLimits API logic test +func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + order := okgroup.PlaceSpotOrderRequest{ + InstrumentID: spotCurrency, + Type: "market", + Side: "buy", + MarginTrading: "1", + Size: "100", + Notional: "100", + } + + request := []okgroup.PlaceSpotOrderRequest{ + order, + } + + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.LTC, symbol.USDT, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.DOGE, symbol.USDT, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.XMR, symbol.USDT, "-").Pair().Lower().String() + request = append(request, order) + order.InstrumentID = pair.NewCurrencyPairWithDelimiter(symbol.BCH, symbol.USDT, "-").Pair().Lower().String() + request = append(request, order) + + _, errs := o.PlaceMultipleMarginOrders(request) + if errs[0].Error() != "up to 4 trading pairs" { + t.Error("Expecting an error when more than 4 trading pairs supplied", errs[0]) + } +} + +// TestCancelMarginOrder API endpoint test +func TestCancelMarginOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.CancelSpotOrderRequest{ + InstrumentID: spotCurrency, + OrderID: 1234, + } + + _, err := o.CancelMarginOrder(request) + testStandardErrorHandling(t, err) +} + +// TestCancelMultipleMarginOrders API endpoint test +func TestCancelMultipleMarginOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.CancelMultipleSpotOrdersRequest{ + InstrumentID: spotCurrency, + OrderIDs: []int64{1, 2, 3, 4}, + } + + _, errs := o.CancelMultipleMarginOrders(request) + if len(errs) > 0 { + testStandardErrorHandling(t, errs[0]) + } +} + +// TestCancelMultipleMarginOrdersOverCurrencyLimits API logic test +func TestCancelMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.CancelMultipleSpotOrdersRequest{ + InstrumentID: spotCurrency, + OrderIDs: []int64{1, 2, 3, 4, 5}, + } + + _, errs := o.CancelMultipleMarginOrders(request) + if errs[0].Error() != "maximum 4 order cancellations for each pair" { + t.Error("Expecting an error when more than 4 orders for a pair supplied", errs[0]) + } +} + +// TestGetMarginOrders API endpoint test +func TestGetMarginOrders(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSpotOrdersRequest{ + InstrumentID: spotCurrency, + Status: "all", + } + _, err := o.GetMarginOrders(request) + testStandardErrorHandling(t, err) +} + +// TestGetMarginOpenOrders API endpoint test +func TestGetMarginOpenOrders(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSpotOpenOrdersRequest{} + _, err := o.GetMarginOpenOrders(request) + testStandardErrorHandling(t, err) +} + +// TestGetMarginOrder API endpoint test +func TestGetMarginOrder(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSpotOrderRequest{ + OrderID: "1234", + InstrumentID: pair.NewCurrencyPairWithDelimiter(symbol.BTC, symbol.USDT, "-").Pair().Upper().String(), + } + _, err := o.GetMarginOrder(request) + testStandardErrorHandling(t, err) +} + +// TestGetMarginTransactionDetails API endpoint test +func TestGetMarginTransactionDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSpotTransactionDetailsRequest{ + OrderID: 1234, + InstrumentID: spotCurrency, + } + _, err := o.GetMarginTransactionDetails(request) + testStandardErrorHandling(t, err) +} + +var genericFutureInstrumentID string + +// getFutureInstrumentID Future contract ids are date based without an easy way to calculate the closest valid date +// This retrieves the value and stores it if running all tests so only one call is made +func getFutureInstrumentID() string { + if genericFutureInstrumentID != "" { + return genericFutureInstrumentID + } + resp, err := o.GetFuturesContractInformation() if err != nil { - t.Error("Test failed - okex GetContractCandlestickData() error", err) - } - _, err = o.GetContractCandlestickData("btc_bla", "1min", "this_week", 1, 2) - if err == nil { - t.Error("Test failed - okex GetContractCandlestickData() error", err) - } - _, err = o.GetContractCandlestickData("btc_usd", "min", "this_week", 1, 2) - if err == nil { - t.Error("Test failed - okex GetContractCandlestickData() error", err) - } - _, err = o.GetContractCandlestickData("btc_usd", "1min", "this_wok", 1, 2) - if err == nil { - t.Error("Test failed - okex GetContractCandlestickData() error", err) + // No error handling here because we're not testing this + return err.Error() } + genericFutureInstrumentID = resp[0].InstrumentID + return genericFutureInstrumentID } -func TestGetContractHoldingsNumber(t *testing.T) { +// TestGetFuturesPostions API endpoint test +func TestGetFuturesPostions(t *testing.T) { + TestSetDefaults(t) t.Parallel() - _, _, err := o.GetContractHoldingsNumber("btc_usd", "this_week") + _, err := o.GetFuturesPostions() + testStandardErrorHandling(t, err) +} + +// TestGetFuturesPostionsForCurrency API endpoint test +func TestGetFuturesPostionsForCurrency(t *testing.T) { + TestSetDefaults(t) + currencyContract := getFutureInstrumentID() + _, err := o.GetFuturesPostionsForCurrency(currencyContract) + testStandardErrorHandling(t, err) +} + +// TestGetFuturesAccountOfAllCurrencies API endpoint test +func TestGetFuturesAccountOfAllCurrencies(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetFuturesAccountOfAllCurrencies() + testStandardErrorHandling(t, err) +} + +// TestGetFuturesAccountOfACurrency API endpoint test +func TestGetFuturesAccountOfACurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetFuturesAccountOfACurrency(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestGetFuturesLeverage API endpoint test +func TestGetFuturesLeverage(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetFuturesLeverage(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestSetFuturesLeverage API endpoint test +func TestSetFuturesLeverage(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.SetFuturesLeverageRequest{ + Currency: symbol.BTC, + InstrumentID: getFutureInstrumentID(), + Leverage: 10, + Direction: "Long", + } + _, err := o.SetFuturesLeverage(request) + testStandardErrorHandling(t, err) +} + +// TestGetFuturesBillDetails API endpoint test +func TestGetFuturesBillDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetFuturesBillDetails(okgroup.GetSpotBillDetailsForCurrencyRequest{ + Currency: symbol.BTC, + }) + testStandardErrorHandling(t, err) +} + +// TestPlaceFuturesOrder API endpoint test +func TestPlaceFuturesOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + _, err := o.PlaceFuturesOrder(okgroup.PlaceFuturesOrderRequest{ + InstrumentID: getFutureInstrumentID(), + Leverage: 10, + Type: 1, + Size: 2, + Price: 432.11, + ClientOid: "12233456", + }) + testStandardErrorHandling(t, err) +} + +// TestPlaceFuturesOrderBatch API endpoint test +func TestPlaceFuturesOrderBatch(t *testing.T) { + TestSetRealOrderDefaults(t) + _, err := o.PlaceFuturesOrderBatch(okgroup.PlaceFuturesOrderBatchRequest{ + InstrumentID: getFutureInstrumentID(), + Leverage: 10, + OrdersData: []okgroup.PlaceFuturesOrderBatchRequestDetails{ + { + ClientOid: "1", + MatchPrice: "0", + Price: "100", + Size: "100", + Type: "1", + }, + }, + }) + testStandardErrorHandling(t, err) +} + +// TestCancelFuturesOrder API endpoint test +func TestCancelFuturesOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + _, err := o.CancelFuturesOrder(okgroup.CancelFuturesOrderRequest{ + InstrumentID: getFutureInstrumentID(), + OrderID: "1", + }) + testStandardErrorHandling(t, err) +} + +// TestCancelMultipleSpotOrders API endpoint test +func TestCancelMultipleFuturesOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + request := okgroup.CancelMultipleSpotOrdersRequest{ + InstrumentID: getFutureInstrumentID(), + OrderIDs: []int64{1, 2, 3, 4}, + } + + _, err := o.CancelFuturesOrderBatch(request) + testStandardErrorHandling(t, err) +} + +// TestGetFuturesOrderList API endpoint test +func TestGetFuturesOrderList(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesOrderList(okgroup.GetFuturesOrdersListRequest{ + InstrumentID: getFutureInstrumentID(), + Status: 6, + }) + testStandardErrorHandling(t, err) +} + +// TestGetFuturesOrderDetails API endpoint test +func TestGetFuturesOrderDetails(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesOrderDetails(okgroup.GetFuturesOrderDetailsRequest{ + InstrumentID: getFutureInstrumentID(), + OrderID: 1, + }) + testStandardErrorHandling(t, err) +} + +// TestGetFuturesTransactionDetails API endpoint test +func TestGetFuturesTransactionDetails(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesTransactionDetails(okgroup.GetFuturesTransactionDetailsRequest{ + InstrumentID: getFutureInstrumentID(), + OrderID: 1, + }) + testStandardErrorHandling(t, err) +} + +// TestGetFuturesContractInformation API endpoint test +func TestGetFuturesContractInformation(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetFuturesContractInformation() if err != nil { - t.Error("Test failed - okex GetContractHoldingsNumber() error", err) - } - _, _, err = o.GetContractHoldingsNumber("btc_bla", "this_week") - if err == nil { - t.Error("Test failed - okex GetContractHoldingsNumber() error", err) - } - _, _, err = o.GetContractHoldingsNumber("btc_usd", "this_bla") - if err == nil { - t.Error("Test failed - okex GetContractHoldingsNumber() error", err) + t.Error(err) } } -func TestGetContractlimit(t *testing.T) { - t.Parallel() - _, err := o.GetContractlimit("btc_usd", "this_week") - if err != nil { - t.Error("Test failed - okex GetContractlimit() error", err) - } - _, err = o.GetContractlimit("btc_bla", "this_week") - if err == nil { - t.Error("Test failed - okex GetContractlimit() error", err) - } - _, err = o.GetContractlimit("btc_usd", "this_bla") - if err == nil { - t.Error("Test failed - okex GetContractlimit() error", err) - } -} - -func TestGetContractUserInfo(t *testing.T) { - t.Parallel() - err := o.GetContractUserInfo() - if err == nil { - t.Error("Test failed - okex GetContractUserInfo() error", err) - } -} - -func TestGetContractPosition(t *testing.T) { - t.Parallel() - err := o.GetContractPosition("btc_usd", "this_week") - if err == nil { - t.Error("Test failed - okex GetContractPosition() error", err) - } -} - -func TestPlaceContractOrders(t *testing.T) { - t.Parallel() - _, err := o.PlaceContractOrders("btc_usd", "this_week", "1", 10, 1, 1, true) - if err == nil { - t.Error("Test failed - okex PlaceContractOrders() error", err) - } -} - -func TestGetContractFuturesTradeHistory(t *testing.T) { - t.Parallel() - err := o.GetContractFuturesTradeHistory("btc_usd", "1972-01-01", 0) - if err == nil { - t.Error("Test failed - okex GetContractTradeHistory() error", err) - } -} - -func TestGetLatestSpotPrice(t *testing.T) { - t.Parallel() - _, err := o.GetLatestSpotPrice("ltc_btc") - if err != nil { - t.Error("Test failed - okex GetLatestSpotPrice() error", err) - } -} - -func TestGetSpotTicker(t *testing.T) { - t.Parallel() - _, err := o.GetSpotTicker("ltc_btc") - if err != nil { - t.Error("Test failed - okex GetSpotTicker() error", err) - } -} - -func TestGetSpotMarketDepth(t *testing.T) { - t.Parallel() - _, err := o.GetSpotMarketDepth(ActualSpotDepthRequestParams{ - Symbol: "eth_btc", - Size: 2, +// TestGetFuturesContractInformation API endpoint test +func TestGetFuturesOrderBook(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesOrderBook(okgroup.GetFuturesOrderBookRequest{ + InstrumentID: getFutureInstrumentID(), + Size: 10, }) if err != nil { - t.Error("Test failed - okex GetSpotMarketDepth() error", err) + t.Error(err) } } -func TestGetSpotRecentTrades(t *testing.T) { +// TestGetAllFuturesTokenInfo API endpoint test +func TestGetAllFuturesTokenInfo(t *testing.T) { + TestSetDefaults(t) t.Parallel() - _, err := o.GetSpotRecentTrades(ActualSpotTradeHistoryRequestParams{ - Symbol: "ltc_btc", - Since: 0, + _, err := o.GetAllFuturesTokenInfo() + if err != nil { + t.Error(err) + } +} + +// TestGetAllFuturesTokenInfo API endpoint test +func TestGetFuturesTokenInfoForCurrency(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesTokenInfoForCurrency(getFutureInstrumentID()) + if err != nil { + t.Error(err) + } +} + +// TestGetFuturesFilledOrder API endpoint test +func TestGetFuturesFilledOrder(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesFilledOrder(okgroup.GetFuturesFilledOrderRequest{ + InstrumentID: getFutureInstrumentID(), }) if err != nil { - t.Error("Test failed - okex GetSpotRecentTrades() error", err) + t.Error(err) } } -func TestGetSpotKline(t *testing.T) { - t.Parallel() - arg := KlinesRequestParams{ - Symbol: "ltc_btc", - Type: TimeIntervalFiveMinutes, - Size: 100, - } - _, err := o.GetSpotKline(arg) +// TestGetFuturesHoldAmount API endpoint test +func TestGetFuturesHoldAmount(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesHoldAmount(getFutureInstrumentID()) + testStandardErrorHandling(t, err) +} + +// TestGetFuturesHoldAmount API endpoint test +func TestGetFuturesIndices(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesIndices(getFutureInstrumentID()) if err != nil { - t.Error("Test failed - okex GetSpotCandleStick() error", err) + t.Error(err) } } -func TestSpotNewOrder(t *testing.T) { +// TestGetFuturesHoldAmount API endpoint test +func TestGetFuturesExchangeRates(t *testing.T) { + TestSetDefaults(t) t.Parallel() - - if o.APIKey == "" || o.APISecret == "" { - t.Skip() + _, err := o.GetFuturesExchangeRates() + if err != nil { + t.Errorf("Encountered error: %v", err) } +} - _, err := o.SpotNewOrder(SpotNewOrderRequestParams{ - Symbol: "ltc_btc", - Amount: 1.1, - Price: 10.1, - Type: SpotNewOrderRequestTypeBuy, +// TestGetFuturesHoldAmount API endpoint test +func TestGetFuturesEstimatedDeliveryPrice(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesEstimatedDeliveryPrice(getFutureInstrumentID()) + if err != nil { + t.Error(err) + } +} + +// TestGetFuturesOpenInterests API endpoint test +func TestGetFuturesOpenInterests(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesOpenInterests(getFutureInstrumentID()) + if err != nil { + t.Error(err) + } +} + +// TestGetFuturesOpenInterests API endpoint test +func TestGetFuturesCurrentPriceLimit(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesCurrentPriceLimit(getFutureInstrumentID()) + if err != nil { + t.Error(err) + } +} + +// TestGetFuturesCurrentMarkPrice API endpoint test +func TestGetFuturesCurrentMarkPrice(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesCurrentMarkPrice(getFutureInstrumentID()) + if err != nil { + t.Error(err) + } +} + +// TestGetFuturesForceLiquidatedOrders API endpoint test +func TestGetFuturesForceLiquidatedOrders(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesForceLiquidatedOrders(okgroup.GetFuturesForceLiquidatedOrdersRequest{ + InstrumentID: getFutureInstrumentID(), + Status: "1", }) if err != nil { - t.Error("Test failed - okex SpotNewOrder() error", err) + t.Error(err) } } -func TestSpotCancelOrder(t *testing.T) { +// TestGetFuturesTagPrice API endpoint test +func TestGetFuturesTagPrice(t *testing.T) { + TestSetDefaults(t) + _, err := o.GetFuturesTagPrice(getFutureInstrumentID()) + testStandardErrorHandling(t, err) +} + +// TestGetSwapPostions API endpoint test +func TestGetSwapPostions(t *testing.T) { + TestSetDefaults(t) t.Parallel() - - if o.APIKey == "" || o.APISecret == "" { - t.Skip() - } - - _, err := o.SpotCancelOrder("ltc_btc", 519158961) - if err != nil { - t.Error("Test failed - okex SpotCancelOrder() error", err) - } + _, err := o.GetSwapPostions() + testStandardErrorHandling(t, err) } -func TestGetUserInfo(t *testing.T) { +// TestGetSwapPostionsForContract API endpoint test +func TestGetSwapPostionsForContract(t *testing.T) { + TestSetDefaults(t) t.Parallel() + _, err := o.GetSwapPostionsForContract(fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD)) + testStandardErrorHandling(t, err) +} - if o.APIKey == "" || o.APISecret == "" { - t.Skip() - } +// TestGetSwapAccountOfAllCurrency API endpoint test +func TestGetSwapAccountOfAllCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapAccountOfAllCurrency() + testStandardErrorHandling(t, err) +} - _, err := o.GetUserInfo() +// TestGetSwapAccountSettingsOfAContract API endpoint test +func TestGetSwapAccountSettingsOfAContract(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapAccountSettingsOfAContract(fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD)) + testStandardErrorHandling(t, err) +} + +// TestSetSwapLeverageLevelOfAContract API endpoint test +func TestSetSwapLeverageLevelOfAContract(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.SetSwapLeverageLevelOfAContract(okgroup.SetSwapLeverageLevelOfAContractRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + Leverage: 10, + Side: 1, + }) + + testStandardErrorHandling(t, err) +} + +// TestGetSwapAccountSettingsOfAContract API endpoint test +func TestGetSwapBillDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapBillDetails(okgroup.GetSpotBillDetailsForCurrencyRequest{ + Currency: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + Limit: 100, + }) + testStandardErrorHandling(t, err) +} + +// TestPlaceSwapOrder API endpoint test +func TestPlaceSwapOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + _, err := o.PlaceSwapOrder(okgroup.PlaceSwapOrderRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + Size: 1, + Type: 1, + Price: 1, + }) + testStandardErrorHandling(t, err) +} + +// TestPlaceMultipleSwapOrders API endpoint test +func TestPlaceMultipleSwapOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + _, err := o.PlaceMultipleSwapOrders(okgroup.PlaceMultipleSwapOrdersRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + Leverage: 10, + OrdersData: []okgroup.PlaceMultipleSwapOrderData{ + { + ClientOID: "hello", + MatchPrice: "0", + Price: "10", + Size: "1", + Type: "1", + }, { + ClientOID: "hello2", + MatchPrice: "0", + Price: "10", + Size: "1", + Type: "1", + }}, + }) + testStandardErrorHandling(t, err) +} + +// TestCancelSwapOrder API endpoint test +func TestCancelSwapOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + _, err := o.CancelSwapOrder(okgroup.CancelSwapOrderRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + OrderID: "64-2a-26132f931-3", + }) + testStandardErrorHandling(t, err) +} + +// TestCancelMultipleSwapOrders API endpoint test +func TestCancelMultipleSwapOrders(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + _, err := o.CancelMultipleSwapOrders(okgroup.CancelMultipleSwapOrdersRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + OrderIDs: []int64{1, 2, 3, 4}, + }) + testStandardErrorHandling(t, err) +} + +// TestGetSwapOrderList API endpoint test +func TestGetSwapOrderList(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapOrderList(okgroup.GetSwapOrderListRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + Status: 6, + }) + testStandardErrorHandling(t, err) +} + +// TestGetSwapOrderDetails API endpoint test +func TestGetSwapOrderDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapOrderDetails(okgroup.GetSwapOrderDetailsRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + OrderID: "64-2a-26132f931-3", + }) + testStandardErrorHandling(t, err) +} + +// TestGetSwapTransactionDetails API endpoint test +func TestGetSwapTransactionDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapTransactionDetails(okgroup.GetSwapTransactionDetailsRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + OrderID: "64-2a-26132f931-3", + }) + testStandardErrorHandling(t, err) +} + +// TestGetSwapContractInformation API endpoint test +func TestGetSwapContractInformation(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapContractInformation() if err != nil { - t.Error("Test failed - okex GetUserInfo() error", err) + t.Error(err) } } +// TestGetSwapOrderBook API endpoint test +func TestGetSwapOrderBook(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapOrderBook(okgroup.GetSwapOrderBookRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + Size: 200, + }) + if err != nil { + t.Error(err) + } +} + +// TestGetAllSwapTokensInformation API endpoint test +func TestGetAllSwapTokensInformation(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetAllSwapTokensInformation() + if err != nil { + t.Error(err) + } +} + +// TestGetSwapTokensInformationForCurrency API endpoint test +func TestGetSwapTokensInformationForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapTokensInformationForCurrency(fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD)) + if err != nil { + t.Error(err) + } +} + +// TestGetSwapFilledOrdersData API endpoint test +func TestGetSwapFilledOrdersData(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapFilledOrdersData(&okgroup.GetSwapFilledOrdersDataRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + Limit: 100, + }) + if err != nil { + t.Error(err) + } +} + +// TestGetSwapMarketData API endpoint test +func TestGetSwapMarketData(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetSwapMarketDataRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + Granularity: 604800, + } + _, err := o.GetSwapMarketData(request) + if err != nil { + t.Error(err) + } +} + +// TestGetSwapIndeces API endpoint test +func TestGetSwapIndeces(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapIndeces(fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD)) + if err != nil { + t.Error(err) + } +} + +// TestGetSwapExchangeRates API endpoint test +func TestGetSwapExchangeRates(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapExchangeRates() + if err != nil { + t.Error(err) + } +} + +// TestGetSwapOpenInterest API endpoint test +func TestGetSwapOpenInterest(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapOpenInterest(fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD)) + if err != nil { + t.Error(err) + } +} + +// TestGetSwapCurrentPriceLimits API endpoint test +func TestGetSwapCurrentPriceLimits(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapCurrentPriceLimits(fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD)) + if err != nil { + t.Error(err) + } +} + +// TestGetSwapForceLiquidatedOrders API endpoint test +func TestGetSwapForceLiquidatedOrders(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapForceLiquidatedOrders(okgroup.GetSwapForceLiquidatedOrdersRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + Status: "0", + }) + if err != nil { + t.Error(err) + } +} + +// TestGetSwapOnHoldAmountForOpenOrders API endpoint test +func TestGetSwapOnHoldAmountForOpenOrders(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapOnHoldAmountForOpenOrders(fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD)) + testStandardErrorHandling(t, err) +} + +// TestGetSwapNextSettlementTime API endpoint test +func TestGetSwapNextSettlementTime(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapNextSettlementTime(fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD)) + if err != nil { + t.Error(err) + } +} + +// TestGetSwapMarkPrice API endpoint test +func TestGetSwapMarkPrice(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapMarkPrice(fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD)) + if err != nil { + t.Error(err) + } +} + +// TestGetSwapFundingRateHistory API endpoint test +func TestGetSwapFundingRateHistory(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetSwapFundingRateHistory(okgroup.GetSwapFundingRateHistoryRequest{ + InstrumentID: fmt.Sprintf("%v-%v-SWAP", symbol.BTC, symbol.USD), + Limit: 100, + }) + if err != nil { + t.Error(err) + } +} + +// TestGetETT API endpoint test +func TestGetETT(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetETT() + testStandardErrorHandling(t, err) +} + +// TestGetETTAccountInformationForCurrency API endpoint test +func TestGetETTAccountInformationForCurrency(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetETTBillsDetails(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestGetETTBillsDetails API endpoint test +func TestGetETTBillsDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetETTBillsDetails(symbol.BTC) + testStandardErrorHandling(t, err) +} + +// TestPlaceETTOrder API endpoint test +func TestPlaceETTOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + request := okgroup.PlaceETTOrderRequest{ + QuoteCurrency: spotCurrency, + Type: 0, + Size: "100", + Amount: 1, + ETT: "OK06", + } + + _, err := o.PlaceETTOrder(request) + testStandardErrorHandling(t, err) +} + +// TestCancelETTOrder API endpoint test +func TestCancelETTOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + _, err := o.CancelETTOrder("888845120785408") + testStandardErrorHandling(t, err) +} + +// TestGetETTOrderList API endpoint test +// This results in a 500 error when its a request object +// Or when it is submitted as URL params +// Unsure how to fix +func TestGetETTOrderList(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + request := okgroup.GetETTOrderListRequest{ + Type: 1, + ETT: "OK06ETT", + Status: 0, + } + + _, err := o.GetETTOrderList(request) + testStandardErrorHandling(t, err) +} + +// TestGetETTOrderDetails API endpoint test +func TestGetETTOrderDetails(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetETTOrderDetails("888845020785408") + testStandardErrorHandling(t, err) +} + +// TestGetETTConstituents API endpoint test +func TestGetETTConstituents(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetETTConstituents("OK06ETT") + if err != nil { + t.Error(err) + } +} + +// TestGetETTSettlementPriceHistory API endpoint test +func TestGetETTSettlementPriceHistory(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + _, err := o.GetETTSettlementPriceHistory("OK06ETT") + if err != nil { + t.Error(err) + } +} + +// Websocket tests ---------------------------------------------------------------------------------------------- + +// TestWsLogin API endpoint test +func TestWsLogin(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() + if o.WebsocketConn == nil { + o.Websocket.Shutdown() + err := setupWSConnection() + if err != nil { + t.Error(err) + } + } + if !o.Websocket.IsConnected() { + t.Skip("Could not connect to websocket. Skipping") + } + err := o.WsLogin() + if err != nil { + t.Error(err) + } + var errorReceived bool + for i := 0; i < 5; i++ { + response := <-o.Websocket.DataHandler + if err, ok := response.(error); ok && err != nil { + t.Log(response) + errorReceived = true + } + } + if errorReceived { + t.Error("Expecting no errors") + } +} + +// TestSubscribeToChannel API endpoint test +func TestSubscribeToChannel(t *testing.T) { + TestSetDefaults(t) + if o.WebsocketConn == nil { + o.Websocket.Shutdown() + err := setupWSConnection() + if err != nil { + t.Error(err) + } + } + if !o.Websocket.IsConnected() { + t.Skip("Could not connect to websocket. Skipping") + } + channelName := "spot/depth:LTC-BTC" + err := o.WsSubscribeToChannel(channelName) + if err != nil { + t.Error(err) + return + } + var errorReceived bool + for i := 0; i < 5; i++ { + response := <-o.Websocket.DataHandler + if err, ok := response.(error); ok && err != nil { + t.Log(response) + if strings.Contains(response.(error).Error(), channelName) { + errorReceived = true + } + } + } + + if errorReceived { + t.Error("Expecting subscription to channel") + } +} + +// TestSubscribeToNonExistantChannel Logic test +// Attempts to subscribe to a channel that doesn't exist +// Then captures the error response +func TestSubscribeToNonExistantChannel(t *testing.T) { + defer disconnectFromWS() + TestSetDefaults(t) + if o.WebsocketConn == nil { + o.Websocket.Shutdown() + err := setupWSConnection() + if err != nil { + t.Error(err) + } + } + if !o.Websocket.IsConnected() { + t.Skip("Could not connect to websocket. Skipping") + } + channelName := "badChannel" + err := o.WsSubscribeToChannel(channelName) + if err != nil { + t.Error(err) + return + } + var errorReceived bool + for i := 0; i < 5; i++ { + response := <-o.Websocket.DataHandler + if err, ok := response.(error); ok && err != nil { + t.Log(response) + if strings.Contains(response.(error).Error(), channelName) { + errorReceived = true + } + } + } + + if !errorReceived { + t.Error("Expecting OKEX error - 30040 message: Channel badChannel doesn't exist") + } +} + +// TestGetAssetTypeFromTableName logic test +func TestGetAssetTypeFromTableName(t *testing.T) { + str := "spot/candle300s:BTC-USDT" + spot := o.GetAssetTypeFromTableName(str) + if spot != "SPOT" { + t.Errorf("Error, expected 'SPOT', received: '%v'", spot) + } +} + +// TestGetWsChannelWithoutOrderType logic test +func TestGetWsChannelWithoutOrderType(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + str := "spot/depth5:BTC-USDT" + expected := "depth5" + resp := o.GetWsChannelWithoutOrderType(str) + if resp != expected { + t.Errorf("Logic change error %v should be %v", resp, expected) + } + str = "spot/depth" + resp = o.GetWsChannelWithoutOrderType(str) + expected = "depth" + if resp != expected { + t.Errorf("Logic change error %v should be %v", resp, expected) + } + str = "testWithBadData" + resp = o.GetWsChannelWithoutOrderType(str) + if resp != str { + t.Errorf("Logic change error %v should be %v", resp, str) + } +} + +// TestOrderBookUpdateChecksumCalculator logic test +func TestOrderBookUpdateChecksumCalculator(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + if o.WebsocketConn == nil { + o.Websocket.Shutdown() + err := setupWSConnection() + if err != nil { + t.Error(err) + } + } + disconnectFromWS() + original := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"BTC-USDT","asks":[["3864.6786","0.145",1],["3864.7682","0.005",1],["3864.9851","0.57",1],["3864.9852","0.30137754",1],["3864.9986","2.81818419",1],["3864.9995","0.002",1],["3865","0.0597",1],["3865.0309","0.4",1],["3865.1995","0.004",1],["3865.3995","0.004",1],["3865.5995","0.004",1],["3865.7995","0.004",1],["3865.9995","0.004",1],["3866.0961","0.25865886",1],["3866.1995","0.004",1],["3866.3995","0.004",1],["3866.4004","0.3243",2],["3866.5995","0.004",1],["3866.7633","0.44247086",1],["3866.7995","0.004",1],["3866.9197","0.511",1],["3867.256","0.51716256",1],["3867.3951","0.02588112",1],["3867.4014","0.025",1],["3867.4566","0.02499999",1],["3867.4675","4.01155057",5],["3867.5515","1.1",1],["3867.6113","0.009",1],["3867.7349","0.026",1],["3867.7781","0.03738652",1],["3867.9163","0.0521",1],["3868.0381","0.34354941",1],["3868.0436","0.051",1],["3868.0657","0.90552172",3],["3868.1819","0.03863346",1],["3868.2013","0.194",1],["3868.346","0.051",1],["3868.3863","0.01155",1],["3868.7716","0.009",1],["3868.947","0.025",1],["3868.98","0.001",1],["3869.0764","1.03487931",1],["3869.2773","0.07724578",1],["3869.4039","0.025",1],["3869.4068","1.03",1],["3869.7068","2.06976398",1],["3870","0.5",1],["3870.0465","0.01",1],["3870.7042","0.02099651",1],["3870.9451","2.07047375",1],["3871.5254","1.2",1],["3871.5596","0.001",1],["3871.6605","0.01035032",1],["3871.7179","2.07047375",1],["3871.8816","0.51751625",1],["3872.1","0.75",1],["3872.2464","0.0646",1],["3872.3747","0.283",1],["3872.4039","0.2",1],["3872.7655","0.23179307",1],["3872.8005","2.06976398",1],["3873.1509","2",1],["3873.3215","0.26",1],["3874.1392","0.001",1],["3874.1487","3.88224364",4],["3874.1685","1.8",1],["3874.5571","0.08974762",1],["3874.734","2.06976398",1],["3874.99","0.3",1],["3875","1.001",2],["3875.0041","1.03505051",1],["3875.45","0.3",1],["3875.4766","0.15",1],["3875.7057","0.51751625",1],["3876","0.001",1],["3876.68","0.3",1],["3876.7188","0.001",1],["3877","0.75",1],["3877.31","0.035",1],["3877.38","0.3",1],["3877.7","0.3",1],["3877.88","0.3",1],["3878.0364","0.34770122",1],["3878.4525","0.48579748",1],["3878.4955","0.02812511",1],["3878.8855","0.00258579",1],["3878.9605","0.895",1],["3879","0.001",1],["3879.2984","0.002",2],["3879.432","0.001",1],["3879.6313","6",1],["3879.9999","0.002",2],["3880","1.25132834",5],["3880.2526","0.04075162",1],["3880.7145","0.0647",1],["3881.2469","1.883",1],["3881.878","0.002",2],["3884.4576","0.002",2],["3885","0.002",2],["3885.2233","0.28304103",1],["3885.7416","18",1],["3886","0.001",1],["3886.1554","5.4",1],["3887","0.001",1],["3887.0372","0.002",2],["3887.2559","0.05214011",1],["3887.9238","0.0019",1],["3888","0.15810538",4],["3889","0.001",1],["3889.5175","0.50510653",1],["3889.6168","0.002",2],["3889.9999","0.001",1],["3890","2.34968109",4],["3890.5222","0.00257806",1],["3891.2659","5",1],["3891.9999","0.00893897",1],["3892.1964","0.002",2],["3892.4358","0.0176",1],["3893.1388","1.4279",1],["3894","0.0026321",1],["3894.776","0.001",1],["3895","1.501",2],["3895.379","0.25881288",1],["3897","0.05",1],["3897.3556","0.001",1],["3897.8432","0.73708079",1],["3898","3.31353018",7],["3898.4462","4.757",1],["3898.6","0.47159638",1],["3898.8769","0.0129",1],["3899","6",2],["3899.6516","0.025",1],["3899.9352","0.001",1],["3899.9999","0.013",2],["3900","22.37447743",24],["3900.9999","0.07763916",1],["3901","0.10192487",1],["3902.1937","0.00257034",1],["3902.3991","1.5532141",1],["3902.5148","0.001",1],["3904","1.49331984",1],["3904.9999","0.95905447",1],["3905","0.501",2],["3905.0944","0.001",1],["3905.61","0.099",1],["3905.6801","0.54343686",1],["3906.2901","0.0258",1],["3907.674","0.001",1],["3907.85","1.35778084",1],["3908","0.03846153",1],["3908.23","1.95189531",1],["3908.906","0.03148978",1],["3909","0.001",1],["3909.9999","0.01398721",2],["3910","0.016",2],["3910.2536","0.001",1],["3912.5406","0.88270517",1],["3912.8332","0.001",1],["3913","1.2640608",1],["3913.87","1.69114184",1],["3913.9003","0.00256266",1],["3914","1.21766411",1],["3915","0.001",1],["3915.4128","0.001",1],["3915.7425","6.848",1],["3916","0.0050949",1],["3917.36","1.28658296",1],["3917.9924","0.001",1],["3919","0.001",1],["3919.9999","0.001",1],["3920","1.21171832",3],["3920.0002","0.20217038",1],["3920.572","0.001",1],["3921","0.128",1],["3923.0756","0.00148064",1],["3923.1516","0.001",1],["3923.86","1.38831714",1],["3925","0.01867801",2],["3925.642","0.00255499",1],["3925.7312","0.001",1],["3926","0.04290757",1],["3927","0.023",1],["3927.3175","0.01212865",1],["3927.65","1.51375612",1],["3928","0.5",1],["3928.3108","0.001",1],["3929","0.001",1],["3929.9999","0.01519338",2],["3930","0.0174985",3],["3930.21","1.49335799",1],["3930.8904","0.001",1],["3932.2999","0.01953",1],["3932.8962","7.96",1],["3933.0387","11.808",1],["3933.47","0.001",1],["3934","1.40839932",1],["3935","0.001",1],["3936.8","0.62879518",1],["3937.23","1.56977841",1],["3937.4189","0.00254735",1]],"bids":[["3864.5217","0.00540709",1],["3864.5216","0.14068758",2],["3864.2275","0.01033576",1],["3864.0989","0.00825047",1],["3864.0273","0.38",1],["3864.0272","0.4",1],["3863.9957","0.01083539",1],["3863.9184","0.01653723",1],["3863.8282","0.25588165",1],["3863.8153","0.154",1],["3863.7791","1.14122492",1],["3863.6866","0.01733662",1],["3863.6093","0.02645958",1],["3863.3775","0.02773862",1],["3863.0297","0.513",1],["3863.0286","1.1028564",2],["3862.8489","0.01",1],["3862.5972","0.01890179",1],["3862.3431","0.01152944",1],["3862.313","0.009",1],["3862.2445","0.90551002",3],["3862.0734","0.014",1],["3862.0539","0.64976067",1],["3861.8586","0.025",1],["3861.7888","0.025",1],["3861.7673","0.008",1],["3861.5785","0.01",1],["3861.3895","0.005",1],["3861.3338","0.25875855",1],["3861.161","0.01",1],["3861.1111","0.03863352",1],["3861.0732","0.51703882",1],["3860.9116","0.17754895",1],["3860.75","0.19",1],["3860.6554","0.015",1],["3860.6172","0.005",1],["3860.6088","0.008",1],["3860.4724","0.12940042",1],["3860.4424","0.25880084",1],["3860.42","0.01",1],["3860.3725","0.51760102",1],["3859.8449","0.005",1],["3859.8285","0.03738652",1],["3859.7638","0.07726703",1],["3859.4502","0.008",1],["3859.3772","0.05173471",1],["3859.3409","0.194",1],["3859","5",1],["3858.827","0.0521",1],["3858.8208","0.001",1],["3858.679","0.26",1],["3858.4814","0.07477305",1],["3858.1669","1.03503422",1],["3857.6005","0.006",1],["3857.4005","0.004",1],["3857.2005","0.004",1],["3857.1871","1.218",1],["3857.0005","0.004",1],["3856.8135","0.0646",1],["3856.8005","0.004",1],["3856.2412","0.001",1],["3856.2349","1.03503422",1],["3856.0197","0.01037339",1],["3855.8781","0.23178117",1],["3855.8005","0.004",1],["3855.7165","0.00259355",1],["3855.4858","0.25875855",1],["3854.4584","0.01",1],["3853.6616","0.001",1],["3853.1373","0.92",1],["3852.5072","0.48599702",1],["3851.3926","0.13008333",1],["3851.082","0.001",1],["3850.9317","2",1],["3850.6359","0.34770165",1],["3850.2058","0.51751624",1],["3850.0823","0.15",1],["3850.0042","0.5175171",1],["3850","0.001",1],["3849.6325","1.8",1],["3849.41","0.3",1],["3848.9686","1.85",1],["3848.7426","0.18511466",1],["3848.52","0.3",1],["3848.5024","0.001",1],["3848.42","0.3",1],["3848.1618","2.204",1],["3847.77","0.3",1],["3847.48","0.3",1],["3847.3581","2.05",1],["3846.8259","0.0646",1],["3846.59","0.3",1],["3846.49","0.3",1],["3845.9228","0.001",1],["3844.184","0.00260133",1],["3844.0092","6.3",1],["3843.3432","0.001",1],["3841","0.06300963",1],["3840.7636","0.001",1],["3840","0.201",3],["3839.7681","18",1],["3839.5328","0.05214011",1],["3838.184","0.001",1],["3837.2344","0.27589557",1],["3836.6479","5.2",1],["3836","2.37196773",3],["3835.6044","0.001",1],["3833.6053","0.25873556",1],["3833.0248","0.001",1],["3833","0.8726502",1],["3832.6859","0.00260913",1],["3832","0.007",1],["3831.637","6",1],["3831.0602","0.001",1],["3830.4452","0.001",1],["3830","0.20375718",4],["3829.7125","0.07833486",1],["3829.6283","0.3519681",1],["3829","0.0039261",1],["3827.8656","0.001",1],["3826.0001","0.53251232",1],["3826","0.0509",1],["3825.7834","0.00698562",1],["3825.286","0.001",1],["3823.0001","0.03010127",1],["3822.8014","0.00261588",1],["3822.7064","0.001",1],["3822.2","1",1],["3822.1121","0.35994101",1],["3821.2222","0.00261696",1],["3821","0.001",1],["3820.1268","0.001",1],["3820","1.12992803",4],["3819","0.01331195",2],["3817.5472","0.001",1],["3816","1.13807184",2],["3815.8343","0.32463428",1],["3815.7834","0.00525295",1],["3815","28.99386799",4],["3814.9676","0.001",1],["3813","0.91303023",4],["3812.388","0.002",2],["3811.2257","0.07",1],["3810","0.32573997",2],["3809.8084","0.001",1],["3809.7928","0.00262481",1],["3807.2288","0.001",1],["3806.8421","0.07003461",1],["3806","0.19",1],["3805.8041","0.05678805",1],["3805","1.01",2],["3804.6492","0.001",1],["3804.3551","0.1",1],["3803","0.005",1],["3802.22","2.05042631",1],["3802.0696","0.001",1],["3802","1.63290092",1],["3801.2257","0.07",1],["3801","57.4",3],["3800.9853","0.02492278",1],["3800.8421","0.06503533",1],["3800.7844","0.02812628",1],["3800.0001","0.00409473",1],["3800","17.91401074",15],["3799.49","0.001",1],["3799","0.1",1],["3796.9104","0.001",1],["3796","9.00128053",2],["3795.5441","0.0028",1],["3794.3308","0.001",1],["3791","55",1],["3790.7777","0.07",1],["3790","12.03238184",7],["3789","1",1],["3788","0.21110454",2],["3787.2959","9",1],["3786.592","0.001",1],["3786","9.01916822",2],["3785","12.87914268",5],["3784.0124","0.001",1],["3781.4328","0.002",2],["3781","56.3",2],["3780.7777","0.07",1],["3780","23.41537654",10],["3778.8532","0.002",2],["3776","9",1],["3774","0.003",1],["3772.2481","0.06901672",1],["3771","55.1",2],["3770.7777","0.07",1],["3770","7.30268416",5],["3769","0.25",1],["3768","1.3725",3],["3766.66","0.02",1],["3766","7.64837924",2],["3765.58","1.22775492",1],["3762.58","1.22873383",1],["3761","51.68262164",1],["3760.8031","0.0399",1],["3760.7777","0.07",1]],"timestamp":"2019-03-06T23:19:17.705Z","checksum":-1785549915}]}` + update := `{"table":"spot/depth","action":"update","data":[{"instrument_id":"BTC-USDT","asks":[["3864.6786","0",0],["3864.9852","0",0],["3865.9994","0.48402971",1],["3866.4004","0.001",1],["3866.7995","0.3273",2],["3867.4566","0",0],["3867.7031","0.025",1],["3868.0436","0",0],["3868.346","0",0],["3868.3695","0.051",1],["3870.9243","0.642",1],["3874.9942","0.51751796",1],["3875.7057","0",0],["3939","0.001",1]],"bids":[["3864.55","0.0565449",1],["3863.8282","0",0],["3863.8153","0",0],["3863.7898","0.01320077",1],["3863.4807","0.02112123",1],["3863.3002","0.04233533",1],["3863.1717","0.03379397",1],["3863.0685","0.04438179",1],["3863.0286","0.7362564",1],["3862.9912","0.06773651",1],["3862.8626","0.05407035",1],["3862.7595","0.07101087",1],["3862.313","0.3756",2],["3862.1848","0.012",1],["3862.0734","0",0],["3861.8391","0.025",1],["3861.7888","0",0],["3856.6716","0.38893641",1],["3768","0",0],["3766.66","0",0],["3766","0",0],["3765.58","0",0],["3762.58","0",0],["3761","0",0],["3760.8031","0",0],["3760.7777","0",0]],"timestamp":"2019-03-06T23:19:18.239Z","checksum":-1587788848}]}` + var dataResponse okgroup.WebsocketDataResponse + err := common.JSONDecode([]byte(original), &dataResponse) + if err != nil { + t.Error(err) + } + err = o.WsProcessOrderBook(&dataResponse) + if err != nil { + t.Error(err) + return + } + var updateResponse okgroup.WebsocketDataResponse + err = common.JSONDecode([]byte(update), &updateResponse) + if err != nil { + t.Error(err) + } + time.Sleep(2 * time.Second) + err = o.WsProcessOrderBook(&updateResponse) + if err != nil { + t.Error(err) + } +} + +// TestOrderBookUpdateChecksumCalculatorWithDash logic test +func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { + TestSetDefaults(t) + t.Parallel() + if o.WebsocketConn == nil { + o.Websocket.Shutdown() + err := setupWSConnection() + if err != nil { + t.Error(err) + } + } + disconnectFromWS() + original := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"WAVES-BTC","asks":[["0.000714","1.15414979",1],["0.000715","3.3",2],["0.000717","426.71348",2],["0.000719","140.84507042",1],["0.00072","590.77",1],["0.000721","991.77",1],["0.000724","0.3532032",1],["0.000725","58.82698567",1],["0.000726","1033.15469748",2],["0.000729","0.35320321",1],["0.00073","352.77",1],["0.000735","0.38469748",1],["0.000736","625.77",1],["0.00075191","152.44796961",1],["0.00075192","114.3359772",1],["0.00075193","85.7519829",1],["0.00075194","64.31398718",1],["0.00075195","48.23549038",1],["0.00075196","36.17661779",1],["0.00075199","61.04804253",1],["0.0007591","70.71318474",1],["0.0007621","53.03488855",1],["0.00076211","39.77616642",1],["0.00076212","29.83212481",1],["0.0007635","22.37409361",1],["0.00076351","29.36599786",2],["0.00076352","9.43907074",1],["0.00076353","7.07930306",1],["0.00076354","14.15860612",1],["0.00076355","3.53965153",1],["0.00076369","3.53965153",1],["0.0008","34.36841101",1],["0.00082858","1.69936503",1],["0.00083232","2.8",1],["0.00084","15.69220129",1],["0.00085","4.42785042",1],["0.00088","0.1",1],["0.000891","0.1",1],["0.0009","12.41486491",2],["0.00093","5",1],["0.0012","12.31486492",1],["0.00531314","6.91803114",1],["0.00799999","0.02",1],["0.0084","0.05989",1],["0.00931314","5.18852336",1],["0.0799999","0.02",1],["0.499","6.00423396",1],["0.5","0.4995",1],["0.799999","0.02",1],["4.99","2",1],["5","3.98583144",1],["7.99999999","0.02",1],["79.99999999","0.02",1],["799.99999999","0.02986704",1]],"bids":[["0.000709","222.91679881",3],["0.000703","0.47161952",1],["0.000701","140.73015789",2],["0.0007","0.3",1],["0.000699","401",1],["0.000698","232.61801667",2],["0.000689","0.71396896",1],["0.000688","0.69910125",1],["0.000613","227.54771052",1],["0.0005","0.01",1],["0.00026789","3.69905341",1],["0.000238","2.4",1],["0.00022","0.53",1],["0.0000055","374.09871696",1],["0.00000056","222",1],["0.00000055","736.84761363",1],["0.0000002","999",1],["0.00000009","1222.22222417",1],["0.00000008","20868.64520447",1],["0.00000002","110000",1],["0.00000001","10000",1]],"timestamp":"2019-03-12T22:22:42.274Z","checksum":1319037905}]}` + update := `{"table":"spot/depth","action":"update","data":[{"instrument_id":"WAVES-BTC","asks":[["0.000715","100.48199596",3],["0.000716","62.21679881",1]],"bids":[["0.000713","38.95772168",1]],"timestamp":"2019-03-12T22:22:42.938Z","checksum":-131160897}]}` + var dataResponse okgroup.WebsocketDataResponse + err := common.JSONDecode([]byte(original), &dataResponse) + if err != nil { + t.Error(err) + } + err = o.WsProcessOrderBook(&dataResponse) + if err != nil { + t.Error(err) + return + } + var updateResponse okgroup.WebsocketDataResponse + err = common.JSONDecode([]byte(update), &updateResponse) + if err != nil { + t.Error(err) + } + time.Sleep(2 * time.Second) + err = o.WsProcessOrderBook(&updateResponse) + if err != nil { + t.Error(err) + } +} + +// TestOrderBookPartialChecksumCalculator logic test +func TestOrderBookPartialChecksumCalculator(t *testing.T) { + orderbookPartialJSON := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"EOS-USDT","asks":[["3.5196","0.1077",1],["3.5198","21.71",1],["3.5199","51.1805",1],["3.5208","75.09",1],["3.521","196.3333",1],["3.5213","0.1",1],["3.5218","39.276",2],["3.5219","395.6334",1],["3.522","27.956",1],["3.5222","404.9595",1],["3.5225","300",1],["3.5227","143.5442",2],["3.523","42.4746",1],["3.5231","852.64",2],["3.5235","34.9602",1],["3.5237","442.0918",2],["3.5238","352.8404",2],["3.5239","341.6759",2],["3.524","84.9493",1],["3.5241","148.4882",1],["3.5242","261.64",1],["3.5243","142.045",1],["3.5246","10",1],["3.5247","284.0788",1],["3.5248","720",1],["3.5249","89.2518",2],["3.5251","1201.8965",2],["3.5254","426.2938",1],["3.5255","213.0863",1],["3.5257","568.1576",1],["3.5258","0.3",1],["3.5259","34.4602",1],["3.526","0.1",1],["3.5263","850.771",1],["3.5265","5.9",1],["3.5268","10.5064",2],["3.5272","1136.8965",1],["3.5274","255.1481",1],["3.5276","29.5374",1],["3.5278","50",1],["3.5282","284.1797",1],["3.5283","1136.8965",1],["3.5284","0.4275",1],["3.5285","100",1],["3.5292","90.9",1],["3.5298","0.2",1],["3.5303","568.1576",1],["3.5305","279.9999",1],["3.532","0.409",1],["3.5321","568.1576",1],["3.5326","6016.8756",1],["3.5328","4.9849",1],["3.533","92.88",2],["3.5343","1200.2383",2],["3.5344","100",1],["3.535","359.7047",1],["3.5354","100",1],["3.5355","100",1],["3.5356","10",1],["3.5358","200",2],["3.5362","435.139",1],["3.5365","2152",1],["3.5366","284.1756",1],["3.5367","568.4644",1],["3.5369","33.9878",1],["3.537","337.1191",2],["3.5373","0.4045",1],["3.5383","1136.7188",1],["3.5386","12.1614",1],["3.5387","90.89",1],["3.54","4.54",1],["3.5423","90.8",1],["3.5436","0.1",1],["3.5454","853.4156",1],["3.5468","142.0656",1],["3.5491","0.0008",1],["3.55","14478.8206",6],["3.5537","21521",1],["3.5555","11.53",1],["3.5573","50.6001",1],["3.5599","4591.4221",1],["3.56","1227.0002",4],["3.5603","2670",1],["3.5608","58.6638",1],["3.5613","0.1",1],["3.5621","45.9473",1],["3.57","2141.7274",3],["3.5712","2956.9816",1],["3.5717","27.9978",1],["3.5718","0.9285",1],["3.5739","299.73",1],["3.5761","864",1],["3.579","22.5225",1],["3.5791","38.26",2],["3.58","7618.4634",5],["3.5801","457.2184",1],["3.582","24.5",1],["3.5822","1572.6425",1],["3.5845","14.1438",1],["3.585","527.169",1],["3.5865","20",1],["3.5867","4490",1],["3.5876","39.0493",1],["3.5879","392.9083",1],["3.5888","436.42",2],["3.5896","50",1],["3.59","2608.9128",8],["3.5913","19.5246",1],["3.5938","7082",1],["3.597","0.1",1],["3.5979","399",1],["3.5995","315.1509",1],["3.5999","2566.2648",1],["3.6","18511.2292",35],["3.603","22.3379",2],["3.605","499.5",1],["3.6055","100",1],["3.6058","499.5",1],["3.608","1021.1485",1],["3.61","11755.4596",13],["3.611","42.8571",1],["3.6131","6690",1],["3.6157","19.5247",1],["3.618","2500",1],["3.6197","525.7146",1],["3.6198","0.4455",1],["3.62","6440.6295",8],["3.6219","0.4175",1],["3.6237","168",1],["3.6265","0.1001",1],["3.628","64.9345",1],["3.63","4435.4985",6],["3.6308","1.7815",1],["3.6331","0.1",1],["3.6338","355.527",2],["3.6358","50",1],["3.6363","2074.7096",1],["3.6376","4000",1],["3.6396","11090",1],["3.6399","0.4055",1],["3.64","4161.9805",4],["3.6437","117.6524",1],["3.648","190",1],["3.6488","200",1],["3.65","11740.5045",25],["3.6512","0.1",1],["3.6521","728",1],["3.6555","100",1],["3.6598","36.6914",1],["3.66","4331.2148",6],["3.6638","200",1],["3.6673","100",1],["3.6679","38",1],["3.6688","2",1],["3.6695","0.1",1],["3.67","7984.698",6],["3.672","300",1],["3.6777","257.8247",1],["3.6789","393.4217",2],["3.68","9202.3222",11],["3.6818","500",1],["3.6823","299.7",1],["3.6839","422.3748",1],["3.685","100",1],["3.6878","0.1",1],["3.6888","72.0958",2],["3.6889","2876",1],["3.689","28",1],["3.6891","28",1],["3.6892","28",1],["3.6895","28",1],["3.6898","28",1],["3.69","643.96",7],["3.6908","118",2],["3.691","28",1],["3.6916","28",1],["3.6918","28",1],["3.6926","28",1],["3.6928","28",1],["3.6932","28",1],["3.6933","200",1],["3.6935","28",1],["3.6936","28",1],["3.6938","28",1],["3.694","28",1],["3.698","1498.5",1],["3.6988","2014.2004",2],["3.7","21904.2689",22],["3.7029","71.95",1],["3.704","3690.1362",1],["3.7055","100",1],["3.7063","0.1",1],["3.71","4421.3468",4],["3.719","17.3491",1],["3.72","1304.5995",3],["3.7211","10",1],["3.7248","0.1",1],["3.725","1900",1],["3.73","31.1785",2],["3.7375","38",1]],"bids":[["3.5182","151.5343",6],["3.5181","0.3691",1],["3.518","271.3967",2],["3.5179","257.8352",1],["3.5178","12.3811",1],["3.5173","34.1921",2],["3.5171","1013.8256",2],["3.517","272.1119",2],["3.5168","395.3376",1],["3.5166","317.1756",2],["3.5165","348.302",3],["3.5164","142.0414",1],["3.5163","96.8933",2],["3.516","600.1034",3],["3.5159","27.481",1],["3.5158","27.33",1],["3.5157","583.1898",2],["3.5156","24.6819",2],["3.5154","25",1],["3.5153","0.429",1],["3.5152","453.9204",3],["3.5151","2131.592",4],["3.515","335",3],["3.5149","37.1586",1],["3.5147","41.6759",1],["3.5146","54.569",1],["3.5145","70.3515",1],["3.5143","68.206",3],["3.5142","359.4538",2],["3.5139","45.4123",2],["3.5137","71.673",2],["3.5136","25",1],["3.5135","300",1],["3.5134","442.57",2],["3.5132","83.3518",1],["3.513","1245.2529",3],["3.5127","20",1],["3.512","284.1353",1],["3.5119","1136.8319",1],["3.5113","56.9351",1],["3.5111","588.1898",2],["3.5109","255.0946",1],["3.5105","48.65",1],["3.5103","50.2",1],["3.5098","720",1],["3.5096","148.95",1],["3.5094","570.5758",2],["3.509","2.386",1],["3.5089","0.4065",1],["3.5087","282.3859",2],["3.5086","145.036",2],["3.5084","2.386",1],["3.5082","90.98",1],["3.5081","2.386",1],["3.5079","2.386",1],["3.5078","857.6229",2],["3.5075","2.386",1],["3.5074","284.1877",1],["3.5073","100",1],["3.5071","100",1],["3.507","768.4159",3],["3.5069","313.0863",2],["3.5068","426.2938",1],["3.5066","568.3594",1],["3.5063","1136.6865",1],["3.5059","0.3",1],["3.5054","9.9999",1],["3.5053","0.2",1],["3.5051","392.428",1],["3.505","13.79",1],["3.5048","99.5497",2],["3.5047","78.5331",2],["3.5046","2153",1],["3.5041","5983.999",1],["3.5037","668.5682",1],["3.5036","160.5948",1],["3.5024","534.8075",1],["3.5014","28.5604",1],["3.5011","91",1],["3.5","1058.8771",2],["3.4997","50.2",1],["3.4985","3430.0414",1],["3.4949","232.0591",1],["3.4942","21521",1],["3.493","2",1],["3.4928","2",1],["3.4925","0.44",1],["3.4917","142.0656",1],["3.49","2051.8826",4],["3.488","280.7459",1],["3.4852","643.4038",1],["3.4851","86.0807",1],["3.485","213.2436",1],["3.484","0.1",1],["3.4811","144.3399",1],["3.4808","89",1],["3.4803","12.1999",1],["3.4801","2390",1],["3.48","930.8453",9],["3.4791","310",1],["3.4768","206",1],["3.4767","0.9415",1],["3.4754","1.4387",1],["3.4728","20",1],["3.4701","1219.2873",1],["3.47","1904.3139",7],["3.468","0.4035",1],["3.4667","0.1",1],["3.4666","3020.0101",1],["3.465","10",1],["3.464","0.4485",1],["3.462","2119.6556",1],["3.46","1305.6113",8],["3.4589","8.0228",1],["3.457","100",1],["3.456","70.3859",2],["3.4538","20",1],["3.4536","4323.9486",2],["3.4531","827.0427",1],["3.4528","0.439",1],["3.4522","8.0381",1],["3.4513","441.1873",1],["3.4512","50.707",1],["3.451","87.0902",1],["3.4509","200",1],["3.4506","100",1],["3.4505","86.4045",2],["3.45","12409.4595",28],["3.4494","0.5365",2],["3.449","10761",1],["3.4482","8.0476",1],["3.4469","0.449",1],["3.445","2000",1],["3.4427","14",1],["3.4421","100",1],["3.4416","8.0631",1],["3.4404","1",1],["3.44","4580.733",11],["3.4388","1868.2085",1],["3.438","937.7246",2],["3.4367","1500",1],["3.4366","62",1],["3.436","29.8743",1],["3.4356","25.4801",1],["3.4349","4.3086",1],["3.4343","43.2402",1],["3.433","2.0688",1],["3.4322","2.7335",2],["3.432","93.3233",1],["3.4302","328.8301",2],["3.43","4440.8158",11],["3.4288","754.574",2],["3.4283","125.7043",2],["3.428","744.3154",2],["3.4273","5460",1],["3.4258","50",1],["3.4255","109.005",1],["3.4248","100",1],["3.4241","129.2048",2],["3.4233","5.3598",1],["3.4228","4498.866",1],["3.4222","3.5435",1],["3.4217","404.3252",2],["3.4211","1000",1],["3.4208","31",1],["3.42","1834.024",9],["3.4175","300",1],["3.4162","400",1],["3.4152","0.1",1],["3.4151","4.3336",1],["3.415","1.5974",1],["3.414","1146",1],["3.4134","306.4246",1],["3.4129","7.5556",1],["3.4111","198.5188",1],["3.4109","500",1],["3.4106","4305",1],["3.41","2150.7635",13],["3.4085","4.342",1],["3.4054","5.6985",1],["3.4019","5.438",1],["3.4015","1010.846",1],["3.4009","8610",1],["3.4005","1.9122",1],["3.4004","1",1],["3.4","27081.1806",67],["3.3955","3.2682",1],["3.3953","5.4486",1],["3.3937","1591.3805",1],["3.39","3221.4155",8],["3.3899","3.2736",1],["3.3888","1500",2],["3.3887","5.4592",1],["3.385","117.0969",2],["3.3821","5.4699",1],["3.382","100.0529",1],["3.3818","172.0164",1],["3.3815","165.6288",1],["3.381","887.3115",1],["3.3808","100",1]],"timestamp":"2019-03-04T00:15:04.155Z","checksum":-2036653089}]}` + var dataResponse okgroup.WebsocketDataResponse + err := common.JSONDecode([]byte(orderbookPartialJSON), &dataResponse) + if err != nil { + t.Error(err) + } + + calculatedChecksum := o.CalculatePartialOrderbookChecksum(&dataResponse.Data[0]) + if calculatedChecksum != dataResponse.Data[0].Checksum { + t.Errorf("Expected %v, Receieved %v", dataResponse.Data[0].Checksum, calculatedChecksum) + } +} + +// Function tests ---------------------------------------------------------------------------------------------- func setFeeBuilder() exchange.FeeBuilder { return exchange.FeeBuilder{ Amount: 1, @@ -309,8 +1812,10 @@ func setFeeBuilder() exchange.FeeBuilder { } } +// TestGetFee fee calcuation test func TestGetFee(t *testing.T) { - o.SetDefaults() + TestSetDefaults(t) + t.Parallel() var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic if resp, err := o.GetFee(feeBuilder); resp != float64(0.0015) || err != nil { @@ -342,22 +1847,6 @@ func TestGetFee(t *testing.T) { t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } - // CryptocurrencyWithdrawalFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee - if resp, err := o.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) - } - - // CryptocurrencyWithdrawalFee Invalid currency - feeBuilder = setFeeBuilder() - feeBuilder.FirstCurrency = "hello" - feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee - if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) - t.Error(err) - } // CyptocurrencyDepositFee Basic feeBuilder = setFeeBuilder() @@ -385,73 +1874,27 @@ func TestGetFee(t *testing.T) { } } +// TestFormatWithdrawPermissions helper test func TestFormatWithdrawPermissions(t *testing.T) { - o.SetDefaults() + TestSetDefaults(t) + t.Parallel() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := o.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } -func TestGetActiveOrders(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, - Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, - } - - _, err := o.GetActiveOrders(getOrdersRequest) - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } -} - -func TestGetOrderHistory(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, - Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, - } - - _, err := o.GetOrderHistory(getOrdersRequest) - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } -} - -// Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them -// ---------------------------------------------------------------------------------------------------------------------------- -func areTestAPIKeysSet() bool { - if o.APIKey != "" && o.APIKey != "Key" && - o.APISecret != "" && o.APISecret != "Secret" { - return true - } - return false -} +// Wrapper tests -------------------------------------------------------------------------------------------------- +// TestSubmitOrder Wrapper test func TestSubmitOrder(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + TestSetRealOrderDefaults(t) + t.Parallel() var p = pair.CurrencyPair{ Delimiter: "", FirstCurrency: symbol.BTC, - SecondCurrency: symbol.EUR, + SecondCurrency: symbol.USDT, } response, err := o.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { @@ -461,16 +1904,11 @@ func TestSubmitOrder(t *testing.T) { } } +// TestCancelExchangeOrder Wrapper test func TestCancelExchangeOrder(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + TestSetRealOrderDefaults(t) + t.Parallel() currencyPair := pair.NewCurrencyPair(symbol.LTC, symbol.BTC) - var orderCancellation = exchange.OrderCancellation{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -479,24 +1917,15 @@ func TestCancelExchangeOrder(t *testing.T) { } err := o.CancelOrder(orderCancellation) - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } + testStandardErrorHandling(t, err) + } +// TestCancelAllExchangeOrders Wrapper test func TestCancelAllExchangeOrders(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + TestSetRealOrderDefaults(t) + t.Parallel() currencyPair := pair.NewCurrencyPair(symbol.LTC, symbol.BTC) - var orderCancellation = exchange.OrderCancellation{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -505,91 +1934,61 @@ func TestCancelAllExchangeOrders(t *testing.T) { } resp, err := o.CancelAllOrders(orderCancellation) - - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } + testStandardErrorHandling(t, err) if len(resp.OrderStatus) > 0 { t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) } } +// TestGetAccountInfo Wrapper test func TestGetAccountInfo(t *testing.T) { - if apiKey != "" || apiSecret != "" { - _, err := o.GetAccountInfo() - if err != nil { - t.Error("Test Failed - GetAccountInfo() error", err) - } - } else { - _, err := o.GetAccountInfo() - if err == nil { - t.Error("Test Failed - GetAccountInfo() error") - } - } + _, err := o.GetAccountInfo() + testStandardErrorHandling(t, err) } +// TestModifyOrder Wrapper test func TestModifyOrder(t *testing.T) { + TestSetRealOrderDefaults(t) + t.Parallel() _, err := o.ModifyOrder(exchange.ModifyOrder{}) - if err == nil { - t.Error("Test failed - ModifyOrder() error") + if err != common.ErrFunctionNotSupported { + t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) } } +// TestWithdraw Wrapper test func TestWithdraw(t *testing.T) { - o.SetDefaults() - TestSetup(t) + TestSetRealOrderDefaults(t) + t.Parallel() var withdrawCryptoRequest = exchange.WithdrawRequest{ Amount: 100, - Currency: "btc_usd", + Currency: symbol.BTC, Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", Description: "WITHDRAW IT ALL", TradePassword: "Password", FeeAmount: 1, } - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - _, err := o.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } + testStandardErrorHandling(t, err) } +// TestWithdrawFiat Wrapper test func TestWithdrawFiat(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + TestSetRealOrderDefaults(t) + t.Parallel() var withdrawFiatRequest = exchange.WithdrawRequest{} - _, err := o.WithdrawFiatFunds(withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) } } +// TestSubmitOrder Wrapper test func TestWithdrawInternationalBank(t *testing.T) { - o.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + TestSetRealOrderDefaults(t) + t.Parallel() var withdrawFiatRequest = exchange.WithdrawRequest{} - _, err := o.WithdrawFiatFundsToInternationalBank(withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/okex/okex_types.go b/exchanges/okex/okex_types.go deleted file mode 100644 index 9f06b244..00000000 --- a/exchanges/okex/okex_types.go +++ /dev/null @@ -1,494 +0,0 @@ -package okex - -import "encoding/json" -import "github.com/thrasher-/gocryptotrader/currency/symbol" - -// SpotInstrument stores the spot instrument info -type SpotInstrument struct { - BaseCurrency string `json:"base_currency"` - BaseIncrement float64 `json:"base_increment,string"` - BaseMinSize float64 `json:"base_min_size,string"` - InstrumentID string `json:"instrument_id"` - MinSize float64 `json:"min_size,string"` - ProductID string `json:"product_id"` - QuoteCurrency string `json:"quote_currency"` - QuoteIncrement float64 `json:"quote_increment,string"` - SizeIncrement float64 `json:"size_increment,string"` - TickSize float64 `json:"tick_size,string"` -} - -// ContractPrice holds date and ticker price price for contracts. -type ContractPrice struct { - Date string `json:"date"` - Ticker struct { - Buy float64 `json:"buy"` - ContractID float64 `json:"contract_id"` - High float64 `json:"high"` - Low float64 `json:"low"` - Last float64 `json:"last"` - Sell float64 `json:"sell"` - UnitAmount float64 `json:"unit_amount"` - Vol float64 `json:"vol"` - } `json:"ticker"` - Result bool `json:"result"` - Error interface{} `json:"error_code"` -} - -// MultiStreamData contains raw data from okex -type MultiStreamData struct { - Channel string `json:"channel"` - Data json.RawMessage `json:"data"` -} - -// TokenOrdersResponse is returned after a request for all Token Orders -type TokenOrdersResponse struct { - Result bool `json:"result"` - Orders []TokenOrder `json:"orders"` -} - -// TokenOrder is the individual order details returned from TokenOrderResponse -type TokenOrder struct { - Amount float64 `json:"amount"` - AvgPrice int64 `json:"avg_price"` - DealAmount int64 `json:"deal_amount"` - OrderID int64 `json:"order_id"` - Price int64 `json:"price"` - Status int64 `json:"status"` - Symbol string `json:"symbol"` - Type string `json:"type"` -} - -// TickerStreamData contains ticker stream data from okex -type TickerStreamData struct { - Buy string `json:"buy"` - Change string `json:"change"` - High string `json:"high"` - Low string `json:"low"` - Last string `json:"last"` - Sell string `json:"sell"` - DayLow string `json:"dayLow"` - DayHigh string `json:"dayHigh"` - Timestamp float64 `json:"timestamp"` - Vol string `json:"vol"` -} - -// DealsStreamData defines Deals data -type DealsStreamData = [][]string - -// KlineStreamData defines kline data -type KlineStreamData = [][]string - -// DepthStreamData defines orderbook depth -type DepthStreamData struct { - Asks [][]string `json:"asks"` - Bids [][]string `json:"bids"` - Timestamp float64 `json:"timestamp"` -} - -// ContractDepth response depth -type ContractDepth struct { - Asks []interface{} `json:"asks"` - Bids []interface{} `json:"bids"` - Result bool `json:"result"` - Error interface{} `json:"error_code"` -} - -// ActualContractDepth better manipulated structure to return -type ActualContractDepth struct { - Asks []struct { - Price float64 - Volume float64 - } - Bids []struct { - Price float64 - Volume float64 - } -} - -// ActualContractTradeHistory holds contract trade history -type ActualContractTradeHistory struct { - Amount float64 `json:"amount"` - DateInMS float64 `json:"date_ms"` - Date float64 `json:"date"` - Price float64 `json:"price"` - TID float64 `json:"tid"` - Type string `json:"buy"` -} - -// CandleStickData holds candlestick data -type CandleStickData struct { - Timestamp float64 `json:"timestamp"` - Open float64 `json:"open"` - High float64 `json:"high"` - Low float64 `json:"low"` - Close float64 `json:"close"` - Volume float64 `json:"volume"` - Amount float64 `json:"amount"` -} - -// Info holds individual information -type Info struct { - AccountRights float64 `json:"account_rights"` - KeepDeposit float64 `json:"keep_deposit"` - ProfitReal float64 `json:"profit_real"` - ProfitUnreal float64 `json:"profit_unreal"` - RiskRate float64 `json:"risk_rate"` -} - -// UserInfo holds a collection of user data -type UserInfo struct { - Info struct { - BTC Info `json:"btc"` - LTC Info `json:"ltc"` - } `json:"info"` - Result bool `json:"result"` -} - -// HoldData is a sub type for FuturePosition -type HoldData struct { - BuyAmount float64 `json:"buy_amount"` - BuyAvailable float64 `json:"buy_available"` - BuyPriceAvg float64 `json:"buy_price_avg"` - BuyPriceCost float64 `json:"buy_price_cost"` - BuyProfitReal float64 `json:"buy_profit_real"` - ContractID float64 `json:"contract_id"` - ContractType string `json:"contract_type"` - CreateDate int `json:"create_date"` - LeverRate float64 `json:"lever_rate"` - SellAmount float64 `json:"sell_amount"` - SellAvailable float64 `json:"sell_available"` - SellPriceAvg float64 `json:"sell_price_avg"` - SellPriceCost float64 `json:"sell_price_cost"` - SellProfitReal float64 `json:"sell_profit_real"` - Symbol string `json:"symbol"` -} - -// FuturePosition contains an array of holding types -type FuturePosition struct { - ForceLiquidationPrice float64 `json:"force_liqu_price"` - Holding []HoldData `json:"holding"` -} - -// FutureTradeHistory will contain futures trade data -type FutureTradeHistory struct { - Amount float64 `json:"amount"` - Date int `json:"date"` - Price float64 `json:"price"` - TID float64 `json:"tid"` - Type string `json:"type"` -} - -// SpotPrice holds date and ticker price price for contracts. -type SpotPrice struct { - Date string `json:"date"` - Ticker struct { - Buy float64 `json:"buy,string"` - ContractID float64 `json:"contract_id"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - Last float64 `json:"last,string"` - Sell float64 `json:"sell,string"` - UnitAmount float64 `json:"unit_amount,string"` - Vol float64 `json:"vol,string"` - } `json:"ticker"` - Result bool `json:"result"` - Error interface{} `json:"error_code"` -} - -// SpotDepth response depth -type SpotDepth struct { - Asks []interface{} `json:"asks"` - Bids []interface{} `json:"bids"` - Result bool `json:"result"` - Error interface{} `json:"error_code"` -} - -// ActualSpotDepthRequestParams represents Klines request data. -type ActualSpotDepthRequestParams struct { - Symbol string `json:"symbol"` // Symbol; example ltc_btc - Size int `json:"size"` // value: 1-200 -} - -// ActualSpotDepth better manipulated structure to return -type ActualSpotDepth struct { - Asks []struct { - Price float64 - Volume float64 - } - Bids []struct { - Price float64 - Volume float64 - } -} - -// ActualSpotTradeHistoryRequestParams represents Klines request data. -type ActualSpotTradeHistoryRequestParams struct { - Symbol string `json:"symbol"` // Symbol; example ltc_btc - Since int `json:"since"` // TID; transaction record ID (return data does not include the current TID value, returning up to 600 items) -} - -// ActualSpotTradeHistory holds contract trade history -type ActualSpotTradeHistory struct { - Amount float64 `json:"amount"` - DateInMS float64 `json:"date_ms"` - Date float64 `json:"date"` - Price float64 `json:"price"` - TID float64 `json:"tid"` - Type string `json:"buy"` -} - -// SpotUserInfo holds the spot user info -type SpotUserInfo struct { - Result bool `json:"result"` - Info map[string]map[string]map[string]string `json:"info"` -} - -// SpotNewOrderRequestParams holds the params for making a new spot order -type SpotNewOrderRequestParams struct { - Amount float64 `json:"amount"` // Order quantity - Price float64 `json:"price"` // Order price - Symbol string `json:"symbol"` // Symbol; example btc_usdt, eth_btc...... - Type SpotNewOrderRequestType `json:"type"` // Order type (see below) -} - -// SpotNewOrderRequestType order type -type SpotNewOrderRequestType string - -var ( - // SpotNewOrderRequestTypeBuy buy order - SpotNewOrderRequestTypeBuy = SpotNewOrderRequestType("buy") - - // SpotNewOrderRequestTypeSell sell order - SpotNewOrderRequestTypeSell = SpotNewOrderRequestType("sell") - - // SpotNewOrderRequestTypeBuyMarket buy market order - SpotNewOrderRequestTypeBuyMarket = SpotNewOrderRequestType("buy_market") - - // SpotNewOrderRequestTypeSellMarket sell market order - SpotNewOrderRequestTypeSellMarket = SpotNewOrderRequestType("sell_market") -) - -// KlinesRequestParams represents Klines request data. -type KlinesRequestParams struct { - Symbol string // Symbol; example btcusdt, bccbtc...... - Type TimeInterval // Kline data time interval; 1min, 5min, 15min...... - Size int // Size; [1-2000] - Since int64 // Since timestamp, return data after the specified timestamp (for example, 1417536000000) -} - -// TimeInterval represents interval enum. -type TimeInterval string - -// vars for time intervals -var ( - TimeIntervalMinute = TimeInterval("1min") - TimeIntervalThreeMinutes = TimeInterval("3min") - TimeIntervalFiveMinutes = TimeInterval("5min") - TimeIntervalFifteenMinutes = TimeInterval("15min") - TimeIntervalThirtyMinutes = TimeInterval("30min") - TimeIntervalHour = TimeInterval("1hour") - TimeIntervalFourHours = TimeInterval("4hour") - TimeIntervalSixHours = TimeInterval("6hour") - TimeIntervalTwelveHours = TimeInterval("12hour") - TimeIntervalDay = TimeInterval("1day") - TimeIntervalThreeDays = TimeInterval("3day") - TimeIntervalWeek = TimeInterval("1week") -) - -// WithdrawalFees the large list of predefined withdrawal fees -// Prone to change, using highest value -var WithdrawalFees = map[string]float64{ - symbol.ZRX: 10, - symbol.ACE: 2.2, - symbol.ACT: 0.01, - symbol.AAC: 5, - symbol.AE: 1, - symbol.AIDOC: 17, - symbol.AST: 8, - symbol.SOC: 20, - symbol.ABT: 3, - symbol.ARK: 0.1, - symbol.ATL: 1.5, - symbol.AVT: 1, - symbol.BNT: 1, - symbol.BKX: 3, - symbol.BEC: 4, - symbol.BTC: 0.0005, - symbol.BCH: 0.0001, - symbol.BCD: 0.02, - symbol.BTG: 0.001, - symbol.VEE: 100, - symbol.BRD: 1.5, - symbol.CTR: 7, - symbol.LINK: 10, - symbol.CAG: 2, - symbol.CHAT: 10, - symbol.CVC: 10, - symbol.CIC: 150, - symbol.CBT: 10, - symbol.CAN: 3, - symbol.CMT: 10, - symbol.DADI: 10, - symbol.DASH: 0.002, - symbol.DAT: 2, - symbol.MANA: 20, - symbol.DCR: 0.03, - symbol.DPY: 0.8, - symbol.DENT: 100, - symbol.DGD: 0.2, - symbol.DNT: 20, - symbol.EDO: 2, - symbol.DNA: 3, - symbol.ENG: 5, - symbol.ENJ: 20, - symbol.ETH: 0.01, - symbol.ETC: 0.001, - symbol.LEND: 10, - symbol.EVX: 1.5, - symbol.XUC: 5.8, - symbol.FAIR: 15, - symbol.FIRST: 6, - symbol.FUN: 40, - symbol.GTC: 40, - symbol.GNX: 8, - symbol.GTO: 10, - symbol.GSC: 20, - symbol.GNT: 5, - symbol.HMC: 40, - symbol.HOT: 10, - symbol.ICN: 2, - symbol.INS: 2.5, - symbol.INT: 10, - symbol.IOST: 100, - symbol.ITC: 2, - symbol.IPC: 2.5, - symbol.KNC: 2, - symbol.LA: 3, - symbol.LEV: 20, - symbol.LIGHT: 100, - symbol.LSK: 0.4, - symbol.LTC: 0.001, - symbol.LRC: 7, - symbol.MAG: 34, - symbol.MKR: 0.002, - symbol.MTL: 0.5, - symbol.AMM: 5, - symbol.MITH: 20, - symbol.MDA: 2, - symbol.MOF: 5, - symbol.MCO: 0.2, - symbol.MTH: 35, - symbol.NGC: 1.5, - symbol.NANO: 0.2, - symbol.NULS: 2, - symbol.OAX: 6, - symbol.OF: 600, - symbol.OKB: 0, - symbol.MOT: 1.5, - symbol.OMG: 0.1, - symbol.RNT: 13, - symbol.POE: 30, - symbol.PPT: 0.2, - symbol.PST: 10, - symbol.PRA: 4, - symbol.QTUM: 0.01, - symbol.QUN: 20, - symbol.QVT: 10, - symbol.RDN: 0.3, - symbol.READ: 20, - symbol.RCT: 15, - symbol.RFR: 200, - symbol.REF: 0.2, - symbol.REN: 50, - symbol.REQ: 15, - symbol.R: 2, - symbol.RCN: 20, - symbol.XRP: 0.15, - symbol.SALT: 0.5, - symbol.SAN: 1, - symbol.KEY: 50, - symbol.SSC: 8, - symbol.SHOW: 150, - symbol.SC: 200, - symbol.OST: 3, - symbol.SNGLS: 20, - symbol.SMT: 8, - symbol.SNM: 20, - symbol.SPF: 5, - symbol.SNT: 50, - symbol.STORJ: 2, - symbol.SUB: 4, - symbol.SNC: 10, - symbol.SWFTC: 350, - symbol.PAY: 0.5, - symbol.USDT: 2, - symbol.TRA: 500, - symbol.THETA: 20, - symbol.TNB: 40, - symbol.TCT: 50, - symbol.TOPC: 20, - symbol.TIO: 2.5, - symbol.TRIO: 200, - symbol.TRUE: 4, - symbol.UCT: 10, - symbol.UGC: 12, - symbol.UKG: 2.5, - symbol.UTK: 3, - symbol.VIB: 6, - symbol.VIU: 40, - symbol.WTC: 0.4, - symbol.WFEE: 500, - symbol.WRC: 48, - symbol.YEE: 70, - symbol.YOYOW: 10, - symbol.ZEC: 0.001, - symbol.ZEN: 0.07, - symbol.ZIL: 20, - symbol.ZIP: 1000, -} - -// FullBalance defines a structured return type with balance data -type FullBalance struct { - Available float64 - Currency string - Hold float64 -} - -// Balance defines returned balance data -type Balance struct { - Info struct { - Funds struct { - Free map[string]string `json:"free"` - Holds map[string]string `json:"holds"` - } `json:"funds"` - } `json:"info"` -} - -// WithdrawalResponse is a response type for withdrawal -type WithdrawalResponse struct { - WithdrawID int `json:"withdraw_id"` - Result bool `json:"result"` -} - -// OrderInfo holds data on an order -type OrderInfo struct { - Amount float64 `json:"amount"` - AvgPrice float64 `json:"avg_price"` - Created int64 `json:"create_date"` - DealAmount float64 `json:"deal_amount"` - OrderID int64 `json:"order_id"` - OrdersID int64 `json:"orders_id"` - Price float64 `json:"price"` - Status int `json:"status"` - Symbol string `json:"symbol"` - Type string `json:"type"` -} - -// OrderHistory holds information on order history -type OrderHistory struct { - CurrentPage int `json:"current_page"` - Orders []OrderInfo `json:"orders"` - PageLength int `json:"page_length"` - Result bool `json:"result"` - Total int `json:"total"` -} diff --git a/exchanges/okex/okex_websocket.go b/exchanges/okex/okex_websocket.go deleted file mode 100644 index 7b22e694..00000000 --- a/exchanges/okex/okex_websocket.go +++ /dev/null @@ -1,318 +0,0 @@ -package okex - -import ( - "bytes" - "compress/flate" - "errors" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/gorilla/websocket" - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/currency/pair" - exchange "github.com/thrasher-/gocryptotrader/exchanges" - log "github.com/thrasher-/gocryptotrader/logger" -) - -const ( - okexDefaultWebsocketURL = "wss://real.okex.com:10440/websocket/okexapi" -) - -func (o *OKEX) writeToWebsocket(message string) error { - o.mu.Lock() - defer o.mu.Unlock() - - return o.WebsocketConn.WriteMessage(websocket.TextMessage, []byte(message)) -} - -// WsConnect initiates a websocket connection -func (o *OKEX) WsConnect() error { - if !o.Websocket.IsEnabled() || !o.IsEnabled() { - return errors.New(exchange.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 - o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(), - http.Header{}) - if err != nil { - return fmt.Errorf("%s Unable to connect to Websocket. Error: %s", - o.Name, - err) - } - - go o.WsHandleData() - go o.wsPingHandler() - - err = o.WsSubscribe() - if err != nil { - return fmt.Errorf("%s could not subscribe to websocket %s", - o.Name, err) - } - - return nil -} - -// WsSubscribe subscribes to the websocket channels -func (o *OKEX) WsSubscribe() error { - myEnabledSubscriptionChannels := []string{} - - for _, pair := range o.EnabledPairs { - - // ----------- deprecate when usd pairs are upgraded to usdt ---------- - checkSymbol := common.SplitStrings(pair, "_") - for i := range checkSymbol { - if common.StringContains(checkSymbol[i], "usdt") { - break - } - if common.StringContains(checkSymbol[i], "usd") { - checkSymbol[i] = "usdt" - } - } - - symbolRedone := common.JoinStrings(checkSymbol, "_") - // ----------- deprecate when usd pairs are upgraded to usdt ---------- - - myEnabledSubscriptionChannels = append(myEnabledSubscriptionChannels, - fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_ticker'}", - symbolRedone), - fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_depth'}", - symbolRedone), - fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_deals'}", - symbolRedone), - fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_kline_1min'}", - symbolRedone)) - } - - for _, outgoing := range myEnabledSubscriptionChannels { - err := o.writeToWebsocket(outgoing) - if err != nil { - return err - } - } - - return nil -} - -// WsReadData reads data from the websocket connection -func (o *OKEX) 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 - } - } - - return exchange.WebsocketResponse{Raw: standardMessage}, nil -} - -func (o *OKEX) wsPingHandler() { - o.Websocket.Wg.Add(1) - defer o.Websocket.Wg.Done() - - ticker := time.NewTicker(time.Second * 27) - - for { - select { - case <-o.Websocket.ShutdownC: - return - - case <-ticker.C: - err := o.writeToWebsocket("{'event':'ping'}") - if err != nil { - o.Websocket.DataHandler <- err - return - } - } - } -} - -// WsHandleData handles the read data from the websocket connection -func (o *OKEX) WsHandleData() { - o.Websocket.Wg.Add(1) - - defer func() { - err := o.WebsocketConn.Close() - if err != nil { - o.Websocket.DataHandler <- fmt.Errorf("okex_websocket.go - Unable to to close Websocket connection. Error: %s", - err) - } - o.Websocket.Wg.Done() - }() - - for { - select { - case <-o.Websocket.ShutdownC: - return - - default: - resp, err := o.WsReadData() - if err != nil { - o.Websocket.DataHandler <- err - return - } - - multiStreamDataArr := []MultiStreamData{} - - err = common.JSONDecode(resp.Raw, &multiStreamDataArr) - if err != nil { - if strings.Contains(string(resp.Raw), "pong") { - continue - } else { - o.Websocket.DataHandler <- err - continue - } - } - - for _, multiStreamData := range multiStreamDataArr { - var errResponse ErrorResponse - if common.StringContains(string(resp.Raw), "error_msg") { - err = common.JSONDecode(resp.Raw, &errResponse) - if err != nil { - log.Error(err) - } - o.Websocket.DataHandler <- fmt.Errorf("okex.go error - %s resp: %s ", - errResponse.ErrorMsg, - string(resp.Raw)) - continue - } - - var newPair string - var assetType string - currencyPairSlice := common.SplitStrings(multiStreamData.Channel, "_") - if len(currencyPairSlice) > 5 { - newPair = currencyPairSlice[3] + "_" + currencyPairSlice[4] - assetType = currencyPairSlice[2] - } - - switch multiStreamData.Channel { - case "ticker": - var ticker TickerStreamData - - err = common.JSONDecode(multiStreamData.Data, &ticker) - if err != nil { - o.Websocket.DataHandler <- err - continue - } - - o.Websocket.DataHandler <- exchange.TickerData{ - Timestamp: time.Unix(0, int64(ticker.Timestamp)), - Exchange: o.GetName(), - AssetType: assetType, - } - case "deals": - var deals DealsStreamData - - err = common.JSONDecode(multiStreamData.Data, &deals) - if err != nil { - o.Websocket.DataHandler <- err - continue - } - - for _, trade := range deals { - price, _ := strconv.ParseFloat(trade[1], 64) - amount, _ := strconv.ParseFloat(trade[2], 64) - tradeTime, _ := time.Parse(time.RFC3339, trade[3]) - - o.Websocket.DataHandler <- exchange.TradeData{ - Timestamp: tradeTime, - Exchange: o.GetName(), - AssetType: assetType, - CurrencyPair: pair.NewCurrencyPairFromString(newPair), - Price: price, - Amount: amount, - EventType: trade[4], - } - } - case "kline": - var klines KlineStreamData - - err := common.JSONDecode(multiStreamData.Data, &klines) - if err != nil { - o.Websocket.DataHandler <- err - continue - } - - for _, kline := range klines { - ntime, _ := strconv.ParseInt(kline[0], 10, 64) - open, _ := strconv.ParseFloat(kline[1], 64) - high, _ := strconv.ParseFloat(kline[2], 64) - low, _ := strconv.ParseFloat(kline[3], 64) - klineClose, _ := strconv.ParseFloat(kline[4], 64) - volume, _ := strconv.ParseFloat(kline[5], 64) - - o.Websocket.DataHandler <- exchange.KlineData{ - Timestamp: time.Unix(ntime, 0), - Pair: pair.NewCurrencyPairFromString(newPair), - AssetType: assetType, - Exchange: o.GetName(), - OpenPrice: open, - HighPrice: high, - LowPrice: low, - ClosePrice: klineClose, - Volume: volume, - } - } - case "depth": - var depth DepthStreamData - - err := common.JSONDecode(multiStreamData.Data, &depth) - if err != nil { - o.Websocket.DataHandler <- err - continue - } - - o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ - Exchange: o.GetName(), - Asset: assetType, - Pair: pair.NewCurrencyPairFromString(newPair), - } - } - } - } - } -} - -// ErrorResponse defines an error response type from the websocket connection -type ErrorResponse struct { - Result bool `json:"result"` - ErrorMsg string `json:"error_msg"` - ErrorCode int64 `json:"error_code"` -} - -// Request defines the JSON request structure to the websocket server -type Request struct { - Event string `json:"event"` - Channel string `json:"channel"` - Parameters string `json:"parameters,omitempty"` -} diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go deleted file mode 100644 index adb52fad..00000000 --- a/exchanges/okex/okex_wrapper.go +++ /dev/null @@ -1,394 +0,0 @@ -package okex - -import ( - "errors" - "fmt" - "strconv" - "strings" - "sync" - "time" - - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/currency/pair" - exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" -) - -// Start starts the OKEX go routine -func (o *OKEX) Start(wg *sync.WaitGroup) { - wg.Add(1) - go func() { - o.Run() - wg.Done() - }() -} - -// Run implements the OKEX wrapper -func (o *OKEX) Run() { - if o.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) - log.Debugf("%s polling delay: %ds.\n", o.GetName(), o.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", o.GetName(), len(o.EnabledPairs), o.EnabledPairs) - } - - prods, err := o.GetSpotInstruments() - if err != nil { - log.Errorf("OKEX failed to obtain available spot instruments. Err: %s", err) - return - } - - var pairs []string - for x := range prods { - pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency) - } - - err = o.UpdateCurrencies(pairs, false, false) - if err != nil { - log.Errorf("OKEX failed to update available currencies. Err: %s", err) - return - } -} - -// UpdateTicker updates and returns the ticker for a currency pair -func (o *OKEX) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { - currency := exchange.FormatExchangeCurrency(o.Name, p).String() - var tickerPrice ticker.Price - - if assetType != ticker.Spot { - tick, err := o.GetContractPrice(currency, assetType) - if err != nil { - return tickerPrice, err - } - - tickerPrice.Pair = p - tickerPrice.Ask = tick.Ticker.Sell - tickerPrice.Bid = tick.Ticker.Buy - tickerPrice.Low = tick.Ticker.Low - tickerPrice.Last = tick.Ticker.Last - tickerPrice.Volume = tick.Ticker.Vol - tickerPrice.High = tick.Ticker.High - ticker.ProcessTicker(o.GetName(), p, tickerPrice, assetType) - } else { - tick, err := o.GetSpotTicker(currency) - if err != nil { - return tickerPrice, err - } - tickerPrice.Pair = p - tickerPrice.Ask = tick.Ticker.Sell - tickerPrice.Bid = tick.Ticker.Buy - tickerPrice.Low = tick.Ticker.Low - tickerPrice.Last = tick.Ticker.Last - tickerPrice.Volume = tick.Ticker.Vol - tickerPrice.High = tick.Ticker.High - ticker.ProcessTicker(o.GetName(), p, tickerPrice, ticker.Spot) - - } - return ticker.GetTicker(o.Name, p, assetType) -} - -// GetTickerPrice returns the ticker for a currency pair -func (o *OKEX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(o.GetName(), p, assetType) - if err != nil { - return o.UpdateTicker(p, assetType) - } - return tickerNew, nil -} - -// GetOrderbookEx returns orderbook base on the currency pair -func (o *OKEX) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) { - ob, err := orderbook.GetOrderbook(o.GetName(), currency, assetType) - if err != nil { - return o.UpdateOrderbook(currency, assetType) - } - return ob, nil -} - -// UpdateOrderbook updates and returns the orderbook for a currency pair -func (o *OKEX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { - var orderBook orderbook.Base - currency := exchange.FormatExchangeCurrency(o.Name, p).String() - - if assetType != ticker.Spot { - orderbookNew, err := o.GetContractMarketDepth(currency, assetType) - if err != nil { - return orderBook, err - } - - for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Volume, Price: data.Price}) - } - - for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Volume, Price: data.Price}) - } - - } else { - orderbookNew, err := o.GetSpotMarketDepth(ActualSpotDepthRequestParams{ - Symbol: currency, - Size: 200, - }) - if err != nil { - return orderBook, err - } - - for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Volume, Price: data.Price}) - } - - for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Volume, Price: data.Price}) - } - } - - orderbook.ProcessOrderbook(o.GetName(), p, orderBook, assetType) - return orderbook.GetOrderbook(o.Name, p, assetType) -} - -// GetAccountInfo retrieves balances for all enabled currencies for the -// OKEX exchange -func (o *OKEX) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo - bal, err := o.GetBalance() - if err != nil { - return info, err - } - - var balances []exchange.AccountCurrencyInfo - for _, data := range bal { - balances = append(balances, exchange.AccountCurrencyInfo{ - CurrencyName: data.Currency, - TotalValue: data.Available + data.Hold, - Hold: data.Hold, - }) - } - - info.Exchange = o.GetName() - info.Accounts = append(info.Accounts, exchange.Account{ - Currencies: balances, - }) - - return info, nil -} - -// GetFundingHistory returns funding history, deposits and -// withdrawals -func (o *OKEX) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported -} - -// GetExchangeHistory returns historic trade data since exchange opening. -func (o *OKEX) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented -} - -// SubmitOrder submits a new order -func (o *OKEX) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - var oT SpotNewOrderRequestType - - switch orderType { - case exchange.LimitOrderType: - oT = SpotNewOrderRequestTypeSell - if side == exchange.BuyOrderSide { - oT = SpotNewOrderRequestTypeBuy - } - case exchange.MarketOrderType: - oT = SpotNewOrderRequestTypeSellMarket - if side == exchange.BuyOrderSide { - oT = SpotNewOrderRequestTypeBuyMarket - } - default: - return submitOrderResponse, errors.New("unsupported order type") - } - - var params = SpotNewOrderRequestParams{ - Amount: amount, - Price: price, - Symbol: p.Pair().String(), - Type: oT, - } - - response, err := o.SpotNewOrder(params) - - if response > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response) - } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - - return submitOrderResponse, err -} - -// ModifyOrder will allow of changing orderbook placement and limit to -// market conversion -func (o *OKEX) ModifyOrder(action exchange.ModifyOrder) (string, error) { - return "", common.ErrFunctionNotSupported -} - -// CancelOrder cancels an order by its corresponding ID number -func (o *OKEX) CancelOrder(order exchange.OrderCancellation) error { - orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) - if err != nil { - return err - } - - _, err = o.SpotCancelOrder(exchange.FormatExchangeCurrency(o.Name, order.CurrencyPair).String(), orderIDInt) - return err -} - -// CancelAllOrders cancels all orders for all enabled currencies -func (o *OKEX) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), - } - var allOpenOrders []TokenOrder - for _, currency := range o.GetEnabledCurrencies() { - formattedCurrency := exchange.FormatExchangeCurrency(o.Name, currency).String() - openOrders, err := o.GetTokenOrders(formattedCurrency, -1) - if err != nil { - return cancelAllOrdersResponse, err - } - - if !openOrders.Result { - return cancelAllOrdersResponse, fmt.Errorf("something went wrong for currency %s", formattedCurrency) - } - - allOpenOrders = append(allOpenOrders, openOrders.Orders...) - } - - for _, openOrder := range allOpenOrders { - _, err := o.SpotCancelOrder(openOrder.Symbol, openOrder.OrderID) - if err != nil { - cancelAllOrdersResponse.OrderStatus[strconv.FormatInt(openOrder.OrderID, 10)] = err.Error() - } - } - - return cancelAllOrdersResponse, nil -} - -// GetOrderInfo returns information on a current open order -func (o *OKEX) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail - return orderDetail, common.ErrNotYetImplemented -} - -// GetDepositAddress returns a deposit address for a specified currency -func (o *OKEX) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { - // NOTE needs API version update to access - return "", common.ErrNotYetImplemented -} - -// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is -// submitted -func (o *OKEX) WithdrawCryptocurrencyFunds(withdrawRequest exchange.WithdrawRequest) (string, error) { - resp, err := o.Withdrawal(withdrawRequest.Currency.String(), withdrawRequest.FeeAmount, withdrawRequest.TradePassword, withdrawRequest.Address, withdrawRequest.Amount) - return fmt.Sprintf("%v", resp), err -} - -// WithdrawFiatFunds returns a withdrawal ID when a -// withdrawal is submitted -func (o *OKEX) WithdrawFiatFunds(withdrawRequest exchange.WithdrawRequest) (string, error) { - return "", common.ErrFunctionNotSupported -} - -// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a -// withdrawal is submitted -func (o *OKEX) WithdrawFiatFundsToInternationalBank(withdrawRequest exchange.WithdrawRequest) (string, error) { - return "", common.ErrFunctionNotSupported -} - -// GetWebsocket returns a pointer to the exchange websocket -func (o *OKEX) GetWebsocket() (*exchange.Websocket, error) { - return o.Websocket, nil -} - -// GetFeeByType returns an estimate of fee based on type of transaction -func (o *OKEX) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { - return o.GetFee(feeBuilder) -} - -// GetActiveOrders retrieves any orders that are active/open -func (o *OKEX) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var allOrders []OrderInfo - for _, currency := range getOrdersRequest.Currencies { - resp, err := o.GetOrderHistoryForCurrency(200, 0, 0, exchange.FormatExchangeCurrency(o.Name, currency).String()) - if err != nil { - return nil, err - } - - allOrders = append(allOrders, resp.Orders...) - } - - var orders []exchange.OrderDetail - for _, order := range allOrders { - // Status 2 == Filled, -1 == Cancelled. - if order.Status == 2 || order.Status == -1 { - continue - } - - symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter) - orderDate := time.Unix(order.Created, 0) - side := exchange.OrderSide(strings.ToUpper(order.Type)) - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.OrderID), - Amount: order.Amount, - OrderDate: orderDate, - Price: order.Price, - OrderSide: side, - CurrencyPair: symbol, - Exchange: o.Name, - }) - } - - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - - return orders, nil -} - -// GetOrderHistory retrieves account order information -// Can Limit response to specific order status -func (o *OKEX) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var allOrders []OrderInfo - for _, currency := range getOrdersRequest.Currencies { - resp, err := o.GetOrderInformation(-1, exchange.FormatExchangeCurrency(o.Name, currency).String()) - if err != nil { - return nil, err - } - - allOrders = append(allOrders, resp...) - } - - var orders []exchange.OrderDetail - for _, order := range allOrders { - symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter) - orderDate := time.Unix(order.Created, 0) - side := exchange.OrderSide(strings.ToUpper(order.Type)) - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.OrderID), - Amount: order.Amount, - OrderDate: orderDate, - Price: order.Price, - OrderSide: side, - CurrencyPair: symbol, - Exchange: o.Name, - }) - } - - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - - return orders, nil -} diff --git a/exchanges/okgroup/README.md b/exchanges/okgroup/README.md new file mode 100644 index 00000000..aa5451cb --- /dev/null +++ b/exchanges/okgroup/README.md @@ -0,0 +1,133 @@ +# GoCryptoTrader package Okex + + + + +[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader/exchanges/okex) +[![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader) + + +This okex package is part of the GoCryptoTrader codebase. + +## This is still in active development + +You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://gocryptotrader.herokuapp.com/) + +## OKex Exchange + +### Current Features + ++ REST Support + +### How to enable + ++ [Enable via configuration](https://github.com/thrasher-/gocryptotrader/tree/master/config#enable-exchange-via-config-example) + ++ Individual package example below: + +```go + // Exchanges will be abstracted out in further updates and examples will be + // supplied then +``` + +### How to do REST public/private calls + ++ If enabled via "configuration".json file the exchange will be added to the +IBotExchange array in the ```go var bot Bot``` and you will only be able to use +the wrapper interface functions for accessing exchange data. View routines.go +for an example of integration usage with GoCryptoTrader. Rudimentary example +below: + +main.go +```go +var o exchange.IBotExchange + +for i := range bot.exchanges { + if bot.exchanges[i].GetName() == "OKex" { + y = bot.exchanges[i] + } +} + +// Public calls - wrapper functions + +// Fetches current ticker information +tick, err := o.GetTickerPrice() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := o.GetOrderbookEx() +if err != nil { + // Handle error +} + +// Private calls - wrapper functions - make sure your APIKEY and APISECRET are +// set and AuthenticatedAPISupport is set to true + +// Fetches current account information +accountInfo, err := o.GetAccountInfo() +if err != nil { + // Handle error +} +``` + ++ If enabled via individually importing package, rudimentary example below: + +```go +// Public calls + +// Fetches current ticker information +ticker, err := o.GetSpotTicker() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := o.GetSpotMarketDepth() +if err != nil { + // Handle error +} + +// Private calls - make sure your APIKEY and APISECRET are set and +// AuthenticatedAPISupport is set to true + +// GetContractPosition returns contract positioning +accountInfo, err := o.GetContractPosition(...) +if err != nil { + // Handle error +} + +// Submits an order and the exchange and returns its tradeID +tradeID, err := o.PlaceContractOrders(...) +if err != nil { + // Handle error +} +``` + +### Please click GoDocs chevron above to view current GoDoc information for this package + +## Contribution + +Please feel free to submit any pull requests or suggest any desired features to be added. + +When submitting a PR, please abide by our coding guidelines: + ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. + +## Donations + + + +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: + +***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** + diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go new file mode 100644 index 00000000..83d5dd6b --- /dev/null +++ b/exchanges/okgroup/okgroup.go @@ -0,0 +1,840 @@ +package okgroup + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "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" + log "github.com/thrasher-/gocryptotrader/logger" +) + +const ( + okGroupAuthRate = 0 + okGroupUnauthRate = 0 + // OKGroupAPIPath const to help with api url formatting + OKGroupAPIPath = "api/" + // API subsections + okGroupAccountSubsection = "account" + okGroupTokenSubsection = "spot" + okGroupMarginTradingSubsection = "margin" + // OKGroupAccounts common api endpoint + OKGroupAccounts = "accounts" + // OKGroupLedger common api endpoint + OKGroupLedger = "ledger" + // OKGroupOrders common api endpoint + OKGroupOrders = "orders" + // OKGroupBatchOrders common api endpoint + OKGroupBatchOrders = "batch_orders" + // OKGroupCancelOrders common api endpoint + OKGroupCancelOrders = "cancel_orders" + // OKGroupCancelOrder common api endpoint + OKGroupCancelOrder = "cancel_order" + // OKGroupCancelBatchOrders common api endpoint + OKGroupCancelBatchOrders = "cancel_batch_orders" + // OKGroupPendingOrders common api endpoint + OKGroupPendingOrders = "orders_pending" + // OKGroupTrades common api endpoint + OKGroupTrades = "trades" + // OKGroupTicker common api endpoint + OKGroupTicker = "ticker" + // OKGroupInstruments common api endpoint + OKGroupInstruments = "instruments" + // OKGroupLiquidation common api endpoint + OKGroupLiquidation = "liquidation" + // OKGroupMarkPrice common api endpoint + OKGroupMarkPrice = "mark_price" + // OKGroupGetAccountDepositHistory common api endpoint + OKGroupGetAccountDepositHistory = "deposit/history" + // OKGroupGetSpotTransactionDetails common api endpoint + OKGroupGetSpotTransactionDetails = "fills" + // OKGroupGetSpotOrderBook common api endpoint + OKGroupGetSpotOrderBook = "book" + // OKGroupGetSpotMarketData common api endpoint + OKGroupGetSpotMarketData = "candles" + // OKGroupPriceLimit common api endpoint + OKGroupPriceLimit = "price_limit" + // Account based endpoints + okGroupGetAccountCurrencies = "currencies" + okGroupGetAccountWalletInformation = "wallet" + okGroupFundsTransfer = "transfer" + okGroupWithdraw = "withdrawal" + okGroupGetWithdrawalFees = "withdrawal/fee" + okGroupGetWithdrawalHistory = "withdrawal/history" + okGroupGetDepositAddress = "deposit/address" + // Margin based endpoints + okGroupGetMarketAvailability = "availability" + okGroupGetLoanHistory = "borrowed" + okGroupGetLoan = "borrow" + okGroupGetRepayment = "repayment" +) + +var errMissValue = errors.New("warning - resp value is missing from exchange") + +// OKGroup is the overaching type across the all of OKEx's exchange methods +type OKGroup struct { + exchange.Base + ExchangeName string + WebsocketConn *websocket.Conn + mu sync.Mutex + // Spot and contract market error codes as per https://www.okex.com/rest_request.html + ErrorCodes map[string]error + // Stores for corresponding variable checks + ContractTypes []string + CurrencyPairs []string + ContractPosition []string + Types []string + // URLs to be overridden by implementations of OKGroup + APIURL string + APIVersion string + WebsocketURL string +} + +// Setup method sets current configuration details if enabled +func (o *OKGroup) Setup(exch config.ExchangeConfig) { + if !exch.Enabled { + o.SetEnabled(false) + } else { + o.Name = exch.Name + o.Enabled = true + o.AuthenticatedAPISupport = exch.AuthenticatedAPISupport + o.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, false) + o.SetHTTPClientTimeout(exch.HTTPTimeout) + o.SetHTTPClientUserAgent(exch.HTTPUserAgent) + o.RESTPollingDelay = exch.RESTPollingDelay + o.Verbose = exch.Verbose + o.Websocket.SetWsStatusAndConnection(exch.Websocket) + o.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") + o.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") + o.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := o.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } + err = o.SetAssetTypes() + if err != nil { + log.Fatal(err) + } + err = o.SetAutoPairDefaults() + if err != nil { + log.Fatal(err) + } + err = o.SetAPIURL(exch) + if err != nil { + log.Fatal(err) + } + err = o.SetClientProxyAddress(exch.ProxyAddress) + if err != nil { + log.Fatal(err) + } + err = o.WebsocketSetup(o.WsConnect, + exch.Name, + exch.Websocket, + o.WebsocketURL, + exch.WebsocketURL) + if err != nil { + log.Fatal(err) + } + } +} + +// GetAccountCurrencies returns a list of tradable spot instruments and their properties +func (o *OKGroup) GetAccountCurrencies() (resp []GetAccountCurrenciesResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, okGroupGetAccountCurrencies, nil, &resp, true) +} + +// GetAccountWalletInformation returns a list of wallets and their properties +func (o *OKGroup) GetAccountWalletInformation(currency string) (resp []WalletInformationResponse, _ error) { + var requestURL string + if currency != "" { + requestURL = fmt.Sprintf("%v/%v", okGroupGetAccountWalletInformation, currency) + } else { + requestURL = okGroupGetAccountWalletInformation + } + + return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true) +} + +// TransferAccountFunds the transfer of funds between wallet, trading accounts, main account and sub accounts. +func (o *OKGroup) TransferAccountFunds(request TransferAccountFundsRequest) (resp TransferAccountFundsResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodPost, okGroupAccountSubsection, okGroupFundsTransfer, request, &resp, true) +} + +// AccountWithdraw withdrawal of tokens to OKCoin International, other OKEx accounts or other addresses. +func (o *OKGroup) AccountWithdraw(request AccountWithdrawRequest) (resp AccountWithdrawResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodPost, okGroupAccountSubsection, okGroupWithdraw, request, &resp, true) +} + +// GetAccountWithdrawalFee retrieves the information about the recommended network transaction fee for withdrawals to digital asset addresses. The higher the fees are, the sooner the confirmations you will get. +func (o *OKGroup) GetAccountWithdrawalFee(currency string) (resp []GetAccountWithdrawalFeeResponse, _ error) { + var requestURL string + if currency != "" { + requestURL = fmt.Sprintf("%v?currency=%v", okGroupGetWithdrawalFees, currency) + } else { + requestURL = okGroupGetAccountWalletInformation + } + + return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true) +} + +// GetAccountWithdrawalHistory retrieves all recent withdrawal records. +func (o *OKGroup) GetAccountWithdrawalHistory(currency string) (resp []WithdrawalHistoryResponse, _ error) { + var requestURL string + if currency != "" { + requestURL = fmt.Sprintf("%v/%v", okGroupGetWithdrawalHistory, currency) + } else { + requestURL = okGroupGetWithdrawalHistory + } + return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true) +} + +// GetAccountBillDetails retrieves the bill details of the wallet. All the information will be paged and sorted in reverse chronological order, +// which means the latest will be at the top. Please refer to the pagination section for additional records after the first page. +// 3 months recent records will be returned at maximum +func (o *OKGroup) GetAccountBillDetails(request GetAccountBillDetailsRequest) (resp []GetAccountBillDetailsResponse, _ error) { + requestURL := fmt.Sprintf("%v%v", OKGroupLedger, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true) +} + +// GetAccountDepositAddressForCurrency retrieves the deposit addresses of different tokens, including previously used addresses. +func (o *OKGroup) GetAccountDepositAddressForCurrency(currency string) (resp []GetDepositAddressResponse, _ error) { + urlValues := url.Values{} + urlValues.Set("currency", currency) + requestURL := fmt.Sprintf("%v?%v", okGroupGetDepositAddress, urlValues.Encode()) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true) +} + +// GetAccountDepositHistory retrieves the deposit history of all tokens.100 recent records will be returned at maximum +func (o *OKGroup) GetAccountDepositHistory(currency string) (resp []GetAccountDepositHistoryResponse, _ error) { + var requestURL string + if currency != "" { + requestURL = fmt.Sprintf("%v/%v", OKGroupGetAccountDepositHistory, currency) + } else { + requestURL = okGroupGetWithdrawalHistory + } + return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true) +} + +// GetSpotTradingAccounts retrieves the list of assets(only show pairs with balance larger than 0), the balances, amount available/on hold in spot accounts. +func (o *OKGroup) GetSpotTradingAccounts() (resp []GetSpotTradingAccountResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, OKGroupAccounts, nil, &resp, true) +} + +// GetSpotTradingAccountForCurrency This endpoint supports getting the balance, amount available/on hold of a token in spot account. +func (o *OKGroup) GetSpotTradingAccountForCurrency(currency string) (resp GetSpotTradingAccountResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, currency) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true) +} + +// GetSpotBillDetailsForCurrency This endpoint supports getting the balance, amount available/on hold of a token in spot account. +func (o *OKGroup) GetSpotBillDetailsForCurrency(request GetSpotBillDetailsForCurrencyRequest) (resp []GetSpotBillDetailsForCurrencyResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupAccounts, request.Currency, OKGroupLedger, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true) +} + +// PlaceSpotOrder token trading only supports limit and market orders (more order types will become available in the future). +// You can place an order only if you have enough funds. +// Once your order is placed, the amount will be put on hold. +func (o *OKGroup) PlaceSpotOrder(request PlaceSpotOrderRequest) (resp PlaceSpotOrderResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodPost, okGroupTokenSubsection, OKGroupOrders, request, &resp, true) +} + +// PlaceMultipleSpotOrders supports placing multiple orders for specific trading pairs +// up to 4 trading pairs, maximum 4 orders for each pair +func (o *OKGroup) PlaceMultipleSpotOrders(request []PlaceSpotOrderRequest) (map[string][]PlaceSpotOrderResponse, []error) { + currencyPairOrders := make(map[string]int) + resp := make(map[string][]PlaceSpotOrderResponse) + for _, order := range request { + currencyPairOrders[order.InstrumentID]++ + } + if len(currencyPairOrders) > 4 { + return resp, []error{errors.New("up to 4 trading pairs")} + } + for _, orderCount := range currencyPairOrders { + if orderCount > 4 { + return resp, []error{errors.New("maximum 4 orders for each pair")} + } + } + + err := o.SendHTTPRequest(http.MethodPost, okGroupTokenSubsection, OKGroupBatchOrders, request, &resp, true) + if err != nil { + return resp, []error{err} + } + + orderErrors := []error{} + for currency, orderResponse := range resp { + for _, order := range orderResponse { + if !order.Result { + orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency)) + } + } + } + if len(orderErrors) == 0 { + orderErrors = nil + } + + return resp, orderErrors +} + +// CancelSpotOrder Cancelling an unfilled order. +func (o *OKGroup) CancelSpotOrder(request CancelSpotOrderRequest) (resp CancelSpotOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", OKGroupCancelOrders, request.OrderID) + return resp, o.SendHTTPRequest(http.MethodPost, okGroupTokenSubsection, requestURL, request, &resp, true) +} + +// CancelMultipleSpotOrders Cancelling multiple unfilled orders. +func (o *OKGroup) CancelMultipleSpotOrders(request CancelMultipleSpotOrdersRequest) (resp map[string][]CancelMultipleSpotOrdersResponse, err error) { + resp = make(map[string][]CancelMultipleSpotOrdersResponse) + if len(request.OrderIDs) > 4 { + return resp, errors.New("maximum 4 order cancellations for each pair") + } + + err = o.SendHTTPRequest(http.MethodPost, okGroupTokenSubsection, OKGroupCancelBatchOrders, []CancelMultipleSpotOrdersRequest{request}, &resp, true) + if err != nil { + return + } + + for currency, orderResponse := range resp { + for _, order := range orderResponse { + cancellationResponse := CancelMultipleSpotOrdersResponse{ + OrderID: order.OrderID, + Result: order.Result, + ClientOID: order.ClientOID, + } + + if !order.Result { + cancellationResponse.Error = fmt.Errorf("order %v for currency %v failed to be cancelled", order.OrderID, currency) + } + + resp[currency] = append(resp[currency], cancellationResponse) + } + } + + return +} + +// GetSpotOrders List your orders. Cursor pagination is used. +// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKGroup) GetSpotOrders(request GetSpotOrdersRequest) (resp []GetSpotOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v%v", OKGroupOrders, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true) +} + +// GetSpotOpenOrders List all your current open orders. Cursor pagination is used. +// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKGroup) GetSpotOpenOrders(request GetSpotOpenOrdersRequest) (resp []GetSpotOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v%v", OKGroupPendingOrders, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true) +} + +// GetSpotOrder Get order details by order ID. +func (o *OKGroup) GetSpotOrder(request GetSpotOrderRequest) (resp GetSpotOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v%v", OKGroupOrders, request.OrderID, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, request, &resp, true) +} + +// GetSpotTransactionDetails Get details of the recent filled orders. Cursor pagination is used. +// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKGroup) GetSpotTransactionDetails(request GetSpotTransactionDetailsRequest) (resp []GetSpotTransactionDetailsResponse, _ error) { + requestURL := fmt.Sprintf("%v%v", OKGroupGetSpotTransactionDetails, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true) +} + +// GetSpotTokenPairDetails Get market data. This endpoint provides the snapshots of market data and can be used without verifications. +// List trading pairs and get the trading limit, price, and more information of different trading pairs. +func (o *OKGroup) GetSpotTokenPairDetails() (resp []GetSpotTokenPairDetailsResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, OKGroupInstruments, nil, &resp, true) +} + +// GetSpotOrderBook Getting the order book of a trading pair. Pagination is not supported here. +// The whole book will be returned for one request. WebSocket is recommended here. +func (o *OKGroup) GetSpotOrderBook(request GetSpotOrderBookRequest) (resp GetSpotOrderBookResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupGetSpotOrderBook, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true) +} + +// GetSpotAllTokenPairsInformation Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all trading pairs. +func (o *OKGroup) GetSpotAllTokenPairsInformation() (resp []GetSpotTokenPairsInformationResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", OKGroupInstruments, OKGroupTicker) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true) +} + +// GetSpotAllTokenPairsInformationForCurrency Get the last traded price, best bid/ask price, 24 hour trading volume and more info of a currency +func (o *OKGroup) GetSpotAllTokenPairsInformationForCurrency(currency string) (resp GetSpotTokenPairsInformationResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v", OKGroupInstruments, currency, OKGroupTicker) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true) +} + +// GetSpotFilledOrdersInformation Get the recent 60 transactions of all trading pairs. +// Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKGroup) GetSpotFilledOrdersInformation(request GetSpotFilledOrdersInformationRequest) (resp []GetSpotFilledOrdersInformationResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupTrades, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true) +} + +// GetSpotMarketData Get the charts of the trading pairs. Charts are returned in grouped buckets based on requested granularity. +func (o *OKGroup) GetSpotMarketData(request GetSpotMarketDataRequest) (resp GetSpotMarketDataResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupGetSpotMarketData, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true) +} + +// GetMarginTradingAccounts List all assets under token margin trading account, including information such as balance, amount on hold and more. +func (o *OKGroup) GetMarginTradingAccounts() (resp []GetMarginAccountsResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, OKGroupAccounts, nil, &resp, true) +} + +// GetMarginTradingAccountsForCurrency Get the balance, amount on hold and more useful information. +func (o *OKGroup) GetMarginTradingAccountsForCurrency(currency string) (resp GetMarginAccountsResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, currency) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true) +} + +// GetMarginBillDetails List all bill details. Pagination is used here. +// before and after cursor arguments should not be confused with before and after in chronological time. +// Most paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKGroup) GetMarginBillDetails(request GetMarginBillDetailsRequest) (resp []GetSpotBillDetailsForCurrencyResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupAccounts, request.InstrumentID, OKGroupLedger, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true) +} + +// GetMarginAccountSettings Get all information of the margin trading account, +// including the maximum loan amount, interest rate, and maximum leverage. +func (o *OKGroup) GetMarginAccountSettings(currency string) (resp []GetMarginAccountSettingsResponse, _ error) { + var requestURL string + if currency != "" { + requestURL = fmt.Sprintf("%v/%v/%v", OKGroupAccounts, currency, okGroupGetMarketAvailability) + } else { + requestURL = fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetMarketAvailability) + } + return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true) +} + +// GetMarginLoanHistory Get loan history of the margin trading account. +// Pagination is used here. before and after cursor arguments should not be confused with before and after in chronological time. +// Most paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKGroup) GetMarginLoanHistory(request GetMarginLoanHistoryRequest) (resp []GetMarginLoanHistoryResponse, _ error) { + var requestURL string + if len(request.InstrumentID) > 0 { + requestURL = fmt.Sprintf("%v/%v/%v", OKGroupAccounts, request.InstrumentID, okGroupGetLoan) + } else { + requestURL = fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetLoan) + } + return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true) +} + +// OpenMarginLoan Borrowing tokens in a margin trading account. +func (o *OKGroup) OpenMarginLoan(request OpenMarginLoanRequest) (resp OpenMarginLoanResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetLoan) + return resp, o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, requestURL, request, &resp, true) +} + +// RepayMarginLoan Repaying tokens in a margin trading account. +func (o *OKGroup) RepayMarginLoan(request RepayMarginLoanRequest) (resp RepayMarginLoanResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetRepayment) + return resp, o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, requestURL, request, &resp, true) +} + +// PlaceMarginOrder OKEx API only supports limit and market orders (more orders will become available in the future). +// You can place an order only if you have enough funds. Once your order is placed, the amount will be put on hold. +func (o *OKGroup) PlaceMarginOrder(request PlaceSpotOrderRequest) (resp PlaceSpotOrderResponse, _ error) { + return resp, o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, OKGroupOrders, request, &resp, true) +} + +// PlaceMultipleMarginOrders Place multiple orders for specific trading pairs (up to 4 trading pairs, maximum 4 orders each) +func (o *OKGroup) PlaceMultipleMarginOrders(request []PlaceSpotOrderRequest) (map[string][]PlaceSpotOrderResponse, []error) { + currencyPairOrders := make(map[string]int) + resp := make(map[string][]PlaceSpotOrderResponse) + for _, order := range request { + currencyPairOrders[order.InstrumentID]++ + } + if len(currencyPairOrders) > 4 { + return resp, []error{errors.New("up to 4 trading pairs")} + } + for _, orderCount := range currencyPairOrders { + if orderCount > 4 { + return resp, []error{errors.New("maximum 4 orders for each pair")} + } + } + + err := o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, OKGroupBatchOrders, request, &resp, true) + if err != nil { + return resp, []error{err} + } + + orderErrors := []error{} + for currency, orderResponse := range resp { + for _, order := range orderResponse { + if !order.Result { + orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency)) + } + } + } + if len(orderErrors) == 0 { + orderErrors = nil + } + + return resp, orderErrors +} + +// CancelMarginOrder Cancelling an unfilled order. +func (o *OKGroup) CancelMarginOrder(request CancelSpotOrderRequest) (resp CancelSpotOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v", OKGroupCancelOrders, request.OrderID) + return resp, o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, requestURL, request, &resp, true) +} + +// CancelMultipleMarginOrders Cancelling multiple unfilled orders. +func (o *OKGroup) CancelMultipleMarginOrders(request CancelMultipleSpotOrdersRequest) (map[string][]CancelMultipleSpotOrdersResponse, []error) { + resp := make(map[string][]CancelMultipleSpotOrdersResponse) + if len(request.OrderIDs) > 4 { + return resp, []error{errors.New("maximum 4 order cancellations for each pair")} + } + + err := o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, OKGroupCancelBatchOrders, []CancelMultipleSpotOrdersRequest{request}, &resp, true) + if err != nil { + return resp, []error{err} + } + + orderErrors := []error{} + for currency, orderResponse := range resp { + for _, order := range orderResponse { + if !order.Result { + orderErrors = append(orderErrors, fmt.Errorf("order %v for currency %v failed to be cancelled", order.OrderID, currency)) + } + } + } + if len(orderErrors) == 0 { + orderErrors = nil + } + + return resp, orderErrors +} + +// GetMarginOrders List your orders. Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKGroup) GetMarginOrders(request GetSpotOrdersRequest) (resp []GetSpotOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v%v", OKGroupOrders, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true) +} + +// GetMarginOpenOrders List all your current open orders. Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKGroup) GetMarginOpenOrders(request GetSpotOpenOrdersRequest) (resp []GetSpotOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v%v", OKGroupPendingOrders, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true) +} + +// GetMarginOrder Get order details by order ID. +func (o *OKGroup) GetMarginOrder(request GetSpotOrderRequest) (resp GetSpotOrderResponse, _ error) { + requestURL := fmt.Sprintf("%v/%v%v", OKGroupOrders, request.OrderID, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, request, &resp, true) +} + +// GetMarginTransactionDetails Get details of the recent filled orders. Cursor pagination is used. +// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first. +func (o *OKGroup) GetMarginTransactionDetails(request GetSpotTransactionDetailsRequest) (resp []GetSpotTransactionDetailsResponse, _ error) { + requestURL := fmt.Sprintf("%v%v", OKGroupGetSpotTransactionDetails, FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true) +} + +// FormatParameters Formats URL parameters, useful for optional parameters due to OKEX signature check +func FormatParameters(request interface{}) (parameters string) { + v, err := query.Values(request) + if err != nil { + log.Errorf("Could not parse %v to URL values. Check that the type has url fields", reflect.TypeOf(request).Name()) + return + } + urlEncodedValues := v.Encode() + if len(urlEncodedValues) > 0 { + parameters = fmt.Sprintf("?%v", urlEncodedValues) + } + return +} + +// GetErrorCode returns an error code +func (o *OKGroup) GetErrorCode(code interface{}) error { + var assertedCode string + + switch reflect.TypeOf(code).String() { + case "float64": + assertedCode = strconv.FormatFloat(code.(float64), 'f', -1, 64) + case "string": + assertedCode = code.(string) + default: + return errors.New("unusual type returned") + } + + if i, ok := o.ErrorCodes[assertedCode]; ok { + return i + } + return errors.New("unable to find SPOT error code") +} + +// SendHTTPRequest sends an authenticated http request to a desired +// path with a JSON payload (of present) +// URL arguments must be in the request path and not as url.URL values +func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, data, result interface{}, authenticated bool) (err error) { + if authenticated && !o.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, o.Name) + } + + utcTime := time.Now().UTC() + iso := utcTime.String() + isoBytes := []byte(iso) + iso = string(isoBytes[:10]) + "T" + string(isoBytes[11:23]) + "Z" + payload := []byte("") + + if data != nil { + payload, err = common.JSONEncode(data) + if err != nil { + return errors.New("sendHTTPRequest: Unable to JSON request") + } + + if o.Verbose { + log.Debugf("Request JSON: %s\n", payload) + } + } + + path := o.APIUrl + requestType + o.APIVersion + requestPath + if o.Verbose { + log.Debugf("Sending %v request to %s \n", requestType, path) + } + + headers := make(map[string]string) + headers["Content-Type"] = "application/json" + if authenticated { + signPath := fmt.Sprintf("/%v%v%v%v", OKGroupAPIPath, requestType, o.APIVersion, requestPath) + hmac := common.GetHMAC(common.HashSHA256, []byte(iso+httpMethod+signPath+string(payload)), []byte(o.APISecret)) + base64 := common.Base64Encode(hmac) + headers["OK-ACCESS-KEY"] = o.APIKey + headers["OK-ACCESS-SIGN"] = base64 + headers["OK-ACCESS-TIMESTAMP"] = iso + headers["OK-ACCESS-PASSPHRASE"] = o.ClientID + } + + var intermediary json.RawMessage + type errCapFormat struct { + Error int64 `json:"error_code,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` + Result bool `json:"result,string,omitempty"` + } + + errCap := errCapFormat{} + errCap.Result = true + err = o.SendPayload(strings.ToUpper(httpMethod), path, headers, bytes.NewBuffer(payload), &intermediary, authenticated, o.Verbose) + if err != nil { + return err + } + + err = common.JSONDecode(intermediary, &errCap) + if err == nil { + if errCap.ErrorMessage != "" { + return fmt.Errorf("error: %v", errCap.ErrorMessage) + } + if errCap.Error > 0 { + return fmt.Errorf("sendHTTPRequest error - %s", + o.ErrorCodes[strconv.FormatInt(errCap.Error, 10)]) + } + if !errCap.Result { + return errors.New("unspecified error occurred") + } + + } + + return common.JSONDecode(intermediary, result) +} + +// SetCheckVarDefaults sets main variables that will be used in requests because +// api does not return an error if there are misspellings in strings. So better +// to check on this, this end. +func (o *OKGroup) SetCheckVarDefaults() { + o.ContractTypes = []string{"this_week", "next_week", "quarter"} + o.CurrencyPairs = []string{"btc_usd", "ltc_usd", "eth_usd", "etc_usd", "bch_usd"} + o.Types = []string{"1min", "3min", "5min", "15min", "30min", "1day", "3day", + "1week", "1hour", "2hour", "4hour", "6hour", "12hour"} + o.ContractPosition = []string{"1", "2", "3", "4"} +} + +// GetFee returns an estimate of fee based on type of transaction +func (o *OKGroup) GetFee(feeBuilder exchange.FeeBuilder) (fee float64, _ error) { + switch feeBuilder.FeeType { + case exchange.CryptocurrencyTradeFee: + fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount, feeBuilder.IsMaker) + case exchange.CryptocurrencyWithdrawalFee: + withdrawFees, err := o.GetAccountWithdrawalFee(feeBuilder.CurrencyItem) + if err != nil { + return -1, err + } + for _, withdrawFee := range withdrawFees { + if withdrawFee.Currency == feeBuilder.CurrencyItem { + fee = withdrawFee.MinFee + break + } + } + } + if fee < 0 { + fee = 0 + } + + return fee, nil +} + +func calculateTradingFee(purchasePrice, amount float64, isMaker bool) (fee float64) { + // TODO volume based fees + if isMaker { + fee = 0.001 + } else { + fee = 0.0015 + } + return fee * amount * purchasePrice +} + +// SetErrorDefaults sets the full error default list +func (o *OKGroup) SetErrorDefaults() { + o.ErrorCodes = map[string]error{ + "0": errors.New("successful"), + "1": errors.New("invalid parameter in url normally"), + "30001": errors.New("request header \"OK_ACCESS_KEY\" cannot be blank"), + "30002": errors.New("request header \"OK_ACCESS_SIGN\" cannot be blank"), + "30003": errors.New("request header \"OK_ACCESS_TIMESTAMP\" cannot be blank"), + "30004": errors.New("request header \"OK_ACCESS_PASSPHRASE\" cannot be blank"), + "30005": errors.New("invalid OK_ACCESS_TIMESTAMP"), + "30006": errors.New("invalid OK_ACCESS_KEY"), + "30007": errors.New("invalid Content_Type, please use \"application/json\" format"), + "30008": errors.New("timestamp request expired"), + "30009": errors.New("system error"), + "30010": errors.New("api validation failed"), + "30011": errors.New("invalid IP"), + "30012": errors.New("invalid authorization"), + "30013": errors.New("invalid sign"), + "30014": errors.New("request too frequent"), + "30015": errors.New("request header \"OK_ACCESS_PASSPHRASE\" incorrect"), + "30016": errors.New("you are using v1 apiKey, please use v1 endpoint. If you would like to use v3 endpoint, please subscribe to v3 apiKey"), + "30017": errors.New("apikey's broker id does not match"), + "30018": errors.New("apikey's domain does not match"), + "30020": errors.New("body cannot be blank"), + "30021": errors.New("json data format error"), + "30023": errors.New("required parameter cannot be blank"), + "30024": errors.New("parameter value error"), + "30025": errors.New("parameter category error"), + "30026": errors.New("requested too frequent; endpoint limit exceeded"), + "30027": errors.New("login failure"), + "30028": errors.New("unauthorized execution"), + "30029": errors.New("account suspended"), + "30030": errors.New("endpoint request failed. Please try again"), + "30031": errors.New("token does not exist"), + "30032": errors.New("pair does not exist"), + "30033": errors.New("exchange domain does not exist"), + "30034": errors.New("exchange ID does not exist"), + "30035": errors.New("trading is not supported in this website"), + "30036": errors.New("no relevant data"), + "30037": errors.New("endpoint is offline or unavailable"), + "30038": errors.New("user does not exist"), + "32001": errors.New("futures account suspended"), + "32002": errors.New("futures account does not exist"), + "32003": errors.New("canceling, please wait"), + "32004": errors.New("you have no unfilled orders"), + "32005": errors.New("max order quantity"), + "32006": errors.New("the order price or trigger price exceeds USD 1 million"), + "32007": errors.New("leverage level must be the same for orders on the same side of the contract"), + "32008": errors.New("max. positions to open (cross margin)"), + "32009": errors.New("max. positions to open (fixed margin)"), + "32010": errors.New("leverage cannot be changed with open positions"), + "32011": errors.New("futures status error"), + "32012": errors.New("futures order update error"), + "32013": errors.New("token type is blank"), + "32014": errors.New("your number of contracts closing is larger than the number of contracts available"), + "32015": errors.New("margin ratio is lower than 100% before opening positions"), + "32016": errors.New("margin ratio is lower than 100% after opening position"), + "32017": errors.New("no BBO"), + "32018": errors.New("the order quantity is less than 1, please try again"), + "32019": errors.New("the order price deviates from the price of the previous minute by more than 3%"), + "32020": errors.New("the price is not in the range of the price limit"), + "32021": errors.New("leverage error"), + "32022": errors.New("this function is not supported in your country or region according to the regulations"), + "32023": errors.New("this account has outstanding loan"), + "32024": errors.New("order cannot be placed during delivery"), + "32025": errors.New("order cannot be placed during settlement"), + "32026": errors.New("your account is restricted from opening positions"), + "32027": errors.New("cancelled over 20 orders"), + "32028": errors.New("account is suspended and liquidated"), + "32029": errors.New("order info does not exist"), + "33001": errors.New("margin account for this pair is not enabled yet"), + "33002": errors.New("margin account for this pair is suspended"), + "33003": errors.New("no loan balance"), + "33004": errors.New("loan amount cannot be smaller than the minimum limit"), + "33005": errors.New("repayment amount must exceed 0"), + "33006": errors.New("loan order not found"), + "33007": errors.New("status not found"), + "33008": errors.New("loan amount cannot exceed the maximum limit"), + "33009": errors.New("user ID is blank"), + "33010": errors.New("you cannot cancel an order during session 2 of call auction"), + "33011": errors.New("no new market data"), + "33012": errors.New("order cancellation failed"), + "33013": errors.New("order placement failed"), + "33014": errors.New("order does not exist"), + "33015": errors.New("exceeded maximum limit"), + "33016": errors.New("margin trading is not open for this token"), + "33017": errors.New("insufficient balance"), + "33018": errors.New("this parameter must be smaller than 1"), + "33020": errors.New("request not supported"), + "33021": errors.New("token and the pair do not match"), + "33022": errors.New("pair and the order do not match"), + "33023": errors.New("you can only place market orders during call auction"), + "33024": errors.New("trading amount too small"), + "33025": errors.New("base token amount is blank"), + "33026": errors.New("transaction completed"), + "33027": errors.New("cancelled order or order cancelling"), + "33028": errors.New("the decimal places of the trading price exceeded the limit"), + "33029": errors.New("the decimal places of the trading size exceeded the limit"), + "34001": errors.New("withdrawal suspended"), + "34002": errors.New("please add a withdrawal address"), + "34003": errors.New("sorry, this token cannot be withdrawn to xx at the moment"), + "34004": errors.New("withdrawal fee is smaller than minimum limit"), + "34005": errors.New("withdrawal fee exceeds the maximum limit"), + "34006": errors.New("withdrawal amount is lower than the minimum limit"), + "34007": errors.New("withdrawal amount exceeds the maximum limit"), + "34008": errors.New("insufficient balance"), + "34009": errors.New("your withdrawal amount exceeds the daily limit"), + "34010": errors.New("transfer amount must be larger than 0"), + "34011": errors.New("conditions not met"), + "34012": errors.New("the minimum withdrawal amount for NEO is 1, and the amount must be an integer"), + "34013": errors.New("please transfer"), + "34014": errors.New("transfer limited"), + "34015": errors.New("subaccount does not exist"), + "34016": errors.New("transfer suspended"), + "34017": errors.New("account suspended"), + "34018": errors.New("incorrect trades password"), + "34019": errors.New("please bind your email before withdrawal"), + "34020": errors.New("please bind your funds password before withdrawal"), + "34021": errors.New("not verified address"), + "34022": errors.New("withdrawals are not available for sub accounts"), + "35001": errors.New("contract subscribing does not exist"), + "35002": errors.New("contract is being settled"), + "35003": errors.New("contract is being paused"), + "35004": errors.New("pending contract settlement"), + "35005": errors.New("perpetual swap trading is not enabled"), + "35008": errors.New("margin ratio too low when placing order"), + "35010": errors.New("closing position size larger than available size"), + "35012": errors.New("placing an order with less than 1 contract"), + "35014": errors.New("order size is not in acceptable range"), + "35015": errors.New("leverage level unavailable"), + "35017": errors.New("changing leverage level"), + "35019": errors.New("order size exceeds limit"), + "35020": errors.New("order price exceeds limit"), + "35021": errors.New("order size exceeds limit of the current tier"), + "35022": errors.New("contract is paused or closed"), + "35030": errors.New("place multiple orders"), + "35031": errors.New("cancel multiple orders"), + "35061": errors.New("invalid instrument_id"), + } +} diff --git a/exchanges/okgroup/okgroup_types.go b/exchanges/okgroup/okgroup_types.go new file mode 100644 index 00000000..cdd6cb8b --- /dev/null +++ b/exchanges/okgroup/okgroup_types.go @@ -0,0 +1,1514 @@ +package okgroup + +import ( + "time" +) + +// GetAccountCurrenciesResponse response data for GetAccountCurrencies +type GetAccountCurrenciesResponse struct { + CanDeposit int64 `json:"can_deposit"` + CanWithdraw int64 `json:"can_withdraw"` + Currency string `json:"currency"` + MinWithdrawal float64 `json:"min_withdrawal"` + Name string `json:"name"` +} + +// WalletInformationResponse response data for WalletInformation +type WalletInformationResponse struct { + Available float64 `json:"available"` + Balance float64 `json:"balance"` + Currency string `json:"currency"` + Hold float64 `json:"hold"` +} + +// TransferAccountFundsRequest request data for TransferAccountFunds +type TransferAccountFundsRequest struct { + Currency string `json:"currency"` // [required] token + Amount float64 `json:"amount"` // [required] Transfer amount + From int64 `json:"from"` // [required] the remitting account (0: sub account 1: spot 3: futures 4:C2C 5: margin 6: wallet 7:ETT 8:PiggyBank 9:swap) + To int64 `json:"to"` // [required] the beneficiary account(0: sub account 1:spot 3: futures 4:C2C 5: margin 6: wallet 7:ETT 8:PiggyBank 9 :swap) + SubAccountID string `json:"sub_account,omitempty"` // [optional] sub account name + InstrumentID int64 `json:"instrument_id,omitempty"` // [optional] margin token pair ID, for supported pairs only +} + +// TransferAccountFundsResponse response data for TransferAccountFunds +type TransferAccountFundsResponse struct { + Amount float64 `json:"amount"` + Currency string `json:"currency"` + From int64 `json:"from"` + Result bool `json:"result"` + To int64 `json:"to"` + TransferID int64 `json:"transfer_id"` +} + +// AccountWithdrawRequest request data for AccountWithdrawRequest +type AccountWithdrawRequest struct { + Amount float64 `json:"amount"` // [required] withdrawal amount + Currency string `json:"currency"` // [required] token + Destination int64 `json:"destination"` // [required] withdrawal address(2:OKCoin International 3:OKEx 4:others) + Fee float64 `json:"fee"` // [required] Network transaction fee≥0. Withdrawals to OKCoin or OKEx are fee-free, please set as 0. Withdrawal to external digital asset address requires network transaction fee. + ToAddress string `json:"to_address"` // [required] verified digital asset address, email or mobile number,some digital asset address format is address+tag , eg: "ARDOR-7JF3-8F2E-QUWZ-CAN7F:123456" + TradePwd string `json:"trade_pwd"` // [required] fund password +} + +// AccountWithdrawResponse response data for AccountWithdrawResponse +type AccountWithdrawResponse struct { + Amount float64 `json:"amount"` + Currency string `json:"currency"` + Result bool `json:"result"` + WithdrawalID int64 `json:"withdrawal_id"` +} + +// GetAccountWithdrawalFeeResponse response data for GetAccountWithdrawalFee +type GetAccountWithdrawalFeeResponse struct { + Currency string `json:"currency"` + MinFee float64 `json:"min_fee"` + MaxFee float64 `json:"max_fee"` +} + +// WithdrawalHistoryResponse response data for WithdrawalHistoryResponse +type WithdrawalHistoryResponse struct { + Amount float64 `json:"amount"` + Currency string `json:"currency"` + Fee string `json:"fee"` + From string `json:"from"` + Status int64 `json:"status"` + Timestamp time.Time `json:"timestamp"` + To string `json:"to"` + Txid string `json:"txid"` + PaymentID string `json:"payment_id"` + Tag string `json:"tag"` +} + +// GetAccountBillDetailsRequest request data for GetAccountBillDetailsRequest +type GetAccountBillDetailsRequest struct { + Currency string `url:"currency,omitempty"` // [optional] token ,information of all tokens will be returned if the field is left blank + Type int64 `url:"type,omitempty"` // [optional] 1:deposit 2:withdrawal 13:cancel withdrawal 18: into futures account 19: out of futures account 20:into sub account 21:out of sub account 28: claim 29: into ETT account 30: out of ETT account 31: into C2C account 32:out of C2C account 33: into margin account 34: out of margin account 37: into spot account 38: out of spot account + From int64 `url:"from,omitempty"` // [optional] you would request pages after this page. + To int64 `url:"to,omitempty"` // [optional] you would request pages before this page + Limit int64 `url:"limit,omitempty"` // [optional] Number of results per request. Maximum 100. (default 100) +} + +// GetAccountBillDetailsResponse response data for GetAccountBillDetails +type GetAccountBillDetailsResponse struct { + Amount float64 `json:"amount"` + Balance int64 `json:"balance"` + Currency string `json:"currency"` + Fee int64 `json:"fee"` + LedgerID int64 `json:"ledger_id"` + Timestamp time.Time `json:"timestamp"` + Typename string `json:"typename"` +} + +// GetDepositAddressResponse response data for GetDepositAddress +type GetDepositAddressResponse struct { + Address string `json:"address"` + Tag string `json:"tag"` + PaymentID string `json:"payment_id,omitempty"` + Currency string `json:"currency"` +} + +// GetAccountDepositHistoryResponse response data for GetAccountDepositHistory +type GetAccountDepositHistoryResponse struct { + Amount float64 `json:"amount"` + Currency string `json:"currency"` + Status int64 `json:"status"` + Timestamp time.Time `json:"timestamp"` + To string `json:"to"` + TransactionID string `json:"txid"` +} + +// GetSpotTradingAccountResponse response data for GetSpotTradingAccount +type GetSpotTradingAccountResponse struct { + Available string `json:"available"` + Balance string `json:"balance"` + Currency string `json:"currency"` + Frozen string `json:"frozen"` + Hold string `json:"hold"` + Holds string `json:"holds"` + ID string `json:"id"` +} + +// GetSpotBillDetailsForCurrencyRequest request data for GetSpotBillDetailsForCurrency +type GetSpotBillDetailsForCurrencyRequest struct { + Currency string `url:"-"` // [required] token + From int64 `url:"from,string,omitempty"` // [optional] request page before(newer) this id. + To int64 `url:"to,string,omitempty"` // [optional] request page after(older) this id. + Limit int64 `url:"limit,string,omitempty"` // [optional] number of results per request. Maximum 100.(default 100) +} + +// GetSpotBillDetailsForCurrencyResponse response data for GetSpotBillDetailsForCurrency +type GetSpotBillDetailsForCurrencyResponse struct { + LedgerID string `json:"ledger_id"` + Balance string `json:"balance"` + CurrencyResponse string `json:"currency"` + Amount string `json:"amount"` + Type string `json:"type"` + TimeStamp time.Time `json:"timestamp"` + Details SpotBillDetails `json:"details"` +} + +// SpotBillDetails response data for GetSpotBillDetailsForCurrency +type SpotBillDetails struct { + OrderID string `json:"order_id"` + InstrumentID string `json:"instrument_id"` +} + +// PlaceSpotOrderRequest request data for PlaceSpotOrder +type PlaceSpotOrderRequest struct { + ClientOID string `json:"client_oid,omitempty"` // the order ID customized by yourself + Type string `json:"type"` // limit / market(default: limit) + Side string `json:"side"` // buy or sell + InstrumentID string `json:"instrument_id"` // trading pair + MarginTrading string `json:"margin_trading"` // order type (The request value is 1) + Size string `json:"size"` + Notional string `json:"notional,omitempty"` // + Price string `json:"price,omitempty"` // price (Limit order only) +} + +// PlaceSpotOrderResponse response data for PlaceSpotOrder +type PlaceSpotOrderResponse struct { + ClientOid string `json:"client_oid"` + OrderID string `json:"order_id"` + Result bool `json:"result"` +} + +// CancelSpotOrderRequest request data for CancelSpotOrder +type CancelSpotOrderRequest struct { + ClientOID string `json:"client_oid,omitempty"` // the order ID created by yourself + OrderID int64 `json:"order_id,string"` // order ID + InstrumentID string `json:"instrument_id"` // By providing this parameter, the corresponding order of a designated trading pair will be cancelled. If not providing this parameter, it will be back to error code. +} + +// CancelSpotOrderResponse response data for CancelSpotOrder +type CancelSpotOrderResponse struct { + ClientOID string `json:"client_oid"` + OrderID int64 `json:"order_id"` + Result bool `json:"result"` +} + +// CancelMultipleSpotOrdersRequest request data for CancelMultipleSpotOrders +type CancelMultipleSpotOrdersRequest struct { + OrderIDs []int64 `json:"order_ids,omitempty"` // order ID. You may cancel up to 4 orders of a trading pair + InstrumentID string `json:"instrument_id"` // by providing this parameter, the corresponding order of a designated trading pair will be cancelled. If not providing this parameter, it will be back to error code. +} + +// CancelMultipleSpotOrdersResponse response data for CancelMultipleSpotOrders +type CancelMultipleSpotOrdersResponse struct { + ClientOID string `json:"client_oid"` + OrderID int64 `json:"order_id,string"` + Result bool `json:"result"` + Error error // Placeholder to store errors +} + +// GetSpotOrdersRequest request data for GetSpotOrders +type GetSpotOrdersRequest struct { + Status string `url:"status"` // list the status of all orders (all, open, part_filled, canceling, filled, cancelled,ordering,failure) + // (Multiple status separated by '|',and '|' need encode to ' %7C') + InstrumentID string `url:"instrument_id"` // trading pair ,information of all trading pair will be returned if the field is left blank + From int64 `url:"from,string,omitempty"` // [optional] request page after this id (latest information) (eg. 1, 2, 3, 4, 5. There is only a 5 "from 4", while there are 1, 2, 3 "to 4") + To int64 `url:"to,string,omitempty"` // [optional] request page after (older) this id. + Limit int64 `url:"limit,string,omitempty"` // [optional] number of results per request. Maximum 100. (default 100) +} + +// GetSpotOrderResponse response data for GetSpotOrders +type GetSpotOrderResponse struct { + FilledNotional float64 `json:"filled_notional,string"` + FilledSize float64 `json:"filled_size,string"` + InstrumentID string `json:"instrument_id"` + Notional float64 `json:"notional,string"` + OrderID string `json:"order_id"` + Price float64 `json:"price,string"` + Side string `json:"side"` + Size float64 `json:"size,string"` + Status string `json:"status"` + Timestamp time.Time `json:"timestamp"` + Type string `json:"type"` +} + +// GetSpotOpenOrdersRequest request data for GetSpotOpenOrders +type GetSpotOpenOrdersRequest struct { + InstrumentID string `json:"instrument_id"` // [optional] trading pair ,information of all trading pair will be returned if the field is left blank + From int64 `json:"from,string,omitempty"` // [optional] request page after this id (latest information) (eg. 1, 2, 3, 4, 5. There is only a 5 "from 4", while there are 1, 2, 3 "to 4") + To int64 `json:"to,string,omitempty"` // [optional] request page after (older) this id. + Limit int64 `json:"limit,string,omitempty"` // [optional] number of results per request. Maximum 100. (default 100) +} + +// GetSpotOrderRequest request data for GetSpotOrder +type GetSpotOrderRequest struct { + OrderID string `url:"-"` // [required] order ID + InstrumentID string `url:"instrument_id"` // [required]trading pair +} + +// GetSpotTransactionDetailsRequest request data for GetSpotTransactionDetails +type GetSpotTransactionDetailsRequest struct { + InstrumentID string `url:"instrument_id"` // [required]list all transaction details of this instrument_id. + OrderID int64 `url:"order_id,string"` // [required]list all transaction details of this order_id. + From int64 `url:"from,string,omitempty"` // [optional] request page after this id (latest information) (eg. 1, 2, 3, 4, 5. There is only a 5 "from 4", while there are 1, 2, 3 "to 4") + To int64 `url:"to,string,omitempty"` // [optional] request page after (older) this id. + Limit int64 `url:"limit,string,omitempty"` // [optional] number of results per request. Maximum 100. (default 100) +} + +// GetSpotTransactionDetailsResponse response data for GetSpotTransactionDetails +type GetSpotTransactionDetailsResponse struct { + ExecType string `json:"exec_type"` + Fee string `json:"fee"` + InstrumentID string `json:"instrument_id"` + LedgerID string `json:"ledger_id"` + OrderID string `json:"order_id"` + Price string `json:"price"` + Side string `json:"side"` + Size string `json:"size"` + Timestamp time.Time `json:"timestamp"` +} + +// GetSpotTokenPairDetailsResponse response data for GetSpotTokenPairDetails +type GetSpotTokenPairDetailsResponse struct { + BaseCurrency string `json:"base_currency"` + InstrumentID string `json:"instrument_id"` + MinSize string `json:"min_size"` + QuoteCurrency string `json:"quote_currency"` + SizeIncrement string `json:"size_increment"` + TickSize string `json:"tick_size"` +} + +// GetSpotOrderBookRequest request data for GetSpotOrderBook +type GetSpotOrderBookRequest struct { + Size int64 `url:"size,string,omitempty"` // [optional] number of results per request. Maximum 200 + Depth float64 `url:"depth,string,omitempty"` // [optional] the aggregation of the book. e.g . 0.1,0.001 + InstrumentID string `url:"-"` // [required] trading pairs +} + +// GetSpotOrderBookResponse response data for GetSpotOrderBook +type GetSpotOrderBookResponse struct { + Timestamp time.Time `json:"timestamp"` + Asks [][]string `json:"asks"` // [[0]: "Price", [1]: "Size", [2]: "Num_orders"], ... + Bids [][]string `json:"bids"` // [[0]: "Price", [1]: "Size", [2]: "Num_orders"], ... +} + +// GetSpotTokenPairsInformationResponse response data for GetSpotTokenPairsInformation +type GetSpotTokenPairsInformationResponse struct { + BaseVolume24h float64 `json:"base_volume_24h,string"` // 24 trading volume of the base currency + BestAsk float64 `json:"best_ask,string"` // best ask price + BestBid float64 `json:"best_bid,string"` // best bid price + High24h float64 `json:"high_24h,string"` // 24 hour high + InstrumentID string `json:"instrument_id"` // trading pair + Last float64 `json:"last,string"` // last traded price + Low24h float64 `json:"low_24h,string"` // 24 hour low + Open24h float64 `json:"open_24h,string"` // 24 hour open + QuoteVolume24h float64 `json:"quote_volume_24h,string"` // 24 trading volume of the quote currency + Timestamp time.Time `json:"timestamp"` +} + +// GetSpotFilledOrdersInformationRequest request data for GetSpotFilledOrdersInformation +type GetSpotFilledOrdersInformationRequest struct { + InstrumentID string `url:"-"` // [required] trading pairs + From int64 `url:"from,string,omitempty"` // [optional] number of results per request. Maximum 100. (default 100) + To int64 `url:"to,string,omitempty"` // [optional] request page after (older) this id. + Limit int64 `url:"limit,string,omitempty"` // [optional] number of results per request. Maximum 100. (default 100) +} + +// GetSpotFilledOrdersInformationResponse response data for GetSpotFilledOrdersInformation +type GetSpotFilledOrdersInformationResponse struct { + Price string `json:"price"` + Side string `json:"side"` + Size string `json:"size"` + Timestamp time.Time `json:"timestamp"` + TradeID string `json:"trade_id"` +} + +// GetSpotMarketDataRequest request data for GetSpotMarketData +type GetSpotMarketDataRequest struct { + Start string `url:"start,omitempty"` // [optional] start time in ISO 8601 + End string `url:"end,omitempty"` // [optional] end time in ISO 8601 + Granularity int64 `url:"granularity"` // The granularity field must be one of the following values: {60, 180, 300, 900, 1800, 3600, 7200, 14400, 43200, 86400, 604800}. + InstrumentID string `url:"-"` // [required] trading pairs +} + +// GetSpotMarketDataResponse response data for GetSpotMarketData +// Return Parameters +// time string Start time +// open string Open price +// high string Highest price +// low string Lowest price +// close string Close price +// volume string Trading volume +type GetSpotMarketDataResponse []interface{} + +// GetMarginAccountsResponse response data for GetMarginAccounts +type GetMarginAccountsResponse struct { + InstrumentID string `json:"instrument_id,omitempty"` + LiquidationPrice string `json:"liquidation_price"` + ProductID string `json:"product_id,omitempty"` + RiskRate string `json:"risk_rate"` + Currencies map[string]MarginAccountInfo +} + +// MarginAccountInfo contains individual currency information +type MarginAccountInfo struct { + Available float64 `json:"available,string"` + Balance float64 `json:"balance,string"` + Borrowed float64 `json:"borrowed,string"` + Frozen float64 `json:"frozen,string"` + Hold float64 `json:"hold,string"` + Holds float64 `json:"holds,string"` + LendingFee float64 `json:"lending_fee,string"` +} + +// GetMarginAccountSettingsResponse response data for GetMarginAccountSettings +type GetMarginAccountSettingsResponse struct { + InstrumentID string `json:"instrument_id"` + ProductID string `json:"product_id"` + Currencies map[string]MarginAccountSettingsInfo +} + +// GetMarginBillDetailsRequest request data for GetMarginBillDetails +type GetMarginBillDetailsRequest struct { + InstrumentID string `url:"-"` // [required] trading pair + Type int64 `url:"type,omitempty"` // [optional] 1:deposit 2:withdrawal 13:cancel withdrawal 18: into futures account 19: out of futures account 20:into sub account 21:out of sub account 28: claim 29: into ETT account 30: out of ETT account 31: into C2C account 32:out of C2C account 33: into margin account 34: out of margin account 37: into spot account 38: out of spot account + From int64 `url:"from,omitempty"` // [optional] you would request pages after this page. + To int64 `url:"to,omitempty"` // [optional] you would request pages before this page + Limit int64 `url:"limit,omitempty"` // [optional] Number of results per request. Maximum 100. (default 100) +} + +// MarginAccountSettingsInfo contains individual currency data +type MarginAccountSettingsInfo struct { + Available float64 `json:"available,string"` + Leverage float64 `json:"leverage,string"` + LeverageRatio float64 `json:"leverage_ratio,string"` + Rate float64 `json:"rate,string"` +} + +// GetMarginLoanHistoryRequest request data for GetMarginLoanHistory +type GetMarginLoanHistoryRequest struct { + InstrumentID string // [optional] Used when a specific currency response is desired + Status int64 `json:"status,string,omitempty"` // [optional] status(0: outstanding 1: repaid) + From int64 `json:"from,string,omitempty"` // [optional] request page from(newer) this id. + To int64 `json:"to,string,omitempty"` // [optional] request page to(older) this id. + Limit int64 `json:"limit,string,omitempty"` // [optional] number of results per request. Maximum 100.(default 100) +} + +// GetMarginLoanHistoryResponse response data for GetMarginLoanHistory +type GetMarginLoanHistoryResponse struct { + Amount float64 `json:"amount,string"` + BorrowID int64 `json:"borrow_id"` + CreatedAt string `json:"created_at"` + Currency string `json:"currency"` + ForceRepayTime string `json:"force_repay_time"` + InstrumentID string `json:"instrument_id"` + Interest float64 `json:"interest,string"` + LastInterestTime string `json:"last_interest_time"` + PaidInterest float64 `json:"paid_interest,string"` + ProductID string `json:"product_id"` + Rate float64 `json:"rate,string"` + RepayAmount string `json:"repay_amount"` + RepayInterest string `json:"repay_interest"` + ReturnedAmount float64 `json:"returned_amount,string"` + Timestamp time.Time `json:"timestamp"` +} + +// OpenMarginLoanRequest request data for OpenMarginLoan +type OpenMarginLoanRequest struct { + QuoteCurrency string `json:"currency"` // [required] Second currency eg BTC-USDT: USDT is quote + InstrumentID string `json:"instrument_id"` // [required] Full pair BTC-USDT + Amount float64 `json:"amount,string"` // [required] Amount wanting to borrow +} + +// OpenMarginLoanResponse response data for OpenMarginLoan +type OpenMarginLoanResponse struct { + BorrowID int64 `json:"borrow_id"` + Result bool `json:"result"` +} + +// RepayMarginLoanRequest request data for RepayMarginLoan +type RepayMarginLoanRequest struct { + Amount float64 `json:"amount,string"` // [required] amount repaid + BorrowID float64 `json:"borrow_id"` // [optional] borrow ID . all borrowed token under this trading pair will be repay if the field is left blank + QuoteCurrency string `json:"currency"` // [required] Second currency eg BTC-USDT: USDT is quote + InstrumentID string `json:"instrument_id"` // [required] Full pair BTC-USDT +} + +// RepayMarginLoanResponse response data for RepayMarginLoan +type RepayMarginLoanResponse struct { + RepaymentID int64 `json:"repayment_id"` + Result bool `json:"result"` +} + +// GetFuturesPositionsResponse response data for GetFuturesPositions +type GetFuturesPositionsResponse struct { + Holding [][]GetFuturePostionsDetails `json:"holding"` + Result bool `json:"result"` +} + +// GetFuturesPositionsForCurrencyResponse response data for GetFuturesPositionsForCurrency +type GetFuturesPositionsForCurrencyResponse struct { + Holding []GetFuturePostionsDetails `json:"holding"` + Result bool `json:"result"` +} + +// GetFuturePostionsDetails Futures details +type GetFuturePostionsDetails struct { + CreatedAt string `json:"created_at"` + InstrumentID string `json:"instrument_id"` + Leverage string `json:"leverage"` + LiquidationPrice string `json:"liquidation_price"` + LongAvailQty string `json:"long_avail_qty"` + LongAvgCost string `json:"long_avg_cost"` + LongLeverage string `json:"long_leverage"` + LongLiquiPrice string `json:"long_liqui_price"` + LongMargin string `json:"long_margin"` + LongPnlRatio string `json:"long_pnl_ratio"` + LongQty string `json:"long_qty"` + LongSettlementPrice string `json:"long_settlement_price"` + MarginMode string `json:"margin_mode"` + RealisedPnl string `json:"realised_pnl"` + ShortAvailQty string `json:"short_avail_qty"` + ShortAvgCost string `json:"short_avg_cost"` + ShortLeverage string `json:"short_leverage"` + ShortLiquiPrice string `json:"short_liqui_price"` + ShortMargin string `json:"short_margin"` + ShortPnlRatio string `json:"short_pnl_ratio"` + ShortQty string `json:"short_qty"` + ShortSettlementPrice string `json:"short_settlement_price"` + UpdatedAt string `json:"updated_at"` +} + +// FuturesAccountForAllCurrenciesResponse response data for FuturesAccountForAllCurrencies +type FuturesAccountForAllCurrenciesResponse struct { + Info struct { + Currency map[string]FuturesCurrencyData + } `json:"info"` +} + +// FuturesCurrencyData Futures details +type FuturesCurrencyData struct { + Contracts []FuturesContractsData `json:"contracts,omitempty"` + Equity string `json:"equity,omitempty"` + Margin string `json:"margin,omitempty"` + MarginMode string `json:"margin_mode,omitempty"` + MarginRatio string `json:"margin_ratio,omitempty"` + RealizedPnl string `json:"realized_pnl,omitempty"` + TotalAvailBalance string `json:"total_avail_balance,omitempty"` + UnrealizedPnl string `json:"unrealized_pnl,omitempty"` +} + +// FuturesContractsData Futures details +type FuturesContractsData struct { + AvailableQty string `json:"available_qty"` + FixedBalance string `json:"fixed_balance"` + InstrumentID string `json:"instrument_id"` + MarginForUnfilled string `json:"margin_for_unfilled"` + MarginFrozen string `json:"margin_frozen"` + RealizedPnl string `json:"realized_pnl"` + UnrealizedPnl string `json:"unrealized_pnl"` +} + +// GetFuturesLeverageResponse response data for GetFuturesLeverage +type GetFuturesLeverageResponse struct { + MarginMode string `json:"margin_mode,omitempty"` + Currency string `json:"currency,omitempty"` + Leverage int64 `json:"leverage,omitempty"` + LeveragePerCoin map[string]GetFuturesLeverageData +} + +// GetFuturesLeverageData Futures details +type GetFuturesLeverageData struct { + LongLeverage int64 `json:"long_leverage"` + ShortLeverage int64 `json:"short_leverage"` +} + +// SetFuturesLeverageRequest request data for SetFuturesLeverage +type SetFuturesLeverageRequest struct { + Direction string `json:"direction,omitempty"` // opening side (long or short) + InstrumentID string `json:"instrument_id,omitempty"` // Contract ID, e.g. "BTC-USD-180213" + Leverage int64 `json:"leverage,omitempty"` // 10x or 20x leverage + Currency string `json:"currency,omitempty"` +} + +// SetFuturesLeverageResponse returned data for SetFuturesLeverage +type SetFuturesLeverageResponse struct { + Currency string `json:"currency"` + Leverage int64 `json:"leverage"` + MarginMode string `json:"margin_mode"` + Result string `json:"result"` + Direction string `json:"direction"` + ShortLongDataPerContract map[string]SetFutureLeverageShortLongData +} + +// SetFutureLeverageShortLongData long and short data from SetFuturesLeverage +type SetFutureLeverageShortLongData struct { + Long int `json:"long"` + Short int `json:"short"` +} + +// PlaceFuturesOrderRequest request data for PlaceFuturesOrder +type PlaceFuturesOrderRequest struct { + ClientOid string `json:"client_oid,omitempty"` // [optional] the order ID customized by yourself + InstrumentID string `json:"instrument_id"` // [required] Contract ID,e.g. "TC-USD-180213" + Type int64 `json:"type,string"` // [required] 1:open long 2:open short 3:close long 4:close short + Price float64 `json:"price,string"` // [required] Price of each contract + Size int64 `json:"size,string"` // [required] The buying or selling quantity + MatchPrice int64 `json:"match_price,string,omitempty"` // [optional] Order at best counter party price? (0:no 1:yes) the parameter is defaulted as 0. If it is set as 1, the price parameter will be ignored + Leverage int64 `json:"leverage,string"` // [required] 10x or 20x leverage +} + +// PlaceFuturesOrderResponse response data for PlaceFuturesOrder +type PlaceFuturesOrderResponse struct { + ClientOid string `json:"client_oid"` + ErrorCode int `json:"error_code"` + ErrorMesssage string `json:"error_messsage"` + OrderID string `json:"order_id"` + Result bool `json:"result"` +} + +// PlaceFuturesOrderBatchRequest request data for PlaceFuturesOrderBatch +type PlaceFuturesOrderBatchRequest struct { + InstrumentID string `json:"instrument_id"` // [required] Contract ID, e.g."BTC-USD-180213" + Leverage int `json:"leverage"` // [required] 10x or 20x leverage + OrdersData []PlaceFuturesOrderBatchRequestDetails `json:"orders_data"` // [required] the JSON word string for placing multiple orders, include:{client_oid type price size match_price} +} + +// PlaceFuturesOrderBatchRequestDetails individual order details for PlaceFuturesOrderBatchRequest +type PlaceFuturesOrderBatchRequestDetails struct { + ClientOid string `json:"client_oid"` // [required] To identify your order with the order ID set by you + MatchPrice string `json:"match_price"` // undocumented + Price string `json:"price"` // undocumented + Size string `json:"size"` // undocumented + Type string `json:"type"` // undocumented +} + +// PlaceFuturesOrderBatchResponse response data from PlaceFuturesOrderBatch +type PlaceFuturesOrderBatchResponse struct { + OrderInfo []PlaceFuturesOrderBatchResponseData `json:"order_info"` + Result bool `json:"result"` +} + +// PlaceFuturesOrderBatchResponseData individual order details from PlaceFuturesOrderBatchResponse +type PlaceFuturesOrderBatchResponseData struct { + ClientOid string `json:"client_oid"` + ErrorCode int `json:"error_code"` + ErrorMessage string `json:"error_message"` + OrderID float64 `json:"order_id"` +} + +// CancelFuturesOrderRequest request data for CancelFuturesOrder +type CancelFuturesOrderRequest struct { + OrderID string `json:"order_id"` // [required] Order ID + InstrumentID string `json:"instrument_id"` // [required] Contract ID,e.g. "BTC-USD-180213" +} + +// CancelFuturesOrderResponse response data from CancelFuturesOrder +type CancelFuturesOrderResponse struct { + InstrumentID string `json:"instrument_id"` + OrderID string `json:"order_id"` + Result bool `json:"result"` +} + +// GetFuturesOrdersListRequest request data for GetFutureOrdersList +type GetFuturesOrdersListRequest struct { + InstrumentID string `url:"-"` // [required] Contract ID, e.g. "BTC-USD-180213" + Status int64 `url:"status,string"` // [required] Order Status (-1 canceled; 0: pending, 1: partially filled, 2: fully filled, 6: open (pending partially + fully filled), 7: completed (canceled + fully filled)) + From int64 `url:"from,string,omitempty"` // [optional] Request paging content for this page number.(Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + To int64 `url:"to,string,omitempty"` // [optional] Request page after (older) this pagination id. (Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + Limit int64 `url:"limit,string,omitempty"` // [optional] Number of results per request. Maximum 100. (default 100) +} + +// GetFuturesOrderListResponse response data from GetFuturesOrderList +type GetFuturesOrderListResponse struct { + OrderInfo []GetFuturesOrderDetailsResponseData `json:"order_info"` + Result bool `json:"result"` +} + +// GetFuturesOrderDetailsResponseData individual order data from GetFuturesOrderList +type GetFuturesOrderDetailsResponseData struct { + ContractVal float64 `json:"contract_val,string"` + Fee float64 `json:"fee,string"` + FilledQty float64 `json:"filled_qty,string"` + InstrumentID string `json:"instrument_id"` + Leverage int64 `json:"leverage,string"` // Leverage value:10\20 default:10 + OrderID int64 `json:"order_id,string"` + Price float64 `json:"price,string"` + PriceAvg float64 `json:"price_avg,string"` + Size float64 `json:"size,string"` + Status int64 `json:"status,string"` // Order Status (-1 canceled; 0: pending, 1: partially filled, 2: fully filled) + Timestamp time.Time `json:"timestamp"` + Type int64 `json:"type,string"` // Type (1: open long 2: open short 3: close long 4: close short) +} + +// GetFuturesOrderDetailsRequest request data for GetFuturesOrderDetails +type GetFuturesOrderDetailsRequest struct { + OrderID int64 `json:"order_id,string"` // [required] Order ID + InstrumentID string `json:"instrument_id"` // [required] Contract ID, e.g. "BTC-USD-180213" +} + +// GetFuturesTransactionDetailsRequest request data for GetFuturesTransactionDetails +type GetFuturesTransactionDetailsRequest struct { + OrderID int64 `json:"order_id,string"` // [required] Order ID + InstrumentID string `json:"instrument_id"` // [required] Contract ID, e.g. "BTC-USD-180213" + Status int64 `json:"status,string"` // [required] Order Status (-1 canceled; 0: pending, 1: partially filled, 2: fully filled, 6: open (pending partially + fully filled), 7: completed (canceled + fully filled)) + From int64 `json:"from,string,omitempty"` // [optional] Request paging content for this page number.(Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + To int64 `json:"to,string,omitempty"` // [optional] Request page after (older) this pagination id. (Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + Limit int64 `json:"limit,string,omitempty"` // [optional] Number of results per request. Maximum 100. (default 100) +} + +// GetFuturesTransactionDetailsResponse response data for GetFuturesTransactionDetails +type GetFuturesTransactionDetailsResponse struct { + CreatedAt string `json:"created_at"` + ExecType string `json:"exec_type"` + Fee string `json:"fee"` + InstrumentID string `json:"instrument_id"` + OrderID string `json:"order_id"` + OrderQty string `json:"order_qty"` + Price string `json:"price"` + Side string `json:"side"` + TradeID string `json:"trade_id"` +} + +// GetFuturesContractInformationResponse individual contract details from GetFuturesContractInformation +type GetFuturesContractInformationResponse struct { + ContractVal int64 `json:"contract_val,string"` + Delivery string `json:"delivery"` + InstrumentID string `json:"instrument_id"` + Listing string `json:"listing"` + QuoteCurrency string `json:"quote_currency"` + TickSize float64 `json:"tick_size,string"` + TradeIncrement int64 `json:"trade_increment,string"` + UnderlyingIndex string `json:"underlying_index"` +} + +// GetFuturesOrderBookRequest request data for GetFuturesOrderBook +type GetFuturesOrderBookRequest struct { + InstrumentID string `url:"-"` // [required] Contract ID, e.g. "BTC-USD-180213" + Size int64 `url:"size,omitempty"` // [optional] The size of the price range (max: 200) +} + +// GetFuturesOrderBookResponse response data for GetFuturesOrderBook +type GetFuturesOrderBookResponse struct { + Asks [][]float64 `json:"asks"` // [[0: Price, 1: Size price, 2: number of force liquidated orders, 3: number of orders on the price]] + Bids [][]float64 `json:"bids"` // [[0: Price, 1: Size price, 2: number of force liquidated orders, 3: number of orders on the price]] + Timestamp time.Time `json:"timestamp"` +} + +// GetFuturesTokenInfoResponse response data for GetFuturesOrderBook +type GetFuturesTokenInfoResponse struct { + BestAsk float64 `json:"best_ask,string"` + BestBid float64 `json:"best_bid,string"` + High24h float64 `json:"high_24h,string"` + InstrumentID string `json:"instrument_id"` + Last float64 `json:"last,string"` + Low24h float64 `json:"low_24h,string"` + Timestamp time.Time `json:"timestamp"` + Volume24h int64 `json:"volume_24h,string"` +} + +// GetFuturesFilledOrderRequest request data for GetFuturesFilledOrder +type GetFuturesFilledOrderRequest struct { + InstrumentID string `url:"-"` // [required] Contract ID, e.g. "BTC-USD-180213" + From int64 `url:"from,string,omitempty"` // [optional] Request paging content for this page number.(Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + To int64 `url:"to,string,omitempty"` // [optional] Request page after (older) this pagination id. (Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + Limit int64 `url:"limit,string,omitempty"` // [optional] Number of results per request. Maximum 100. (default 100) +} + +// GetFuturesFilledOrdersResponse response data for GetFuturesFilledOrders +type GetFuturesFilledOrdersResponse struct { + Price float64 `json:"price,string"` + Qty int64 `json:"qty,string"` + Side string `json:"side"` + Timestamp time.Time `json:"timestamp"` + TradeID string `json:"trade_id"` +} + +// GetFuturesMarketDateRequest retrieves candle data information +type GetFuturesMarketDateRequest struct { + Start string `url:"start,omitempty"` // [optional] start time in ISO 8601 + End string `url:"end,omitempty"` // [optional] end time in ISO 8601 + Granularity int64 `url:"granularity,omitempty"` // [optional] The granularity field must be one of the following values: {60, 180, 300, 900, 1800, 3600, 7200, 14400, 43200, 86400, 604800}. + InstrumentID string `url:"-"` // [required] trading pairs +} + +// GetFuturesMarketDataResponse contains candle data from a GetSpotMarketDataRequest +// Return Parameters +// time string Start time +// open string Open price +// high string Highest price +// low string Lowest price +// close string Close price +// volume string Trading volume +// currencyvolume string The trading volume in a specific token +type GetFuturesMarketDataResponse []interface{} + +// GetFuturesHoldAmountResponse response data for GetFuturesHoldAmount +type GetFuturesHoldAmountResponse struct { + Amount float64 `json:"amount,string"` + InstrumentID string `json:"instrument_id"` + Timestamp time.Time `json:"timestamp"` +} + +// GetFuturesIndicesResponse response data for GetFuturesIndices +type GetFuturesIndicesResponse struct { + Index float64 `json:"index,string"` + InstrumentID string `json:"instrument_id"` + Timestamp time.Time `json:"timestamp"` +} + +// GetFuturesExchangeRatesResponse response data for GetFuturesExchangeRate +type GetFuturesExchangeRatesResponse struct { + InstrumentID string `json:"instrument_id"` + Rate float64 `json:"rate,string"` + Timestamp time.Time `json:"timestamp"` +} + +// GetFuturesEstimatedDeliveryPriceResponse response data for GetFuturesEstimatedDeliveryPrice +type GetFuturesEstimatedDeliveryPriceResponse struct { + InstrumentID string `json:"instrument_id"` + SettlementPrice float64 `json:"settlement_price,string"` + Timestamp time.Time `json:"timestamp"` +} + +// GetFuturesOpenInterestsResponse response data for GetFuturesOpenInterests +type GetFuturesOpenInterestsResponse struct { + Amount float64 `json:"amount,string"` + InstrumentID string `json:"instrument_id"` + Timestamp time.Time `json:"timestamp"` +} + +// GetFuturesCurrentPriceLimitResponse response data for GetFuturesCurrentPriceLimit +type GetFuturesCurrentPriceLimitResponse struct { + Highest float64 `json:"highest,string"` + InstrumentID string `json:"instrument_id"` + Lowest float64 `json:"lowest,string"` + Timestamp time.Time `json:"timestamp"` +} + +// GetFuturesCurrentMarkPriceResponse response data for GetFuturesCurrentMarkPrice +type GetFuturesCurrentMarkPriceResponse struct { + MarkPrice float64 `json:"mark_price"` + InstrumentID string `json:"instrument_id"` + Timestamp time.Time `json:"timestamp"` +} + +// GetFuturesForceLiquidatedOrdersRequest request data for GetFuturesForceLiquidatedOrders +type GetFuturesForceLiquidatedOrdersRequest struct { + InstrumentID string `url:"-"` // [required] Contract ID, e.g. "BTC-USD-180213" + From int64 `url:"from,string,omitempty"` // [optional] Request paging content for this page number.(Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + To int64 `url:"to,string,omitempty"` // [optional] Request page after (older) this pagination id. (Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + Limit int64 `url:"limit,string,omitempty"` // [optional] Number of results per request. Maximum 100. (default 100) + Status string `url:"status,omitempty"` // [optional] Status (0:unfilled orders in the recent 7 days 1:filled orders in the recent 7 days) +} + +// GetFuturesForceLiquidatedOrdersResponse response data for GetFuturesForceLiquidatedOrders +type GetFuturesForceLiquidatedOrdersResponse struct { + Loss float64 `json:"loss"` + Size int64 `json:"size"` + Price float64 `json:"price"` + CreatedAt string `json:"created_at"` + InstrumentID string `json:"instrument_id"` + Type int64 `json:"type"` +} + +// GetFuturesTagPriceResponse response data for GetFuturesTagPrice +type GetFuturesTagPriceResponse struct { + MarkPrice float64 `json:"mark_price"` + InstrumentID string `json:"instrument_id"` + Timestamp time.Time `json:"timestamp"` +} + +// GetSwapPostionsResponse response data for GetSwapPostions +type GetSwapPostionsResponse struct { + MarginMode string `json:"margin_mode"` + Holding []GetSwapPostionsResponseHolding `json:"holding"` +} + +// GetSwapPostionsResponseHolding response data for GetSwapPostions +type GetSwapPostionsResponseHolding struct { + AvailPosition string `json:"avail_position"` + AvgCost string `json:"avg_cost"` + InstrumentID string `json:"instrument_id"` + Leverage string `json:"leverage"` + LiquidationPrice string `json:"liquidation_price"` + Margin string `json:"margin"` + Position string `json:"position"` + RealizedPnl string `json:"realized_pnl"` + SettlementPrice string `json:"settlement_price"` + Side string `json:"side"` + Timestamp time.Time `json:"timestamp"` +} + +// GetSwapAccountOfAllCurrencyResponse response data for GetSwapAccountOfAllCurrency +type GetSwapAccountOfAllCurrencyResponse struct { + Info []GetSwapAccountOfAllCurrencyResponseInfo `json:"info"` +} + +// GetSwapAccountOfAllCurrencyResponseInfo response data for GetSwapAccountOfAllCurrency +type GetSwapAccountOfAllCurrencyResponseInfo struct { + Equity string `json:"equity"` + FixedBalance string `json:"fixed_balance"` + TotalAvailBalance string `json:"total_avail_balance"` + Margin string `json:"margin"` + RealizedPnl string `json:"realized_pnl"` + UnrealizedPnl string `json:"unrealized_pnl"` + MarginRatio string `json:"margin_ratio"` + InstrumentID string `json:"instrument_id"` + MarginFrozen string `json:"margin_frozen"` + Timestamp time.Time `json:"timestamp"` + MarginMode string `json:"margin_mode"` +} + +// GetSwapAccountSettingsOfAContractResponse response data for GetSwapAccountSettingsOfAContract +type GetSwapAccountSettingsOfAContractResponse struct { + LongLeverage float64 `json:"long_leverage,string"` + MarginMode string `json:"margin_mode"` + ShortLeverage float64 `json:"short_leverage,string"` + InstrumentID string `json:"instrument_id"` +} + +// SetSwapLeverageLevelOfAContractRequest request data for SetSwapLeverageLevelOfAContract +type SetSwapLeverageLevelOfAContractRequest struct { + InstrumentID string `json:"instrument_id,omitempty"` // [required] Contract ID, e.g. BTC-USD-SWAP + Leverage int64 `json:"leverage,string"` // [required] New leverage level from 1-100 + Side int64 `json:"side,string"` // [required] Side: 1.FIXED-LONG 2.FIXED-SHORT 3.CROSSED +} + +// SetSwapLeverageLevelOfAContractResponse response data for SetSwapLeverageLevelOfAContract +type SetSwapLeverageLevelOfAContractResponse struct { + InstrumentID string `json:"instrument_id"` + LongLeverage int64 `json:"long_leverage,string"` + MarginMode string `json:"margin_mode"` + ShortLeverage int64 `json:"short_leverage,string"` +} + +// GetSwapBillDetailsResponse response data for GetSwapBillDetails +type GetSwapBillDetailsResponse struct { + LedgerID string `json:"ledger_id"` + Amount string `json:"amount"` + Type string `json:"type"` + Fee string `json:"fee"` + Timestamp time.Time `json:"timestamp"` + InstrumentID string `json:"instrument_id"` +} + +// PlaceSwapOrderRequest request data for PlaceSwapOrder +type PlaceSwapOrderRequest struct { + ClientOID string `json:"client_oid,omitempty"` // [optional] the order ID customized by yourself. 1-32 with digits and letter,The type of client_oid should be comprised of alphabets + numbers or only alphabets within 1 – 32 characters,Both uppercase and lowercase letters are supported + Size float64 `json:"size,string"` // [required] The buying or selling quantity + Type int64 `json:"type,string"` // [required] 1:open long 2:open short 3:close long 4:close short + MatchPrice int64 `json:"match_price,string,omitempty"` // [optional] Order at best counter party price? (0:no 1:yes) + Price float64 `json:"price,string"` // [required] Price of each contract + InstrumentID string `json:"instrument_id"` // [required] Contract ID, e.g. BTC-USD-SWAP +} + +// PlaceSwapOrderResponse response data for PlaceSwapOrder +type PlaceSwapOrderResponse struct { + OrderID string `json:"order_id"` + ClientOID int64 `json:"client_oid,string"` + ErrorCode int64 `json:"error_code,string"` + ErrorMessage string `json:"error_message"` + Result bool `json:"result,string"` +} + +// PlaceMultipleSwapOrdersRequest response data for PlaceMultipleSwapOrders +type PlaceMultipleSwapOrdersRequest struct { + InstrumentID string `json:"instrument_id"` // [required] Contract ID, e.g. BTC-USD-SWAP + Leverage int64 `json:"leverage"` // [required] 10x or 20x leverage + OrdersData []PlaceMultipleSwapOrderData `json:"orders_data"` // [required] the JSON word string for placing multiple orders, include:{client_oid type price size match_price} +} + +// PlaceMultipleSwapOrderData response data for PlaceMultipleSwapOrders +type PlaceMultipleSwapOrderData struct { + ClientOID string `json:"client_oid"` // [required] To identify your order with the order ID set by you + Type string `json:"type"` // Undocumented + Price string `json:"price"` // Undocumented + Size string `json:"size"` // Undocumented + MatchPrice string `json:"match_price"` // Undocumented +} + +// PlaceMultipleSwapOrdersResponse response data for PlaceMultipleSwapOrders +type PlaceMultipleSwapOrdersResponse struct { + Result bool `json:"result,string"` + OrderInfo []PlaceMultipleSwapOrdersResponseInfo `json:"order_info"` +} + +// PlaceMultipleSwapOrdersResponseInfo response data for PlaceMultipleSwapOrders +type PlaceMultipleSwapOrdersResponseInfo struct { + ErrorMessage string `json:"error_message"` + ErrorCode int64 `json:"error_code"` + ClientOID string `json:"client_oid"` + OrderID string `json:"order_id"` +} + +// CancelSwapOrderRequest request data for CancelSwapOrder +type CancelSwapOrderRequest struct { + OrderID string `json:"order_id"` // [required] Order ID + InstrumentID string `json:"instrument_id"` // [required] Contract ID,e.g. BTC-USD-SWAP +} + +// CancelSwapOrderResponse response data for CancelSwapOrder +type CancelSwapOrderResponse struct { + Result bool `json:"result,string"` + OrderID string `json:"order_id"` + InstrumentID string `json:"instrument_id"` +} + +// CancelMultipleSwapOrdersRequest request data for CancelMultipleSwapOrders +type CancelMultipleSwapOrdersRequest struct { + InstrumentID string `json:"instrument_id,omitempty"` // [required] The contract of the orders to be cancelled + OrderIDs []int64 `json:"order_ids"` // [required] ID's of the orders canceled +} + +// CancelMultipleSwapOrdersResponse response data for CancelMultipleSwapOrders +type CancelMultipleSwapOrdersResponse struct { + Result bool `json:"result,string"` + OrderIDS []string `json:"order_ids"` + InstrumentID string `json:"instrument_id"` +} + +// GetSwapOrderListRequest request data for GetSwapOrderList +type GetSwapOrderListRequest struct { + InstrumentID string `url:"-"` // [required] Contract ID, e.g. "BTC-USD-180213" + Status int64 `url:"status,string"` // [required] Order Status (-1 canceled; 0: pending, 1: partially filled, 2: fully filled, 6: open (pending partially + fully filled), 7: completed (canceled + fully filled)) + From int64 `url:"from,string,omitempty"` // [optional] Request paging content for this page number.(Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + To int64 `url:"to,string,omitempty"` // [optional] Request page after (older) this pagination id. (Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + Limit int64 `url:"limit,string,omitempty"` // [optional] Number of results per request. Maximum 100. (default 100) +} + +// GetSwapOrderListResponse response data for GetSwapOrderList +type GetSwapOrderListResponse struct { + Result bool `json:"result,string"` + OrderInfo []GetSwapOrderListResponseData `json:"order_info"` +} + +// GetSwapOrderListResponseData individual order data from GetSwapOrderList +type GetSwapOrderListResponseData struct { + ContractVal float64 `json:"contract_val,string"` + Fee float64 `json:"fee,string"` + FilledQty float64 `json:"filled_qty,string"` + InstrumentID string `json:"instrument_id"` + Leverage int64 `json:"leverage,string"` // Leverage value:10\20 default:10 + OrderID int64 `json:"order_id,string"` + Price float64 `json:"price,string"` + PriceAvg float64 `json:"price_avg,string"` + Size float64 `json:"size,string"` + Status int64 `json:"status,string"` // Order Status (-1 canceled; 0: pending, 1: partially filled, 2: fully filled) + Timestamp time.Time `json:"timestamp"` + Type int64 `json:"type,string"` // Type (1: open long 2: open short 3: close long 4: close short) +} + +// GetSwapOrderDetailsRequest request data for GetSwapOrderList +type GetSwapOrderDetailsRequest struct { + InstrumentID string `json:"instrument_id"` // [required] Contract ID,e.g. BTC-USD-SWAP + OrderID string `json:"order_id"` // [required] Order ID +} + +// GetSwapTransactionDetailsRequest request data for GetSwapTransactionDetails +type GetSwapTransactionDetailsRequest struct { + InstrumentID string `json:"instrument_id"` // [required] Contract ID, e.g. BTC-USD-SWAP + OrderID string `json:"order_id"` // [required] Order ID + From int64 `json:"from,string,omitempty"` // [optional] Request paging content for this page number.(Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + To int64 `json:"to,string,omitempty"` // [optional] Request page after (older) this pagination id. (Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + Limit int64 `json:"limit,string,omitempty"` // [optional] number of results per request. Maximum 100. (default 100) +} + +// GetSwapTransactionDetailsResponse response data for GetSwapTransactionDetails +type GetSwapTransactionDetailsResponse struct { + TradeID string `json:"trade_id"` + InstrumentID string `json:"instrument_id"` + OrderID string `json:"order_id"` + Price string `json:"price"` + OrderQty string `json:"order_qty"` + Fee string `json:"fee"` + Timestamp time.Time `json:"timestamp"` + ExecType string `json:"exec_type"` + Side string `json:"side"` +} + +// GetSwapContractInformationResponse response data for GetSwapContractInformation +type GetSwapContractInformationResponse struct { + InstrumentID string `json:"instrument_id"` + UnderlyingIndex string `json:"underlying_index"` + QuoteCurrency string `json:"quote_currency"` + Coin string `json:"coin"` + ContractVal float64 `json:"contract_val,string"` + Listing string `json:"listing"` + Delivery string `json:"delivery"` + SizeIncrement float64 `json:"size_increment,string"` + TickSize float64 `json:"tick_size,string"` +} + +// GetSwapOrderBookRequest request data for GetSwapOrderBook +type GetSwapOrderBookRequest struct { + InstrumentID string `url:"-"` + Size float64 `url:"size,string,omitempty"` +} + +// GetSwapOrderBookResponse response data for GetSwapOrderBook +type GetSwapOrderBookResponse struct { + Asks [][]interface{} `json:"asks"` // eg [["411.3","16",5,4]] [[0: Price, 1: Size price, 2: number of force liquidated orders, 3: number of orders on the price]] + Bids [][]interface{} `json:"bids"` // eg [["411.3","16",5,4]] [[0: Price, 1: Size price, 2: number of force liquidated orders, 3: number of orders on the price]] + Timestamp time.Time `json:"timestamp"` +} + +// GetAllSwapTokensInformationResponse response data for GetAllSwapTokensInformation +type GetAllSwapTokensInformationResponse struct { + InstrumentID string `json:"instrument_id"` + Last float64 `json:"last,string"` + High24H float64 `json:"high_24h,string"` + Low24H float64 `json:"low_24h,string"` + BestBid float64 `json:"best_bid,string"` + BestAsk float64 `json:"best_ask,string"` + Volume24H float64 `json:"volume_24h,string"` + Timestamp time.Time `json:"timestamp"` +} + +// GetSwapFilledOrdersDataRequest request data for GetSwapFilledOrdersData +type GetSwapFilledOrdersDataRequest struct { + InstrumentID string `url:"-"` // [required] Contract ID, e.g. "BTC-USD-SWAP + From int64 `url:"from,string,omitempty"` // [optional] Request paging content for this page number.(Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + To int64 `url:"to,string,omitempty"` // [optional] Request page after (older) this pagination id. (Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + Limit int64 `url:"limit,string,omitempty"` // [optional] Number of results per request. Maximum 100. (default 100) +} + +// GetSwapFilledOrdersDataResponse response data for GetSwapFilledOrdersData +type GetSwapFilledOrdersDataResponse struct { + TradeID string `json:"trade_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Side string `json:"side"` + Timestamp time.Time `json:"timestamp"` +} + +// GetSwapMarketDataRequest retrieves candle data information +type GetSwapMarketDataRequest struct { + Start string `url:"start,omitempty"` // [optional] start time in ISO 8601 + End string `url:"end,omitempty"` // [optional] end time in ISO 8601 + Granularity int64 `url:"granularity,omitempty"` // The granularity field must be one of the following values: {60, 180, 300, 900, 1800, 3600, 7200, 14400, 43200, 86400, 604800}. + InstrumentID string `url:"-"` // [required] trading pairs +} + +// GetSwapMarketDataResponse response data for GetSwapMarketData +// Return Parameters +// time string Start time +// open string Open price +// high string Highest price +// low string Lowest price +// close string Close price +// volume string Trading volume +// currency_volume string Volume in a specific token +type GetSwapMarketDataResponse []interface{} + +// GetSwapIndecesResponse response data for GetSwapIndeces +type GetSwapIndecesResponse struct { + InstrumentID string `json:"instrument_id"` + Index float64 `json:"index,string"` + Timestamp time.Time `json:"timestamp"` +} + +// GetSwapExchangeRatesResponse response data for GetSwapExchangeRates +type GetSwapExchangeRatesResponse struct { + InstrumentID string `json:"instrument_id"` + Rate float64 `json:"rate,string"` + Timestamp time.Time `json:"timestamp"` +} + +// GetSwapOpenInterestResponse response data for GetSwapOpenInterest +type GetSwapOpenInterestResponse struct { + InstrumentID string `json:"instrument_id"` + Amount float64 `json:"amount,string"` + Timestamp time.Time `json:"timestamp"` +} + +// GetSwapCurrentPriceLimitsResponse response data for GetSwapCurrentPriceLimits +type GetSwapCurrentPriceLimitsResponse struct { + InstrumentID string `json:"instrument_id"` + Highest float64 `json:"highest,string"` + Lowest float64 `json:"lowest,string"` + Timestamp time.Time `json:"timestamp"` +} + +// GetSwapForceLiquidatedOrdersRequest request data for GetSwapForceLiquidatedOrders +type GetSwapForceLiquidatedOrdersRequest struct { + InstrumentID string `url:"-"` // [required] Contract ID, e.g. "BTC-USD-180213" + From int64 `url:"from,string,omitempty"` // [optional] Request paging content for this page number.(Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + To int64 `url:"to,string,omitempty"` // [optional] Request page after (older) this pagination id. (Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + Limit int64 `url:"limit,string,omitempty"` // [optional] Number of results per request. Maximum 100. (default 100) + Status string `url:"status,omitempty"` // [optional] Status (0:unfilled orders in the recent 7 days 1:filled orders in the recent 7 days) +} + +// GetSwapForceLiquidatedOrdersResponse response data for GetSwapForceLiquidatedOrders +type GetSwapForceLiquidatedOrdersResponse struct { + Loss float64 `json:"loss"` + Size int64 `json:"size"` + Price float64 `json:"price"` + CreatedAt string `json:"created_at"` + InstrumentID string `json:"instrument_id"` + Type int64 `json:"type"` +} + +// GetSwapOnHoldAmountForOpenOrdersResponse response data for GetSwapOnHoldAmountForOpenOrders +type GetSwapOnHoldAmountForOpenOrdersResponse struct { + InstrumentID string `json:"instrument_id"` + Amount float64 `json:"amount,string"` + Timestamp time.Time `json:"timestamp"` +} + +// GetSwapNextSettlementTimeResponse response data for GetSwapNextSettlementTime +type GetSwapNextSettlementTimeResponse struct { + InstrumentID string `json:"instrument_id"` + FundingTime string `json:"funding_time"` +} + +// GetSwapMarkPriceResponse response data for GetSwapMarkPrice +type GetSwapMarkPriceResponse struct { + InstrumentID string `json:"instrument_id"` + MarkPrice string `json:"mark_price"` + Timstamp string `json:"timstamp"` +} + +// GetSwapFundingRateHistoryRequest request data for GetSwapFundingRateHistory +type GetSwapFundingRateHistoryRequest struct { + InstrumentID string `url:"ins-trument_id"` // [required] Contract ID, e.g. "BTC-USD-SWAP + From int64 `url:"from,string,omitempty"` // [optional] Request paging content for this page number.(Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + To int64 `url:"to,string,omitempty"` // [optional] Request page after (older) this pagination id. (Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + Limit int64 `url:"limit,string,omitempty"` // [optional] Number of results per request. Maximum 100. +} + +// GetSwapFundingRateHistoryResponse response data for GetSwapFundingRateHistory +type GetSwapFundingRateHistoryResponse struct { + InstrumentID string `json:"instrument_id"` + FundingRate float64 `json:"funding_rate,string,omitempty"` + RealizedRate float64 `json:"realized_rate,string"` + InterestRate float64 `json:"interest_rate,string"` + FundingTime string `json:"funding_time"` + FundingFee float64 `json:"funding_fee,string,omitempty"` +} + +// GetETTResponse response data for GetETT +type GetETTResponse struct { + Currency string `json:"currency"` + Balance float64 `json:"balance"` + Holds float64 `json:"holds"` + Available float64 `json:"available"` +} + +// GetETTBillsDetailsResponse response data for GetETTBillsDetails +type GetETTBillsDetailsResponse struct { + LedgerID int64 `json:"ledger_id"` + Currency string `json:"currency"` + Balance float64 `json:"balance"` + Amount float64 `json:"amount"` + Type string `json:"type"` + CreatedAt string `json:"created_at"` + Details int64 `json:"details"` +} + +// PlaceETTOrderRequest request data for PlaceETTOrder +type PlaceETTOrderRequest struct { + ClientOID string `json:"client_oid"` // [optional]the order ID customized by yourself + Type int64 `json:"type"` // Type of order (0:ETT subscription 1:subscribe with USDT 2:Redeem in USDT 3:Redeem in underlying) + QuoteCurrency string `json:"quote_currency"` // Subscription/redemption currency + Amount float64 `json:"amount"` // Subscription amount. Required for usdt subscription + Size string `json:"size"` // Redemption size. Required for ETT subscription and redemption + ETT string `json:"ett"` // ETT name +} + +// PlaceETTOrderResponse response data for PlaceETTOrder +type PlaceETTOrderResponse struct { + ClientOID string `json:"client_oid"` + OrderID string `json:"type"` + Result bool `json:"quote_currency"` +} + +// GetETTOrderListRequest request data for GetETTOrderList +type GetETTOrderListRequest struct { + ETT string `url:"ett"` // [required] list specific ETT order + Type int64 `url:"type"` // [required](1: subscription 2: redemption) + Status int64 `url:"status,omitempty"` // [optional] List the orders of the status (0:All 1:Unfilled 2:Filled 3:Canceled) + From int64 `url:"from,string,omitempty"` // [optional] Request paging content for this page number.(Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + To int64 `url:"to,string,omitempty"` // [optional] Request page after (older) this pagination id. (Example: 1,2,3,4,5. From 4 we only have 4, to 4 we only have 3) + Limit int64 `url:"limit,string,omitempty"` // [optional] Number of results per request. Maximum 100. +} + +// GetETTOrderListResponse response data for GetETTOrderList +type GetETTOrderListResponse struct { + OrderID string `json:"order_id"` + Price string `json:"price"` + Size string `json:"size"` + Amount string `json:"amount"` + QuoteCurrency string `json:"quote_currency"` + Ett string `json:"ett"` + Type int64 `json:"type"` + CreatedAt string `json:"created_at"` + Status string `json:"status"` +} + +// GetETTConstituentsResponse response data for GetETTConstituents +type GetETTConstituentsResponse struct { + NetValue float64 `json:"net_value"` + Ett string `json:"ett"` + Constituents []ConstituentData `json:"constituents"` +} + +// ConstituentData response data for GetETTConstituents +type ConstituentData struct { + Amount float64 `json:"amount"` + Currency string `json:"currency"` +} + +// GetETTSettlementPriceHistoryResponse response data for GetETTSettlementPriceHistory +type GetETTSettlementPriceHistoryResponse struct { + Date string `json:"date"` + Price float64 `json:"price"` +} + +// OrderStatus Holds OKGroup order status values +var OrderStatus = map[int64]string{ + -3: "pending cancel", + -2: "cancelled", + -1: "failed", + 0: "pending", + 1: "sending", + 2: "sent", + 3: "email confirmation", + 4: "manual confirmation", + 5: "awaiting identity confirmation", +} + +// SpotInstrument contains spot data +type SpotInstrument struct { + BaseCurrency string `json:"base_currency"` + BaseIncrement float64 `json:"base_increment,string"` + BaseMinSize float64 `json:"base_min_size,string"` + InstrumentID string `json:"instrument_id"` + MinSize float64 `json:"min_size,string"` + ProductID string `json:"product_id"` + QuoteCurrency string `json:"quote_currency"` + QuoteIncrement float64 `json:"quote_increment,string"` + SizeIncrement float64 `json:"size_increment,string"` + TickSize float64 `json:"tick_size,string"` +} + +// WebsocketEventRequest contains event data for a websocket channel +type WebsocketEventRequest struct { + Operation string `json:"op"` // 1--subscribe 2--unsubscribe 3--login + Arguments []string `json:"args"` // args: the value is the channel name, which can be one or more channels +} + +// WebsocketEventResponse contains event data for a websocket channel +type WebsocketEventResponse struct { + Event string `json:"event"` + Channel string `json:"channel"` +} + +// WebsocketDataResponse formats all response data for a websocket event +type WebsocketDataResponse struct { + Table string `json:"table"` + Action string `json:"action,omitempty"` + Data []WebsocketDataWrapper `json:"data"` +} + +// WebsocketDataWrapper holds all data responses for websocket +// Can review in future if struct becomes too large +// allows for easy data processing +type WebsocketDataWrapper struct { + InstrumentID string `json:"instrument_id"` + Timestamp time.Time `json:"timestamp,omitempty"` + WebsocketTickerData + WebsocketCandleResponse + WebsocketOrderBooksData + WebsocketTradeResponse + WebsocketFundingFeeResponse + WebsocketMarkPriceResponse + WebsocketEstimatedPriceResponse + WebsocketPriceRangeResponse + WebsocketUserSwapPositionResponse + WebsocketUserSwapOrdersResponse + WebsocketUserSwapFutureAccountResponse + WebsocketUserSpotAccountResponse + WebsocketSpotMarginOrderResponse + WebsocketUserFutureFixedMarginAccountResponse + WebsocketUserFuturePositionResponse + WebsocketSpotOrderResponse +} + +// WebsocketTickerData contains formatted data for ticker related websocket responses +type WebsocketTickerData struct { + High24H float64 `json:"high_24h,string,omitempty"` + Last float64 `json:"last,string,omitempty"` + BestBid float64 `json:"best_bid,string,omitempty"` + BestAsk float64 `json:"best_ask,string,omitempty"` + Low24H float64 `json:"low_24h,string,omitempty"` + Volume24H float64 `json:"volume_24h,string,omitempty"` +} + +// WebsocketTradeResponse contains formatted data for trade related websocket responses +type WebsocketTradeResponse struct { + Price float64 `json:"price,string,omitempty"` + Side string `json:"side,omitempty"` + Qty float64 `json:"qty,string,omitempty"` + TradeID string `json:"trade_id,omitempty"` +} + +// WebsocketCandleResponse contains formatted data for candle related websocket responses +type WebsocketCandleResponse struct { + Candle []string `json:"candle,omitempty"` // [0]timestamp, [1]open, [2]high, [3]low, [4]close, [5]volume, [6]currencyVolume +} + +// WebsocketFundingFeeResponse contains formatted data for funding fee related websocket responses +type WebsocketFundingFeeResponse struct { + FundingRate float64 `json:"funding_rate,string,omitempty"` + FundingTime time.Time `json:"funding_time,omitempty"` + InterestRate float64 `json:"interest_rate,string,omitempty"` +} + +// WebsocketMarkPriceResponse contains formatted data for mark prices +type WebsocketMarkPriceResponse struct { + MarkPrice float64 `json:"mark_price,string,omitempty"` +} + +// WebsocketEstimatedPriceResponse contains formatted data for estimated prices +type WebsocketEstimatedPriceResponse struct { + SettlementPrice float64 `json:"settlement_price,string,omitempty"` +} + +// WebsocketMarkPriceResponse contains formatted data for mark prices +type WebsocketPriceRangeResponse struct { + Highest float64 `json:"highest,omitempty"` + Lowest float64 `json:"lowest,omitempty"` +} + +// WebsocketOrderBooksData contains orderbook data from WebsocketOrderBooksResponse +type WebsocketOrderBooksData struct { + Asks [][]interface{} `json:"asks,omitempty"` // [0] Price, [1] Size, [2] Number of orders + Bids [][]interface{} `json:"bids,omitempty"` // [0] Price, [1] Size, [2] Number of orders + Checksum int32 `json:"checksum,omitempty"` +} + +// WebsocketUserPositionResponse contains formatted data for user position data +type WebsocketUserSwapPositionResponse struct { + Holding []WebsocketUserSwapPositionHoldingData `json:"holding,omitempty"` +} + +// WebsocketUserPositionResponse contains formatted data for user position holding data +type WebsocketUserSwapPositionHoldingData struct { + AvailablePosition float64 `json:"avail_position,string,omitempty"` + AverageCost float64 `json:"avg_cost,string,omitempty"` + Leverage float64 `json:"leverage,string,omitempty"` + LiquidationPrice float64 `json:"liquidation_price,string,omitempty"` + Margin float64 `json:"margin,string,omitempty"` + Position float64 `json:"position,string,omitempty"` + RealizedPnl float64 `json:"realized_pnl,string,omitempty"` + SettlementPrice float64 `json:"settlement_price,string,omitempty"` + Side string `json:"side,omitempty"` + Timestamp time.Time `json:"timestamp,omitempty"` +} + +// WebsocketUserAccountResponse contains formatted data for user account data +type WebsocketUserSwapFutureAccountResponse struct { + Equity float64 `json:"equity,string,omitempty"` + FixedBalance float64 `json:"fixed_balance,string,omitempty"` + MarginFrozen float64 `json:"margin_frozen,string,omitempty"` + MarginRatio float64 `json:"margin_ratio,string,omitempty"` + RealizedPnl float64 `json:"realized_pnl,string,omitempty"` + UnrealizedPnl float64 `json:"unrealized_pnl,string,omitempty"` + // MarginMode A member, but part already exists as part of WebsocketDataResponse + // TotalAvailBalance A member, but part already exists as part of WebsocketDataResponse + // Margin A member, but part already exists as part of WebsocketDataResponse +} + +// WebsocketUserSpotAccountResponse contains formatted data for user account data +type WebsocketUserSpotAccountResponse struct { + Balance string `json:"balance"` + Available string `json:"available"` + Currency string `json:"currency"` + ID int64 `json:"id"` + Hold string `json:"hold"` +} + +// WebsocketSpotMarginOrderResponse contains formatted data for user account data +type WebsocketSpotMarginOrderResponse struct { + MarginMode string `json:"margin_mode"` + TotalAvailBalance string `json:"total_avail_balance"` + // UnrealizedPnl A member, but part already exists as part of WebsocketDataResponse + // Equity A member, but part already exists as part of WebsocketDataResponse + // FixedBalance A member, but part already exists as part of WebsocketDataResponse + // InstrumentID A member, but part already exists as part of WebsocketDataResponse + // Margin A member, but part already exists as part of WebsocketDataResponse + // MarginFrozen A member, but part already exists as part of WebsocketDataResponse + // MarginRatio A member, but part already exists as part of WebsocketDataResponse + // RealizedPnl A member, but part already exists as part of WebsocketDataResponse + // Timestamp A member, but part already exists as part of WebsocketDataResponse +} + +// WebsocketUserFutureFixedMarginAccountResponse contains formatted data for user account data +type WebsocketUserFutureFixedMarginAccountResponse map[string]WebsocketUserFutureFixedMarginAccountData + +type WebsocketUserFutureFixedMarginAccountData struct { + Contracts []WebsocketUserSwapFutureAccountResponse `json:"contracts"` + Equity string `json:"equity"` + MarginMode string `json:"margin_mode"` + TotalAvailBalance string `json:"total_avail_balance"` +} + +// WebsocketUserOrdersResponse contains formatted data for user order data +type WebsocketUserSwapOrdersResponse struct { + FilledQuantity float64 `json:"filled_qty,string,omitempty"` + ClientOID string `json:"client_oid,omitempty"` + Fee float64 `json:"fee,string,omitempty"` + ContractValue float64 `json:"contract_val,string,omitempty"` + PriceAverage float64 `json:"price_avg,string,omitempty"` + OrderID string `json:"order_id,omitempty"` + // Size A member, but part already exists as part of WebsocketDataResponse + // Status A member, but part already exists as part of WebsocketDataResponse + // Leverage A member, but part already exists as part of WebsocketDataResponse + // Price A member, but part already exists as part of WebsocketDataResponse + // Type A member, but part already exists as part of WebsocketDataResponse +} + +// WebsocketUserFuturePositionResponse contains formatted data for futures positions data +type WebsocketUserFuturePositionResponse struct { + LongQty string `json:"long_qty"` + LongAvailQty int64 `json:"long_avail_qty"` + LongAvgCost string `json:"long_avg_cost"` + LongSettlementPrice string `json:"long_settlement_price"` + RealisedPnl string `json:"realised_pnl"` + ShortQty string `json:"short_qty"` + ShortAvailQty string `json:"short_avail_qty"` + ShortAvgCost string `json:"short_avg_cost"` + ShortSettlementPrice string `json:"short_settlement_price"` + LiquidationPrice string `json:"liquidation_price"` + Leverage string `json:"leverage"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + LongMargin string `json:"long_margin"` + LongLiquiPrice string `json:"long_liqui_price"` + LongPnlRatio string `json:"long_pnl_ratio"` + ShortMargin string `json:"short_margin"` + ShortLiquiPrice string `json:"short_liqui_price"` + ShortPnlRatio string `json:"short_pnl_ratio"` + LongLeverage string `json:"long_leverage"` + ShortLeverage string `json:"short_leverage"` + // UpdatedAt A member, but part already exists as part of WebsocketDataResponse + // MarginMode A member, but part already exists as part of WebsocketDataResponse + // InstrumentID A member, but part already exists as part of WebsocketDataResponse +} + +// WebsocketSpotOrderResponse contains formatted data for spot user orders +type WebsocketSpotOrderResponse struct { + FilledNotional float64 `json:"filled_notional,string"` + FilledSize float64 `json:"filled_size,string"` + Notional float64 `json:"notional,string"` + Size float64 `json:"size,string"` + Status string `json:"status"` + MarginTrading int64 `json:"margin_trading"` + Type string `json:"type"` + // Price A member, but part already exists as part of WebsocketDataResponse + // InstrumentID A member, but part already exists as part of WebsocketDataResponse + // Timestamp A member, but part already exists as part of WebsocketDataResponse + // OrderID A member, but part already exists as part of WebsocketDataResponse +} + +// WebsocketErrorResponse yo +type WebsocketErrorResponse struct { + Event string `json:"event"` + Message string `json:"message"` + ErrorCode int64 `json:"errorCode"` +} diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go new file mode 100644 index 00000000..5ccb557f --- /dev/null +++ b/exchanges/okgroup/okgroup_websocket.go @@ -0,0 +1,759 @@ +package okgroup + +import ( + "bytes" + "compress/flate" + "errors" + "fmt" + "hash/crc32" + "io/ioutil" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/gorilla/websocket" + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/pair" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + 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" + // API subsections + okGroupWsSwapSubsection = "swap/" + okGroupWsIndexSubsection = "index/" + okGroupWsFuturesSubsection = "futures/" + okGroupWsSpotSubsection = "spot/" + // Shared API endpoints + okGroupWsCandle = "candle" + okGroupWsCandle60s = okGroupWsCandle + "60s" + okGroupWsCandle180s = okGroupWsCandle + "180s" + okGroupWsCandle300s = okGroupWsCandle + "300s" + okGroupWsCandle900s = okGroupWsCandle + "900s" + okGroupWsCandle1800s = okGroupWsCandle + "1800s" + okGroupWsCandle3600s = okGroupWsCandle + "3600s" + okGroupWsCandle7200s = okGroupWsCandle + "7200s" + okGroupWsCandle14400s = okGroupWsCandle + "14400s" + okGroupWsCandle21600s = okGroupWsCandle + "21600" + okGroupWsCandle43200s = okGroupWsCandle + "43200s" + okGroupWsCandle86400s = okGroupWsCandle + "86400s" + okGroupWsCandle604900s = okGroupWsCandle + "604800s" + okGroupWsTicker = "ticker" + okGroupWsTrade = "trade" + okGroupWsDepth = "depth" + okGroupWsDepth5 = "depth5" + okGroupWsAccount = "account" + okGroupWsMarginAccount = "margin_account" + okGroupWsOrder = "order" + okGroupWsFundingRate = "funding_rate" + okGroupWsPriceRange = "price_range" + okGroupWsMarkPrice = "mark_price" + okGroupWsPosition = "position" + okGroupWsEstimatedPrice = "estimated_price" + // Spot endpoints + okGroupWsSpotTicker = okGroupWsSpotSubsection + okGroupWsTicker + okGroupWsSpotCandle60s = okGroupWsSpotSubsection + okGroupWsCandle60s + okGroupWsSpotCandle180s = okGroupWsSpotSubsection + okGroupWsCandle180s + okGroupWsSpotCandle300s = okGroupWsSpotSubsection + okGroupWsCandle300s + okGroupWsSpotCandle900s = okGroupWsSpotSubsection + okGroupWsCandle900s + okGroupWsSpotCandle1800s = okGroupWsSpotSubsection + okGroupWsCandle1800s + okGroupWsSpotCandle3600s = okGroupWsSpotSubsection + okGroupWsCandle3600s + okGroupWsSpotCandle7200s = okGroupWsSpotSubsection + okGroupWsCandle7200s + okGroupWsSpotCandle14400s = okGroupWsSpotSubsection + okGroupWsCandle14400s + okGroupWsSpotCandle21600s = okGroupWsSpotSubsection + okGroupWsCandle21600s + okGroupWsSpotCandle43200s = okGroupWsSpotSubsection + okGroupWsCandle43200s + okGroupWsSpotCandle86400s = okGroupWsSpotSubsection + okGroupWsCandle86400s + okGroupWsSpotCandle604900s = okGroupWsSpotSubsection + okGroupWsCandle604900s + okGroupWsSpotTrade = okGroupWsSpotSubsection + okGroupWsTrade + okGroupWsSpotDepth = okGroupWsSpotSubsection + okGroupWsDepth + okGroupWsSpotDepth5 = okGroupWsSpotSubsection + okGroupWsDepth5 + okGroupWsSpotAccount = okGroupWsSpotSubsection + okGroupWsAccount + okGroupWsSpotMarginAccount = okGroupWsSpotSubsection + okGroupWsMarginAccount + okGroupWsSpotOrder = okGroupWsSpotSubsection + okGroupWsOrder + // Swap endpoints + okGroupWsSwapTicker = okGroupWsSwapSubsection + okGroupWsTicker + okGroupWsSwapCandle60s = okGroupWsSwapSubsection + okGroupWsCandle60s + okGroupWsSwapCandle180s = okGroupWsSwapSubsection + okGroupWsCandle180s + okGroupWsSwapCandle300s = okGroupWsSwapSubsection + okGroupWsCandle300s + okGroupWsSwapCandle900s = okGroupWsSwapSubsection + okGroupWsCandle900s + okGroupWsSwapCandle1800s = okGroupWsSwapSubsection + okGroupWsCandle1800s + okGroupWsSwapCandle3600s = okGroupWsSwapSubsection + okGroupWsCandle3600s + okGroupWsSwapCandle7200s = okGroupWsSwapSubsection + okGroupWsCandle7200s + okGroupWsSwapCandle14400s = okGroupWsSwapSubsection + okGroupWsCandle14400s + okGroupWsSwapCandle21600s = okGroupWsSwapSubsection + okGroupWsCandle21600s + okGroupWsSwapCandle43200s = okGroupWsSwapSubsection + okGroupWsCandle43200s + okGroupWsSwapCandle86400s = okGroupWsSwapSubsection + okGroupWsCandle86400s + okGroupWsSwapCandle604900s = okGroupWsSwapSubsection + okGroupWsCandle604900s + okGroupWsSwapTrade = okGroupWsSwapSubsection + okGroupWsTrade + okGroupWsSwapDepth = okGroupWsSwapSubsection + okGroupWsDepth + okGroupWsSwapDepth5 = okGroupWsSwapSubsection + okGroupWsDepth5 + okGroupWsSwapFundingRate = okGroupWsSwapSubsection + okGroupWsFundingRate + okGroupWsSwapPriceRange = okGroupWsSwapSubsection + okGroupWsPriceRange + okGroupWsSwapMarkPrice = okGroupWsSwapSubsection + okGroupWsMarkPrice + okGroupWsSwapPosition = okGroupWsSwapSubsection + okGroupWsPosition + okGroupWsSwapAccount = okGroupWsSwapSubsection + okGroupWsAccount + okGroupWsSwapOrder = okGroupWsSwapSubsection + okGroupWsOrder + // Index endpoints + okGroupWsIndexTicker = okGroupWsIndexSubsection + okGroupWsTicker + okGroupWsIndexCandle60s = okGroupWsIndexSubsection + okGroupWsCandle60s + okGroupWsIndexCandle180s = okGroupWsIndexSubsection + okGroupWsCandle180s + okGroupWsIndexCandle300s = okGroupWsIndexSubsection + okGroupWsCandle300s + okGroupWsIndexCandle900s = okGroupWsIndexSubsection + okGroupWsCandle900s + okGroupWsIndexCandle1800s = okGroupWsIndexSubsection + okGroupWsCandle1800s + okGroupWsIndexCandle3600s = okGroupWsIndexSubsection + okGroupWsCandle3600s + okGroupWsIndexCandle7200s = okGroupWsIndexSubsection + okGroupWsCandle7200s + okGroupWsIndexCandle14400s = okGroupWsIndexSubsection + okGroupWsCandle14400s + okGroupWsIndexCandle21600s = okGroupWsIndexSubsection + okGroupWsCandle21600s + okGroupWsIndexCandle43200s = okGroupWsIndexSubsection + okGroupWsCandle43200s + okGroupWsIndexCandle86400s = okGroupWsIndexSubsection + okGroupWsCandle86400s + okGroupWsIndexCandle604900s = okGroupWsIndexSubsection + okGroupWsCandle604900s + // Futures endpoints + okGroupWsFuturesTicker = okGroupWsFuturesSubsection + okGroupWsTicker + okGroupWsFuturesCandle60s = okGroupWsFuturesSubsection + okGroupWsCandle60s + okGroupWsFuturesCandle180s = okGroupWsFuturesSubsection + okGroupWsCandle180s + okGroupWsFuturesCandle300s = okGroupWsFuturesSubsection + okGroupWsCandle300s + okGroupWsFuturesCandle900s = okGroupWsFuturesSubsection + okGroupWsCandle900s + okGroupWsFuturesCandle1800s = okGroupWsFuturesSubsection + okGroupWsCandle1800s + okGroupWsFuturesCandle3600s = okGroupWsFuturesSubsection + okGroupWsCandle3600s + okGroupWsFuturesCandle7200s = okGroupWsFuturesSubsection + okGroupWsCandle7200s + okGroupWsFuturesCandle14400s = okGroupWsFuturesSubsection + okGroupWsCandle14400s + okGroupWsFuturesCandle21600s = okGroupWsFuturesSubsection + okGroupWsCandle21600s + okGroupWsFuturesCandle43200s = okGroupWsFuturesSubsection + okGroupWsCandle43200s + okGroupWsFuturesCandle86400s = okGroupWsFuturesSubsection + okGroupWsCandle86400s + okGroupWsFuturesCandle604900s = okGroupWsFuturesSubsection + okGroupWsCandle604900s + okGroupWsFuturesTrade = okGroupWsFuturesSubsection + okGroupWsTrade + okGroupWsFuturesEstimatedPrice = okGroupWsFuturesSubsection + okGroupWsTrade + okGroupWsFuturesPriceRange = okGroupWsFuturesSubsection + okGroupWsPriceRange + okGroupWsFuturesDepth = okGroupWsFuturesSubsection + okGroupWsDepth + okGroupWsFuturesDepth5 = okGroupWsFuturesSubsection + okGroupWsDepth5 + okGroupWsFuturesMarkPrice = okGroupWsFuturesSubsection + okGroupWsMarkPrice + okGroupWsFuturesAccount = okGroupWsFuturesSubsection + okGroupWsAccount + okGroupWsFuturesPosition = okGroupWsFuturesSubsection + okGroupWsPosition + okGroupWsFuturesOrder = okGroupWsFuturesSubsection + okGroupWsOrder +) + +// orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time +var orderbookMutex sync.Mutex + +// writeToWebsocket sends a message to the websocket endpoint +func (o *OKGroup) writeToWebsocket(message string) error { + o.mu.Lock() + defer o.mu.Unlock() + if o.Verbose { + log.Debugf("Sending message to WS: %v", message) + } + 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) + } + + 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{}) + if err != nil { + return fmt.Errorf("%s Unable to connect to Websocket. Error: %s", + o.Name, + err) + } + if o.Verbose { + log.Debugf("Successful connection to %v", o.Websocket.GetWebsocketURL()) + } + + go o.WsHandleData() + go o.wsPingHandler() + + err = o.WsSubscribeToDefaults() + if err != nil { + return fmt.Errorf("error: Could not subscribe to the OKEX websocket %s", + err) + } + return nil +} + +// WsSubscribeToDefaults subscribes to the websocket channels +func (o *OKGroup) WsSubscribeToDefaults() (err error) { + channelsToSubscribe := []string{okGroupWsSpotDepth, okGroupWsSpotCandle300s, okGroupWsSpotTicker, okGroupWsSpotTrade} + for _, pair := range o.EnabledPairs { + formattedPair := strings.ToUpper(strings.Replace(pair, "_", "-", 1)) + for _, channel := range channelsToSubscribe { + err = o.WsSubscribeToChannel(fmt.Sprintf("%v:%s", channel, formattedPair)) + if err != nil { + return + } + } + } + + 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() { + o.Websocket.Wg.Add(1) + defer o.Websocket.Wg.Done() + + ticker := time.NewTicker(time.Second * 27) + + for { + select { + case <-o.Websocket.ShutdownC: + return + + case <-ticker.C: + err := o.writeToWebsocket("ping") + if o.Verbose { + log.Debugf("%v sending ping", o.GetName()) + } + if err != nil { + o.Websocket.DataHandler <- err + return + } + } + } +} + +// WsHandleData handles the read data from the websocket connection +func (o *OKGroup) WsHandleData() { + o.Websocket.Wg.Add(1) + defer func() { + err := o.WebsocketConn.Close() + if err != nil { + o.Websocket.DataHandler <- fmt.Errorf("okex_websocket.go - Unable to to close Websocket connection. Error: %s", + err) + } + o.Websocket.Wg.Done() + }() + + for { + select { + case <-o.Websocket.ShutdownC: + return + default: + resp, err := o.WsReadData() + if err != nil { + o.Websocket.DataHandler <- err + return + } + var dataResponse WebsocketDataResponse + err = common.JSONDecode(resp.Raw, &dataResponse) + if err == nil && dataResponse.Table != "" { + if len(dataResponse.Data) > 0 { + o.WsHandleDataResponse(&dataResponse) + } + continue + } + var errorResponse WebsocketErrorResponse + err = common.JSONDecode(resp.Raw, &errorResponse) + if err == nil && errorResponse.ErrorCode > 0 { + if o.Verbose { + log.Debugf("WS Error Event: %v Message: %v", errorResponse.Event, errorResponse.Message) + } + o.WsHandleErrorResponse(errorResponse) + continue + } + var eventResponse WebsocketEventResponse + err = common.JSONDecode(resp.Raw, &eventResponse) + if err == nil && len(eventResponse.Channel) > 0 { + if o.Verbose { + log.Debugf("WS Event: %v on Channel: %v", eventResponse.Event, eventResponse.Channel) + } + continue + } + o.Websocket.DataHandler <- fmt.Errorf("unrecognised response: %v", resp.Raw) + continue + } + } +} + +// WsSubscribeToChannel sends a request to WS to subscribe to supplied channel +func (o *OKGroup) WsSubscribeToChannel(topic string) error { + resp := WebsocketEventRequest{ + Operation: "subscribe", + Arguments: []string{topic}, + } + json, err := common.JSONEncode(resp) + if err != nil { + return err + } + err = o.writeToWebsocket(string(json)) + if err != nil { + return err + } + return nil +} + +// WsUnsubscribeToChannel sends a request to WS to unsubscribe to supplied channel +func (o *OKGroup) WsUnsubscribeToChannel(topic string) error { + resp := WebsocketEventRequest{ + Operation: "unsubscribe", + Arguments: []string{topic}, + } + json, err := common.JSONEncode(resp) + if err != nil { + return err + } + err = o.writeToWebsocket(string(json)) + if err != nil { + return err + } + return nil +} + +// WsLogin sends a login request to websocket to enable access to authenticated endpoints +func (o *OKGroup) WsLogin() error { + utcTime := time.Now().UTC() + unixTime := utcTime.Unix() + 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{ + Operation: "login", + Arguments: []string{o.APIKey, o.ClientID, fmt.Sprintf("%v", unixTime), base64}, + } + json, err := common.JSONEncode(resp) + if err != nil { + return err + } + err = o.writeToWebsocket(string(json)) + if err != nil { + return err + } + return nil +} + +// WsHandleErrorResponse sends an error message to ws handler +func (o *OKGroup) WsHandleErrorResponse(event WebsocketErrorResponse) { + errorMessage := fmt.Sprintf("%v error - %v message: %s ", + o.GetName(), event.ErrorCode, event.Message) + if o.Verbose { + log.Error(errorMessage) + } + o.Websocket.DataHandler <- fmt.Errorf(errorMessage) +} + +// GetWsChannelWithoutOrderType takes WebsocketDataResponse.Table and returns +// The base channel name eg receive "spot/depth5:BTC-USDT" return "depth5" +func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string { + index := strings.Index(table, "/") + if index == -1 { + return table + } + channel := table[index+1:] + index = strings.Index(channel, ":") + // Some events do not contain a currency + if index == -1 { + return channel + } + + return channel[:index] +} + +// GetAssetTypeFromTableName gets the asset type from the table name +// eg "spot/ticker:BTCUSD" results in "SPOT" +func (o *OKGroup) GetAssetTypeFromTableName(table string) string { + assetIndex := strings.Index(table, "/") + return strings.ToUpper(table[:assetIndex]) +} + +// WsHandleDataResponse classifies the WS response and sends to appropriate handler +func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) { + switch o.GetWsChannelWithoutOrderType(response.Table) { + 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 { + log.Error(err) + subscriptionChannel := fmt.Sprintf("%v:%v", response.Table, response.Data[0].InstrumentID) + o.ResubscribeToChannel(subscriptionChannel) + } + 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) + } +} + +// resubscribeToChannel will attempt to unsubscribe and resubscribe to a channel +func (o *OKGroup) ResubscribeToChannel(channel string) { + if okGroupWsResubscribeFailureLimit > 0 { + var successfulUnsubscribe bool + for i := 0; i < okGroupWsResubscribeFailureLimit; i++ { + err := o.WsUnsubscribeToChannel(channel) + if err != nil { + log.Error(err) + time.Sleep(okGroupWsResubscribeDelayInSeconds * time.Second) + continue + } + successfulUnsubscribe = true + break + } + if !successfulUnsubscribe { + log.Fatalf("%v websocket channel %v failed to unsubscribe after %v attempts", o.GetName(), channel, okGroupWsResubscribeFailureLimit) + } + successfulSubscribe := true + for i := 0; i < okGroupWsResubscribeFailureLimit; i++ { + err := o.WsSubscribeToChannel(channel) + if err != nil { + log.Error(err) + time.Sleep(okGroupWsResubscribeDelayInSeconds * time.Second) + continue + } + successfulSubscribe = true + break + } + if !successfulSubscribe { + log.Fatalf("%v websocket channel %v failed to resubscribe after %v attempts", o.GetName(), channel, okGroupWsResubscribeFailureLimit) + } + } else { + log.Fatalf("%v websocket channel %v cannot resubscribe. Limit: %v", o.GetName(), channel, okGroupWsResubscribeFailureLimit) + } +} + +// logDataResponse will log the details of any websocket data event +// where there is no websocket datahandler for it +func logDataResponse(response *WebsocketDataResponse) { + for _, data := range response.Data { + log.Errorf("Unhandled channel: '%v'. Instrument '%v' Timestamp '%v', Data '%v", + response.Table, + data.InstrumentID, + data.Timestamp, + data) + } +} + +// wsProcessTickers converts ticker data and sends it to the datahandler +func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) { + for _, tickerData := range response.Data { + instrument := pair.NewCurrencyPairDelimiter(tickerData.InstrumentID, "-") + o.Websocket.DataHandler <- exchange.TickerData{ + Timestamp: tickerData.Timestamp, + Exchange: o.GetName(), + AssetType: o.GetAssetTypeFromTableName(response.Table), + HighPrice: tickerData.High24H, + LowPrice: tickerData.Low24H, + ClosePrice: tickerData.Last, + Pair: instrument, + } + } +} + +// wsProcessTrades converts trade data and sends it to the datahandler +func (o *OKGroup) wsProcessTrades(response *WebsocketDataResponse) { + for _, trade := range response.Data { + instrument := pair.NewCurrencyPairDelimiter(trade.InstrumentID, "-") + o.Websocket.DataHandler <- exchange.TradeData{ + Amount: trade.Qty, + AssetType: o.GetAssetTypeFromTableName(response.Table), + CurrencyPair: instrument, + EventTime: time.Now().Unix(), + Exchange: o.GetName(), + Price: trade.WebsocketTradeResponse.Price, + Side: trade.Side, + Timestamp: trade.Timestamp, + } + } +} + +// wsProcessCandles converts candle data and sends it to the data handler +func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) { + for _, candle := range response.Data { + instrument := pair.NewCurrencyPairDelimiter(candle.InstrumentID, "-") + timeData, err := time.Parse(time.RFC3339Nano, candle.WebsocketCandleResponse.Candle[0]) + if err != nil { + log.Warnf("%v Time data could not be parsed: %v", o.GetName(), candle.Candle[0]) + } + + candleIndex := strings.LastIndex(response.Table, okGroupWsCandle) + secondIndex := strings.LastIndex(response.Table, "0s") + candleInterval := "" + if candleIndex > 0 || secondIndex > 0 { + candleInterval = response.Table[candleIndex+len(okGroupWsCandle) : secondIndex] + } + + klineData := exchange.KlineData{ + AssetType: o.GetAssetTypeFromTableName(response.Table), + Pair: instrument, + Exchange: o.GetName(), + Timestamp: timeData, + Interval: candleInterval, + } + klineData.OpenPrice, _ = strconv.ParseFloat(candle.Candle[1], 64) + klineData.HighPrice, _ = strconv.ParseFloat(candle.Candle[2], 64) + klineData.LowPrice, _ = strconv.ParseFloat(candle.Candle[3], 64) + klineData.ClosePrice, _ = strconv.ParseFloat(candle.Candle[4], 64) + klineData.Volume, _ = strconv.ParseFloat(candle.Candle[5], 64) + + o.Websocket.DataHandler <- klineData + } +} + +// WsProcessOrderBook Validates the checksum and updates internal orderbook values +func (o *OKGroup) WsProcessOrderBook(response *WebsocketDataResponse) (err error) { + for i := range response.Data { + instrument := pair.NewCurrencyPairDelimiter(response.Data[i].InstrumentID, "-") + if response.Action == okGroupWsOrderbookPartial { + err = o.WsProcessPartialOrderBook(&response.Data[i], instrument, response.Table) + } else if response.Action == okGroupWsOrderbookUpdate { + err = o.WsProcessUpdateOrderbook(&response.Data[i], instrument, response.Table) + } + } + return +} + +// AppendWsOrderbookItems adds websocket orderbook data bid/asks into an orderbook item array +func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) (orderbookItems []orderbook.Item) { + for j := range entries { + amount, _ := strconv.ParseFloat(entries[j][1].(string), 64) + price, _ := strconv.ParseFloat(entries[j][0].(string), 64) + orderbookItems = append(orderbookItems, orderbook.Item{ + Amount: amount, + Price: price, + }) + } + return +} + +// WsProcessPartialOrderBook takes websocket orderbook data and creates an orderbook +// Calculates checksum to ensure it is valid +func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, instrument pair.CurrencyPair, tableName string) error { + signedChecksum := o.CalculatePartialOrderbookChecksum(wsEventData) + if signedChecksum != wsEventData.Checksum { + return fmt.Errorf("channel: %v. Orderbook partial for %v checksum invalid", tableName, instrument) + } + if o.Verbose { + log.Debug("Passed checksum!") + } + asks := o.AppendWsOrderbookItems(wsEventData.Asks) + bids := o.AppendWsOrderbookItems(wsEventData.Bids) + newOrderBook := orderbook.Base{ + Asks: asks, + Bids: bids, + AssetType: o.GetAssetTypeFromTableName(tableName), + CurrencyPair: wsEventData.InstrumentID, + LastUpdated: wsEventData.Timestamp, + Pair: instrument, + } + + err := o.Websocket.Orderbook.LoadSnapshot(newOrderBook, o.GetName(), true) + if err != nil { + return err + } + o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ + Exchange: o.GetName(), + Asset: o.GetAssetTypeFromTableName(tableName), + Pair: instrument, + } + return nil +} + +// WsProcessUpdateOrderbook updates an existing orderbook using websocket data +// After merging WS data, it will sort, validate and finally update the existing orderbook +func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, instrument pair.CurrencyPair, tableName string) error { + internalOrderbook, err := o.GetOrderbookEx(instrument, o.GetAssetTypeFromTableName(tableName)) + if err != nil { + return errors.New("orderbook nil, could not load existing orderbook") + } + if internalOrderbook.LastUpdated.After(wsEventData.Timestamp) { + if o.Verbose { + log.Errorf("Orderbook update out of order. Existing: %v, Attempted: %v", internalOrderbook.LastUpdated.Unix(), wsEventData.Timestamp.Unix()) + } + return errors.New("updated orderbook is older than existing") + } + internalOrderbook.Asks = o.WsUpdateOrderbookEntry(wsEventData.Asks, internalOrderbook.Asks) + internalOrderbook.Bids = o.WsUpdateOrderbookEntry(wsEventData.Bids, internalOrderbook.Bids) + sort.Slice(internalOrderbook.Asks, func(i, j int) bool { + return internalOrderbook.Asks[i].Price < internalOrderbook.Asks[j].Price + }) + sort.Slice(internalOrderbook.Bids, func(i, j int) bool { + return internalOrderbook.Bids[i].Price > internalOrderbook.Bids[j].Price + }) + checksum := o.CalculateUpdateOrderbookChecksum(internalOrderbook) + if checksum == wsEventData.Checksum { + if o.Verbose { + log.Debug("Orderbook valid") + } + internalOrderbook.LastUpdated = wsEventData.Timestamp + if o.Verbose { + log.Debug("Internalising orderbook") + } + + err := o.Websocket.Orderbook.LoadSnapshot(internalOrderbook, o.GetName(), true) + if err != nil { + log.Error(err) + } + o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ + Exchange: o.GetName(), + Asset: o.GetAssetTypeFromTableName(tableName), + Pair: instrument, + } + } else { + if o.Verbose { + log.Debug("Orderbook invalid") + } + return fmt.Errorf("channel: %v. Orderbook update for %v checksum invalid. Received %v Calculated %v", tableName, instrument, wsEventData.Checksum, checksum) + } + return nil +} + +// WsUpdateOrderbookEntry takes WS bid or ask data and merges it with existing orderbook bid or ask data +func (o *OKGroup) WsUpdateOrderbookEntry(wsEntries [][]interface{}, existingOrderbookEntries []orderbook.Item) []orderbook.Item { + for j := range wsEntries { + wsEntryPrice, _ := strconv.ParseFloat(wsEntries[j][0].(string), 64) + wsEntryAmount, _ := strconv.ParseFloat(wsEntries[j][1].(string), 64) + matchFound := false + for k := 0; k < len(existingOrderbookEntries); k++ { + if existingOrderbookEntries[k].Price != wsEntryPrice { + continue + } + matchFound = true + if wsEntryAmount == 0 { + existingOrderbookEntries = append(existingOrderbookEntries[:k], existingOrderbookEntries[k+1:]...) + k-- + continue + } + existingOrderbookEntries[k].Amount = wsEntryAmount + continue + } + if !matchFound { + existingOrderbookEntries = append(existingOrderbookEntries, orderbook.Item{ + Amount: wsEntryAmount, + Price: wsEntryPrice, + }) + } + } + return existingOrderbookEntries +} + +// CalculatePartialOrderbookChecksum alternates over the first 25 bid and ask entries from websocket data +// The checksum is made up of the price and the quantity with a semicolon (:) deliminating them +// This will also work when there are less than 25 entries (for whatever reason) +// eg Bid:Ask:Bid:Ask:Ask:Ask +func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketDataWrapper) int32 { + var checksum string + iterations := 25 + for i := 0; i < iterations; i++ { + bidsMessage := "" + askMessage := "" + if len(orderbookData.Bids)-1 >= i { + bidsMessage = fmt.Sprintf("%v:%v:", orderbookData.Bids[i][0], orderbookData.Bids[i][1]) + } + if len(orderbookData.Asks)-1 >= i { + askMessage = fmt.Sprintf("%v:%v:", orderbookData.Asks[i][0], orderbookData.Asks[i][1]) + + } + if checksum == "" { + checksum = fmt.Sprintf("%v%v", bidsMessage, askMessage) + } else { + checksum = fmt.Sprintf("%v%v%v", checksum, bidsMessage, askMessage) + } + } + checksum = strings.TrimSuffix(checksum, ":") + return int32(crc32.ChecksumIEEE([]byte(checksum))) +} + +// CalculateUpdateOrderbookChecksum alternates over the first 25 bid and ask entries of a merged orderbook +// The checksum is made up of the price and the quantity with a semicolon (:) deliminating them +// This will also work when there are less than 25 entries (for whatever reason) +// eg Bid:Ask:Bid:Ask:Ask:Ask +func (o *OKGroup) CalculateUpdateOrderbookChecksum(orderbookData orderbook.Base) int32 { + var checksum string + iterations := 25 + for i := 0; i < iterations; i++ { + bidsMessage := "" + askMessage := "" + if len(orderbookData.Bids)-1 >= i { + price := strconv.FormatFloat(orderbookData.Bids[i].Price, 'f', -1, 64) + amount := strconv.FormatFloat(orderbookData.Bids[i].Amount, 'f', -1, 64) + bidsMessage = fmt.Sprintf("%v:%v:", price, amount) + } + if len(orderbookData.Asks)-1 >= i { + price := strconv.FormatFloat(orderbookData.Asks[i].Price, 'f', -1, 64) + amount := strconv.FormatFloat(orderbookData.Asks[i].Amount, 'f', -1, 64) + askMessage = fmt.Sprintf("%v:%v:", price, amount) + } + if checksum == "" { + checksum = fmt.Sprintf("%v%v", bidsMessage, askMessage) + } else { + checksum = fmt.Sprintf("%v%v%v", checksum, bidsMessage, askMessage) + } + } + checksum = strings.TrimSuffix(checksum, ":") + return int32(crc32.ChecksumIEEE([]byte(checksum))) +} diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go new file mode 100644 index 00000000..0256247e --- /dev/null +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -0,0 +1,411 @@ +package okgroup + +import ( + "fmt" + "strconv" + "strings" + "sync" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/pair" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// Note: GoCryptoTrader wrapper funcs currently only support SPOT trades. +// Therefore this OKGroup_Wrapper can be shared between OKEX and OKCoin. +// When circumstances change, wrapper funcs can be split appropriately + +// Start starts the OKGroup go routine +func (o *OKGroup) Start(wg *sync.WaitGroup) { + wg.Add(1) + go func() { + o.Run() + wg.Done() + }() +} + +// Run implements the OKEX wrapper +func (o *OKGroup) Run() { + if o.Verbose { + log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) + log.Debugf("%s polling delay: %ds.\n", o.GetName(), o.RESTPollingDelay) + log.Debugf("%s %d currencies enabled: %s.\n", o.GetName(), len(o.EnabledPairs), o.EnabledPairs) + } + + prods, err := o.GetSpotTokenPairDetails() + if err != nil { + log.Errorf("%v failed to obtain available spot instruments. Err: %s", o.Name, err) + return + } + + var pairs []string + for x := range prods { + pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency) + } + + err = o.UpdateCurrencies(pairs, false, false) + if err != nil { + log.Errorf("%v failed to update available currencies. Err: %s", o.Name, err) + return + } +} + +// UpdateTicker updates and returns the ticker for a currency pair +func (o *OKGroup) UpdateTicker(p pair.CurrencyPair, assetType string) (tickerData ticker.Price, err error) { + resp, err := o.GetSpotAllTokenPairsInformationForCurrency(exchange.FormatExchangeCurrency(o.Name, p).String()) + if err != nil { + return + } + tickerData = ticker.Price{ + Ask: resp.BestAsk, + Bid: resp.BestBid, + CurrencyPair: exchange.FormatExchangeCurrency(o.Name, p).String(), + High: resp.High24h, + Last: resp.Last, + LastUpdated: resp.Timestamp, + Low: resp.Low24h, + Pair: p, + Volume: resp.BaseVolume24h, + } + + ticker.ProcessTicker(o.Name, p, tickerData, assetType) + + return +} + +// GetTickerPrice returns the ticker for a currency pair +func (o *OKGroup) GetTickerPrice(p pair.CurrencyPair, assetType string) (tickerData ticker.Price, err error) { + tickerData, err = ticker.GetTicker(o.GetName(), p, assetType) + if err != nil { + return o.UpdateTicker(p, assetType) + } + return +} + +// GetOrderbookEx returns orderbook base on the currency pair +func (o *OKGroup) GetOrderbookEx(p pair.CurrencyPair, assetType string) (resp orderbook.Base, err error) { + ob, err := orderbook.GetOrderbook(o.GetName(), p, assetType) + if err != nil { + return o.UpdateOrderbook(p, assetType) + } + return ob, nil +} + +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (o *OKGroup) UpdateOrderbook(p pair.CurrencyPair, assetType string) (resp orderbook.Base, err error) { + orderbookNew, err := o.GetSpotOrderBook(GetSpotOrderBookRequest{ + InstrumentID: exchange.FormatExchangeCurrency(o.Name, p).String(), + }) + if err != nil { + return + } + + for x := range orderbookNew.Bids { + amount, err := strconv.ParseFloat(orderbookNew.Bids[x][1], 64) + if err != nil { + log.Errorf("Could not convert %v to float64", orderbookNew.Bids[x][1]) + } + price, err := strconv.ParseFloat(orderbookNew.Bids[x][0], 64) + if err != nil { + log.Errorf("Could not convert %v to float64", orderbookNew.Bids[x][0]) + } + resp.Bids = append(resp.Bids, orderbook.Item{ + Amount: amount, + Price: price, + }) + } + + for x := range orderbookNew.Asks { + amount, err := strconv.ParseFloat(orderbookNew.Asks[x][1], 64) + if err != nil { + log.Errorf("Could not convert %v to float64", orderbookNew.Asks[x][1]) + } + price, err := strconv.ParseFloat(orderbookNew.Asks[x][0], 64) + if err != nil { + log.Errorf("Could not convert %v to float64", orderbookNew.Asks[x][0]) + } + resp.Asks = append(resp.Asks, orderbook.Item{ + Amount: amount, + Price: price, + }) + } + + orderbook.ProcessOrderbook(o.GetName(), p, resp, assetType) + return orderbook.GetOrderbook(o.Name, p, assetType) +} + +// GetAccountInfo retrieves balances for all enabled currencies +func (o *OKGroup) GetAccountInfo() (resp exchange.AccountInfo, err error) { + resp.Exchange = o.Name + currencies, err := o.GetSpotTradingAccounts() + currencyAccount := exchange.Account{} + + for _, curr := range currencies { + hold, err := strconv.ParseFloat(curr.Hold, 64) + if err != nil { + log.Errorf("Could not convert %v to float64", curr.Hold) + } + totalValue, err := strconv.ParseFloat(curr.Balance, 64) + if err != nil { + log.Errorf("Could not convert %v to float64", curr.Balance) + } + currencyAccount.Currencies = append(currencyAccount.Currencies, exchange.AccountCurrencyInfo{ + CurrencyName: curr.ID, + Hold: hold, + TotalValue: totalValue, + }) + } + + resp.Accounts = append(resp.Accounts, currencyAccount) + return +} + +// GetFundingHistory returns funding history, deposits and +// withdrawals +func (o *OKGroup) GetFundingHistory() (resp []exchange.FundHistory, err error) { + accountDepositHistory, err := o.GetAccountDepositHistory("") + if err != nil { + return + } + for _, deposit := range accountDepositHistory { + orderStatus := "" + switch deposit.Status { + case 0: + orderStatus = "waiting" + case 1: + orderStatus = "confirmation account" + case 2: + orderStatus = "recharge success" + } + + resp = append(resp, exchange.FundHistory{ + Amount: deposit.Amount, + Currency: deposit.Currency, + ExchangeName: o.Name, + Status: orderStatus, + Timestamp: deposit.Timestamp, + TransferID: deposit.TransactionID, + TransferType: "deposit", + }) + } + accountWithdrawlHistory, err := o.GetAccountWithdrawalHistory("") + for _, withdrawal := range accountWithdrawlHistory { + resp = append(resp, exchange.FundHistory{ + Amount: withdrawal.Amount, + Currency: withdrawal.Currency, + ExchangeName: o.Name, + Status: OrderStatus[withdrawal.Status], + Timestamp: withdrawal.Timestamp, + TransferID: withdrawal.Txid, + TransferType: "withdrawal", + }) + } + return resp, err +} + +// GetExchangeHistory returns historic trade data since exchange opening. +func (o *OKGroup) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented +} + +// SubmitOrder submits a new order +func (o *OKGroup) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (resp exchange.SubmitOrderResponse, err error) { + request := PlaceSpotOrderRequest{ + ClientOID: clientID, + InstrumentID: exchange.FormatExchangeCurrency(o.Name, p).String(), + Side: strings.ToLower(side.ToString()), + Type: strings.ToLower(orderType.ToString()), + Size: strconv.FormatFloat(amount, 'f', -1, 64), + } + if orderType == exchange.LimitOrderType { + request.Price = strconv.FormatFloat(price, 'f', -1, 64) + } + + orderResponse, err := o.PlaceSpotOrder(request) + if err != nil { + return + } + resp.IsOrderPlaced = orderResponse.Result + resp.OrderID = orderResponse.OrderID + + return +} + +// ModifyOrder will allow of changing orderbook placement and limit to +// market conversion +func (o *OKGroup) ModifyOrder(action exchange.ModifyOrder) (string, error) { + return "", common.ErrFunctionNotSupported +} + +// CancelOrder cancels an order by its corresponding ID number +func (o *OKGroup) CancelOrder(orderCancellation exchange.OrderCancellation) (err error) { + orderID, err := strconv.ParseInt(orderCancellation.OrderID, 10, 64) + if err != nil { + return + } + orderCancellationResponse, err := o.CancelSpotOrder(CancelSpotOrderRequest{ + InstrumentID: exchange.FormatExchangeCurrency(o.Name, orderCancellation.CurrencyPair).String(), + OrderID: orderID, + }) + if !orderCancellationResponse.Result { + err = fmt.Errorf("order %v failed to be cancelled", orderCancellationResponse.OrderID) + } + + return +} + +// CancelAllOrders cancels all orders associated with a currency pair +func (o *OKGroup) CancelAllOrders(orderCancellation exchange.OrderCancellation) (resp exchange.CancelAllOrdersResponse, _ error) { + orderIDs := strings.Split(orderCancellation.OrderID, ",") + var orderIDNumbers []int64 + for _, i := range orderIDs { + orderIDNumber, err := strconv.ParseInt(i, 10, 64) + if err != nil { + return resp, err + } + orderIDNumbers = append(orderIDNumbers, orderIDNumber) + } + + cancelOrdersResponse, err := o.CancelMultipleSpotOrders(CancelMultipleSpotOrdersRequest{ + InstrumentID: exchange.FormatExchangeCurrency(o.Name, orderCancellation.CurrencyPair).String(), + OrderIDs: orderIDNumbers, + }) + if err != nil { + return + } + + for _, orderMap := range cancelOrdersResponse { + for _, cancelledOrder := range orderMap { + resp.OrderStatus[fmt.Sprintf("%v", cancelledOrder.OrderID)] = fmt.Sprintf("%v", cancelledOrder.Result) + } + } + + return +} + +// GetOrderInfo returns information on a current open order +func (o *OKGroup) GetOrderInfo(orderID string) (resp exchange.OrderDetail, err error) { + order, err := o.GetSpotOrder(GetSpotOrderRequest{OrderID: orderID}) + if err != nil { + return + } + resp = exchange.OrderDetail{ + Amount: order.Size, + CurrencyPair: pair.NewCurrencyPairDelimiter(order.InstrumentID, o.ConfigCurrencyPairFormat.Delimiter), + Exchange: o.Name, + OrderDate: order.Timestamp, + ExecutedAmount: order.FilledSize, + Status: order.Status, + OrderSide: exchange.OrderSide(order.Side), + } + return +} + +// GetDepositAddress returns a deposit address for a specified currency +func (o *OKGroup) GetDepositAddress(p pair.CurrencyItem, accountID string) (_ string, err error) { + wallet, err := o.GetAccountDepositAddressForCurrency(p.Lower().String()) + if err != nil { + return + } + return wallet[0].Address, nil +} + +// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is +// submitted +func (o *OKGroup) WithdrawCryptocurrencyFunds(withdrawRequest exchange.WithdrawRequest) (string, error) { + withdrawal, err := o.AccountWithdraw(AccountWithdrawRequest{ + Amount: withdrawRequest.Amount, + Currency: withdrawRequest.Currency.Lower().String(), + Destination: 4, // 1, 2, 3 are all internal + Fee: withdrawRequest.FeeAmount, + ToAddress: withdrawRequest.Address, + TradePwd: withdrawRequest.TradePassword, + }) + if err != nil { + return "", err + } + if !withdrawal.Result { + return fmt.Sprintf("%v", withdrawal.WithdrawalID), fmt.Errorf("could not withdraw currency %v to %v, no error specified", withdrawRequest.Currency.String(), withdrawRequest.Address) + } + + return fmt.Sprintf("%v", withdrawal.WithdrawalID), nil +} + +// WithdrawFiatFunds returns a withdrawal ID when a +// withdrawal is submitted +func (o *OKGroup) WithdrawFiatFunds(withdrawRequest exchange.WithdrawRequest) (string, error) { + return "", common.ErrFunctionNotSupported +} + +// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a +// withdrawal is submitted +func (o *OKGroup) WithdrawFiatFundsToInternationalBank(withdrawRequest exchange.WithdrawRequest) (string, error) { + return "", common.ErrFunctionNotSupported +} + +// GetActiveOrders retrieves any orders that are active/open +func (o *OKGroup) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) (resp []exchange.OrderDetail, err error) { + for _, currency := range getOrdersRequest.Currencies { + spotOpenOrders, err := o.GetSpotOpenOrders(GetSpotOpenOrdersRequest{ + InstrumentID: exchange.FormatExchangeCurrency(o.Name, currency).String(), + }) + if err != nil { + return resp, err + } + for _, openOrder := range spotOpenOrders { + resp = append(resp, exchange.OrderDetail{ + Amount: openOrder.Size, + CurrencyPair: currency, + Exchange: o.Name, + ExecutedAmount: openOrder.FilledSize, + OrderDate: openOrder.Timestamp, + Status: openOrder.Status, + }) + } + } + + return +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (o *OKGroup) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) (resp []exchange.OrderDetail, err error) { + for _, currency := range getOrdersRequest.Currencies { + spotOpenOrders, err := o.GetSpotOrders(GetSpotOrdersRequest{ + InstrumentID: exchange.FormatExchangeCurrency(o.Name, currency).String(), + }) + if err != nil { + return resp, err + } + for _, openOrder := range spotOpenOrders { + resp = append(resp, exchange.OrderDetail{ + Amount: openOrder.Size, + CurrencyPair: currency, + Exchange: o.Name, + ExecutedAmount: openOrder.FilledSize, + OrderDate: openOrder.Timestamp, + Status: openOrder.Status, + }) + } + } + + return +} + +// GetWebsocket returns a pointer to the exchange websocket +func (o *OKGroup) GetWebsocket() (*exchange.Websocket, error) { + return o.Websocket, nil +} + +// GetFeeByType returns an estimate of fee based on type of transaction +func (o *OKGroup) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { + return o.GetFee(feeBuilder) +} + +// GetWithdrawCapabilities returns the types of withdrawal methods permitted by the exchange +func (o *OKGroup) GetWithdrawCapabilities() uint32 { + return o.GetWithdrawPermissions() +} diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index 7d3ebf6f..9af1ea94 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -160,7 +160,9 @@ func ProcessOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew Bas orderbookNew.Pair = p } orderbookNew.CurrencyPair = p.Pair().String() - orderbookNew.LastUpdated = time.Now() + if orderbookNew.LastUpdated.IsZero() { + orderbookNew.LastUpdated = time.Now() + } orderbook, err := GetOrderbookByExchange(exchangeName) if err != nil { diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 9faeb280..bfe83ca3 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -100,7 +100,7 @@ func (p *Poloniex) Setup(exch config.ExchangeConfig) { p.SetHTTPClientUserAgent(exch.HTTPUserAgent) p.RESTPollingDelay = exch.RESTPollingDelay p.Verbose = exch.Verbose - p.Websocket.SetEnabled(exch.Websocket) + p.Websocket.SetWsStatusAndConnection(exch.Websocket) p.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") p.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") p.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/yobit/yobit.go b/exchanges/yobit/yobit.go index c0fbe5eb..66946cdc 100644 --- a/exchanges/yobit/yobit.go +++ b/exchanges/yobit/yobit.go @@ -87,7 +87,7 @@ func (y *Yobit) Setup(exch config.ExchangeConfig) { y.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) y.RESTPollingDelay = exch.RESTPollingDelay y.Verbose = exch.Verbose - y.Websocket.SetEnabled(exch.Websocket) + y.Websocket.SetWsStatusAndConnection(exch.Websocket) y.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") y.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") y.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index ef6d6e1a..73e173d7 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -93,7 +93,7 @@ func (z *ZB) Setup(exch config.ExchangeConfig) { z.SetHTTPClientUserAgent(exch.HTTPUserAgent) z.RESTPollingDelay = exch.RESTPollingDelay z.Verbose = exch.Verbose - z.Websocket.SetEnabled(exch.Websocket) + z.Websocket.SetWsStatusAndConnection(exch.Websocket) z.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") z.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") z.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") diff --git a/go.mod b/go.mod index c3dad935..2bca8062 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/thrasher-/gocryptotrader require ( + github.com/google/go-querystring v1.0.0 github.com/gorilla/mux v1.7.0 github.com/gorilla/websocket v1.4.0 github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb diff --git a/go.sum b/go.sum index 7dffd19f..e7a91117 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= diff --git a/tools/exchange_template/main_file.tmpl b/tools/exchange_template/main_file.tmpl index ef3edaa9..a65a298d 100644 --- a/tools/exchange_template/main_file.tmpl +++ b/tools/exchange_template/main_file.tmpl @@ -61,7 +61,7 @@ func ({{.Variable}} *{{.CapitalName}}) Setup(exch config.ExchangeConfig) { {{.Variable}}.SetHTTPClientUserAgent(exch.HTTPUserAgent) {{.Variable}}.RESTPollingDelay = exch.RESTPollingDelay {{.Variable}}.Verbose = exch.Verbose - {{.Variable}}.Websocket.SetEnabled(exch.Websocket) + {{.Variable}}.Websocket.SetWsStatusAndConnection(exch.Websocket) {{.Variable}}.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") {{.Variable}}.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") {{.Variable}}.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")