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, ",")