From 11a68a9bb7c486d949fcca848fb536247af6fe0d Mon Sep 17 00:00:00 2001 From: Scott Date: Wed, 4 Dec 2019 14:16:23 +1100 Subject: [PATCH] Utilising authenticated websocket functions in exchange wrappers (#384) * Basic concept commit * Initial changes to support bitfinex v2. Reverts linter changes as they suck. Exports bitfinex ws types * Adds ticker, trade and orderbook support * Candles sub that returns no data COMPLETE * Adds authenticated ws support * Adds the barebones endpoints to support * Adds more endpoints * Even more endpoints * minicommit to switch and test * All the interactive types * Adds support for simultaneous connections. Updates tests. Nothing is working * Successfully adds place order. Moves all authenticated endpoints to new switch case * Cancel order and modify order * Cancel all orders, cancel multi orders * Finalises implementation. Uses testMain * Adds WS wrapper support for some funcs * Fixing rebasing issues * Replaces use of currency as a variable. Updates a lot of coinut websocket auth endpoint stuff * Fixes some fun for loops with GetEnabledPairs * Fixes tests impacted by currency var change * Adds coinut support for WS functions. Replaces `order` vars with `ord`. Fixes some for loops too. Removes verbose from bitfinex * So many panics * I'm fixing a hole, where the panics get in, and stops my mind from wandering, where it will go * Moves func `CanUseAuthenticatedWebsocketEndpoint` to Websocket package as it fits better. Adds test coverage of `CanUseAuthenticatedWebsocketEndpoint` * Finishes up all of coinuts ws implementations. * GateIO implementation * Adds some helper funcs for types, sides and status. Adds support for huobi. Removes unnecessary type * Adds forgotten huobi endpoint * Fixes cancel order endpoint * go hates my formatting and so do I * The process to get authenticated kraken websocket to work. Uses testmain. Adds new auth channel, auth subscriptions, auth data handling. Not working yet * Finishes open orders handling * Mini update for status only updates * Fixes some kraken points * Finishes WS kraken since it doesn't work * Unfinished commit, cleaning up types * Finishes the const replacing * Fixes extra GetNAmes after rebase * An end to the cleanup. testmain for gateio * Adds ZB support * Bitfinex cleanup. Renamed func * Testmain-47s for everyone!!! yayaaaaaaa * Adds kraken websocket wrapper support * Fixes rebase issues * Fixes tests from rebase * Adds test for conversion. Fixes for loop. Updates test order pricing. Fixes some poor made tests. Adds proper error handling for ws responses instead of logging them. Fixed issue where commented code ruined kraken ws. * Fixes secret linting issues. Prioritises bitfinex channelID responses over authorised * Fixes sloppy error/var declarations * Fixes crazy bad logic where submit order errors weren't really considered. Parralols alphapoint/alphapoint_test.go. Removes buffer for multi-websocket comms channel. * Removal of inline string and removal of redundant nil checkerinos * Fixes err checks. Checks whether float has decimal. Fixes append. Drops omitempties. Parallel to some tests. Moves var declarations * Replaces my lazy sprintfs with strconv.FormatInt(time.Now().Unix(), 10) * Adds shiny new FullyMatched bool. Fixes coinbene buy sell consts * Fixes oopsie with coinbene const replacement * Fixes currency issue * Cleans up new places that use JSONDecode * Fixes huge panic bug from string int conversion. Adds large testtable for strings to order types * Fixes some more strconversion issues. Fixes table test var usage. Changes mapperino name * Added some new scenarios for number splitting * Fixes lint issues * negative num fix * Typo fix * Accuracy warning comment --- common/convert/convert.go | 30 + common/convert/convert_test.go | 56 + currency/pair_test.go | 8 +- currency/storage.go | 16 +- engine/orders.go | 6 +- engine/syncer.go | 20 +- exchanges/alphapoint/alphapoint_test.go | 272 ++--- exchanges/alphapoint/alphapoint_wrapper.go | 12 +- exchanges/anx/anx_test.go | 6 +- exchanges/anx/anx_wrapper.go | 21 +- exchanges/binance/binance_test.go | 5 +- exchanges/binance/binance_websocket.go | 5 +- exchanges/binance/binance_wrapper.go | 12 +- exchanges/bitfinex/bitfinex.go | 15 +- exchanges/bitfinex/bitfinex_test.go | 239 +++- exchanges/bitfinex/bitfinex_types.go | 247 +++- exchanges/bitfinex/bitfinex_websocket.go | 1050 +++++++++++------ exchanges/bitfinex/bitfinex_wrapper.go | 111 +- exchanges/bitflyer/bitflyer_test.go | 72 +- exchanges/bithumb/bithumb_test.go | 71 +- exchanges/bithumb/bithumb_wrapper.go | 24 +- exchanges/bitmex/bitmex_test.go | 55 +- exchanges/bitmex/bitmex_wrapper.go | 11 +- exchanges/bitstamp/bitstamp_test.go | 3 +- exchanges/bitstamp/bitstamp_wrapper.go | 12 +- exchanges/bittrex/bittrex.go | 2 +- exchanges/bittrex/bittrex_test.go | 57 +- exchanges/bittrex/bittrex_wrapper.go | 11 +- exchanges/btse/btse_test.go | 4 +- exchanges/btse/btse_wrapper.go | 9 +- exchanges/coinbasepro/coinbasepro_test.go | 52 +- .../coinbasepro/coinbasepro_websocket.go | 3 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 25 +- exchanges/coinbene/coinbene.go | 7 +- exchanges/coinbene/coinbene_test.go | 3 +- exchanges/coinbene/coinbene_wrapper.go | 9 +- exchanges/coinut/coinut.go | 96 +- exchanges/coinut/coinut_test.go | 120 +- exchanges/coinut/coinut_types.go | 132 +-- exchanges/coinut/coinut_websocket.go | 167 ++- exchanges/coinut/coinut_wrapper.go | 549 +++++---- exchanges/exchange_types.go | 75 +- exchanges/exmo/exmo_test.go | 58 +- exchanges/exmo/exmo_wrapper.go | 35 +- exchanges/gateio/gateio_test.go | 80 +- exchanges/gateio/gateio_types.go | 28 +- exchanges/gateio/gateio_websocket.go | 5 +- exchanges/gateio/gateio_wrapper.go | 215 ++-- exchanges/gemini/gemini_test.go | 4 - exchanges/gemini/gemini_websocket.go | 8 +- exchanges/gemini/gemini_wrapper.go | 15 +- exchanges/hitbtc/hitbtc.go | 2 +- exchanges/hitbtc/hitbtc_test.go | 56 +- exchanges/hitbtc/hitbtc_websocket.go | 5 +- exchanges/hitbtc/hitbtc_wrapper.go | 46 +- exchanges/huobi/huobi.go | 14 +- exchanges/huobi/huobi_test.go | 78 +- exchanges/huobi/huobi_types.go | 31 +- exchanges/huobi/huobi_websocket.go | 6 +- exchanges/huobi/huobi_wrapper.go | 267 +++-- exchanges/itbit/itbit_test.go | 49 +- exchanges/itbit/itbit_wrapper.go | 12 +- exchanges/kraken/kraken.go | 17 +- exchanges/kraken/kraken_test.go | 135 ++- exchanges/kraken/kraken_types.go | 110 +- exchanges/kraken/kraken_websocket.go | 460 ++++++-- exchanges/kraken/kraken_wrapper.go | 133 ++- exchanges/lakebtc/lakebtc_test.go | 89 +- exchanges/lakebtc/lakebtc_wrapper.go | 15 +- exchanges/lbank/lbank_test.go | 6 +- exchanges/lbank/lbank_wrapper.go | 3 + exchanges/localbitcoins/localbitcoins.go | 2 +- exchanges/localbitcoins/localbitcoins_test.go | 5 +- exchanges/okcoin/okcoin_test.go | 147 +-- exchanges/okex/okex_test.go | 203 +--- exchanges/okgroup/okgroup.go | 26 +- exchanges/okgroup/okgroup_test.go | 78 -- exchanges/okgroup/okgroup_wrapper.go | 3 + exchanges/order/order_test.go | 136 +++ exchanges/order/order_types.go | 3 +- exchanges/order/orders.go | 86 +- exchanges/orderbook/orderbook_test.go | 12 +- exchanges/poloniex/poloniex_test.go | 6 +- exchanges/poloniex/poloniex_websocket.go | 12 +- exchanges/poloniex/poloniex_wrapper.go | 26 +- exchanges/websocket/wshandler/wshandler.go | 20 +- .../websocket/wshandler/wshandler_test.go | 19 + .../websocket/wshandler/wshandler_types.go | 3 +- exchanges/yobit/yobit_test.go | 51 +- exchanges/yobit/yobit_wrapper.go | 32 +- exchanges/zb/zb.go | 6 +- exchanges/zb/zb_test.go | 61 +- exchanges/zb/zb_types.go | 5 +- exchanges/zb/zb_websocket.go | 34 - exchanges/zb/zb_websocket_types.go | 103 +- exchanges/zb/zb_wrapper.go | 169 ++- 96 files changed, 4112 insertions(+), 2818 deletions(-) delete mode 100644 exchanges/okgroup/okgroup_test.go diff --git a/common/convert/convert.go b/common/convert/convert.go index 5bf7e740..1993de89 100644 --- a/common/convert/convert.go +++ b/common/convert/convert.go @@ -1,8 +1,11 @@ package convert import ( + "errors" "fmt" + "math" "strconv" + "strings" "time" ) @@ -77,3 +80,30 @@ func UnixMillis(t time.Time) int64 { func RecvWindow(d time.Duration) int64 { return int64(d) / int64(time.Millisecond) } + +// SplitFloatDecimals takes in a float64 and splits +// the decimals into their own integers +// Warning. Passing in numbers with many decimals +// can lead to a loss of accuracy +func SplitFloatDecimals(input float64) (baseNum, decimalNum int64, err error) { + if input > float64(math.MaxInt64) { + return 0, 0, errors.New("number too large to split into integers") + } + if input == float64(int64(input)) { + return int64(input), 0, nil + } + decStr := strconv.FormatFloat(input, 'f', -1, 64) + splitNum := strings.Split(decStr, ".") + baseNum, err = strconv.ParseInt(splitNum[0], 10, 64) + if err != nil { + return 0, 0, err + } + decimalNum, err = strconv.ParseInt(splitNum[1], 10, 64) + if err != nil { + return 0, 0, err + } + if baseNum < 0 { + decimalNum *= -1 + } + return baseNum, decimalNum, nil +} diff --git a/common/convert/convert_test.go b/common/convert/convert_test.go index 03aabb3d..60d84153 100644 --- a/common/convert/convert_test.go +++ b/common/convert/convert_test.go @@ -1,6 +1,7 @@ package convert import ( + "math" "testing" "time" ) @@ -149,3 +150,58 @@ func TestRecvWindow(t *testing.T) { expectedOutput, actualOutput) } } + +// TestSplitFloatDecimals ensures SplitFloatDecimals +// accurately splits decimals into integers +func TestSplitFloatDecimals(t *testing.T) { + x, y, err := SplitFloatDecimals(1.2) + if err != nil { + t.Error(err) + } + if x != 1 && y != 2 { + t.Error("Conversion error") + } + x, y, err = SplitFloatDecimals(123456.654321) + if err != nil { + t.Error(err) + } + if x != 123456 && y != 654321 { + t.Error("Conversion error") + } + x, y, err = SplitFloatDecimals(123.111000) + if err != nil { + t.Error(err) + } + if x != 123 && y != 111 { + t.Error("Conversion error") + } + x, y, err = SplitFloatDecimals(0123.111001) + if err != nil { + t.Error(err) + } + if x != 123 && y != 111001 { + t.Error("Conversion error") + } + x, y, err = SplitFloatDecimals(1) + if err != nil { + t.Error(err) + } + if x != 1 && y != 0 { + t.Error("Conversion error") + } + _, _, err = SplitFloatDecimals(float64(math.MaxInt64) + 1) + if err == nil { + t.Error("Expected conversion error") + } + _, _, err = SplitFloatDecimals(1797693134862315700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111) + if err == nil { + t.Error("Expected conversion error") + } + x, y, err = SplitFloatDecimals(-1.2) + if err != nil { + t.Error(err) + } + if x != -1 && y != -2 { + t.Error("Conversion error") + } +} diff --git a/currency/pair_test.go b/currency/pair_test.go index 837d52e2..b3856528 100644 --- a/currency/pair_test.go +++ b/currency/pair_test.go @@ -343,10 +343,10 @@ func TestNewPairDelimiter(t *testing.T) { // specific index func TestNewPairFromIndex(t *testing.T) { t.Parallel() - currency := defaultPair + curr := defaultPair index := "BTC" - pair, err := NewPairFromIndex(currency, index) + pair, err := NewPairFromIndex(curr, index) if err != nil { t.Error("NewPairFromIndex() error", err) } @@ -362,9 +362,9 @@ func TestNewPairFromIndex(t *testing.T) { ) } - currency = "DOGEBTC" + curr = "DOGEBTC" - pair, err = NewPairFromIndex(currency, index) + pair, err = NewPairFromIndex(curr, index) if err != nil { t.Error("NewPairFromIndex() error", err) } diff --git a/currency/storage.go b/currency/storage.go index b0452f0e..3ed5aec0 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -175,19 +175,15 @@ func (s *Storage) SetupConversionRates() { // SetDefaultFiatCurrencies assigns the default fiat currency list and adds it // to the running list func (s *Storage) SetDefaultFiatCurrencies(c ...Code) { - for _, currency := range c { - s.defaultFiatCurrencies = append(s.defaultFiatCurrencies, currency) - s.fiatCurrencies = append(s.fiatCurrencies, currency) - } + s.defaultFiatCurrencies = append(s.defaultFiatCurrencies, c...) + s.fiatCurrencies = append(s.fiatCurrencies, c...) } // SetDefaultCryptocurrencies assigns the default cryptocurrency list and adds // it to the running list func (s *Storage) SetDefaultCryptocurrencies(c ...Code) { - for _, currency := range c { - s.defaultCryptoCurrencies = append(s.defaultCryptoCurrencies, currency) - s.cryptocurrencies = append(s.cryptocurrencies, currency) - } + s.defaultCryptoCurrencies = append(s.defaultCryptoCurrencies, c...) + s.cryptocurrencies = append(s.cryptocurrencies, c...) } // SetupForexProviders sets up a new instance of the forex providers @@ -507,8 +503,8 @@ func (s *Storage) GetTotalMarketCryptocurrencies() (Currencies, error) { // IsDefaultCurrency returns if a currency is a default currency func (s *Storage) IsDefaultCurrency(c Code) bool { t, _ := GetTranslation(c) - for _, d := range s.defaultFiatCurrencies { - if d.Match(c) || d.Match(t) { + for i := range s.defaultFiatCurrencies { + if s.defaultFiatCurrencies[i].Match(c) || s.defaultFiatCurrencies[i].Match(t) { return true } } diff --git a/engine/orders.go b/engine/orders.go index 303df534..aa50406e 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -264,11 +264,11 @@ func (o *orderManager) processOrders() { } for x := range result { - order := &result[x] - result := o.orderStore.Add(order) + ord := &result[x] + result := o.orderStore.Add(ord) if result != ErrOrdersAlreadyExists { msg := fmt.Sprintf("Order manager: Exchange %s added order ID=%v pair=%v price=%v amount=%v side=%v type=%v.", - order.Exchange, order.ID, order.CurrencyPair, order.Price, order.Amount, order.OrderSide, order.OrderType) + ord.Exchange, ord.ID, ord.CurrencyPair, ord.Price, ord.Amount, ord.OrderSide, ord.OrderType) log.Debugf(log.OrderMgr, "%v\n", msg) Bot.CommsManager.PushEvent(base.Event{ Type: "order", diff --git a/engine/syncer.go b/engine/syncer.go index 6807dd9c..d900160e 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -310,16 +310,17 @@ func (e *ExchangeCurrencyPairSyncer) worker() { } for y := range assetTypes { - for _, p := range Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) { + enabledPairs := Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) + for i := range enabledPairs { if atomic.LoadInt32(&e.shutdown) == 1 { return } - if !e.exists(exchangeName, p, assetTypes[y]) { + if !e.exists(exchangeName, enabledPairs[i], assetTypes[y]) { c := CurrencyPairSyncAgent{ AssetType: assetTypes[y], Exchange: exchangeName, - Pair: p, + Pair: enabledPairs[i], } if e.Cfg.SyncTicker { @@ -346,7 +347,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { e.add(&c) } - c, err := e.get(exchangeName, p, assetTypes[y]) + c, err := e.get(exchangeName, enabledPairs[i], assetTypes[y]) if err != nil { log.Errorf(log.SyncMgr, "failed to get item. Err: %s\n", err) continue @@ -354,7 +355,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { if switchedToRest && usingWebsocket { log.Infof(log.SyncMgr, "%s %s: Websocket re-enabled, switching from rest to websocket\n", - c.Exchange, FormatCurrency(p).String()) + c.Exchange, FormatCurrency(enabledPairs[i]).String()) switchedToRest = false } if e.Cfg.SyncTicker { @@ -371,7 +372,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { c.Ticker.IsUsingREST = true log.Warnf(log.SyncMgr, "%s %s: No ticker update after 10 seconds, switching from websocket to rest\n", - c.Exchange, FormatCurrency(p).String()) + c.Exchange, FormatCurrency(enabledPairs[i]).String()) switchedToRest = true e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, false) } @@ -521,14 +522,15 @@ func (e *ExchangeCurrencyPairSyncer) Start() { } for y := range assetTypes { - for _, p := range Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) { - if e.exists(exchangeName, p, assetTypes[y]) { + enabledPairs := Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) + for i := range enabledPairs { + if e.exists(exchangeName, enabledPairs[i], assetTypes[y]) { continue } c := CurrencyPairSyncAgent{ AssetType: assetTypes[y], Exchange: exchangeName, - Pair: p, + Pair: enabledPairs[i], } if e.Cfg.SyncTicker { diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index 964cd0d8..910e9880 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -2,6 +2,8 @@ package alphapoint import ( "encoding/json" + "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -17,45 +19,37 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { - t.Parallel() - SetDefaults := Alphapoint{} +var a Alphapoint - SetDefaults.SetDefaults() - if SetDefaults.API.Endpoints.URL != "https://sim3.alphapoint.com:8400" { - t.Error("SetDefaults: String Incorrect -", SetDefaults.API.Endpoints.URL) - } - if SetDefaults.API.Endpoints.WebsocketURL != "wss://sim3.alphapoint.com:8401/v1/GetTicker/" { - t.Error("SetDefaults: String Incorrect -", SetDefaults.API.Endpoints.WebsocketURL) - } -} - -func testSetAPIKey(a *Alphapoint) { +func TestMain(m *testing.M) { + a.SetDefaults() a.API.Credentials.Key = apiKey a.API.Credentials.Secret = apiSecret a.API.AuthenticatedSupport = true -} - -func testIsAPIKeysSet(a *Alphapoint) bool { - if apiKey != "" && apiSecret != "" && a.API.AuthenticatedSupport { - return true + if a.API.Endpoints.URL != "https://sim3.alphapoint.com:8400" { + log.Fatal("SetDefaults: String Incorrect -", a.API.Endpoints.URL) } - return false + if a.API.Endpoints.WebsocketURL != "wss://sim3.alphapoint.com:8401/v1/GetTicker/" { + log.Fatal("SetDefaults: String Incorrect -", a.API.Endpoints.WebsocketURL) + } + os.Exit(m.Run()) } -func TestGetTicker(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() +func areTestAPIKeysSet() bool { + return a.ValidateAPICredentials() +} + +func TestGetTicker(t *testing.T) { + t.Parallel() var ticker Ticker var err error - if onlineTest { - ticker, err = alpha.GetTicker("BTCUSD") + ticker, err = a.GetTicker("BTCUSD") if err != nil { t.Fatal("Alphapoint GetTicker init error: ", err) } - _, err = alpha.GetTicker("wigwham") + _, err = a.GetTicker("wigwham") if err == nil { t.Error("Alphapoint GetTicker Expected error") } @@ -80,19 +74,16 @@ func TestGetTicker(t *testing.T) { } func TestGetTrades(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() - + t.Parallel() var trades Trades var err error - if onlineTest { - trades, err = alpha.GetTrades("BTCUSD", 0, 10) + trades, err = a.GetTrades("BTCUSD", 0, 10) if err != nil { t.Fatalf("Init error: %s", err) } - _, err = alpha.GetTrades("wigwham", 0, 10) + _, err = a.GetTrades("wigwham", 0, 10) if err == nil { t.Fatal("GetTrades Expected error") } @@ -121,18 +112,15 @@ func TestGetTrades(t *testing.T) { } func TestGetTradesByDate(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() - + t.Parallel() var trades Trades var err error - if onlineTest { - trades, err = alpha.GetTradesByDate("BTCUSD", 1414799400, 1414800000) + trades, err = a.GetTradesByDate("BTCUSD", 1414799400, 1414800000) if err != nil { t.Errorf("Init error: %s", err) } - _, err = alpha.GetTradesByDate("wigwham", 1414799400, 1414800000) + _, err = a.GetTradesByDate("wigwham", 1414799400, 1414800000) if err == nil { t.Error("GetTradesByDate Expected error") } @@ -168,19 +156,16 @@ func TestGetTradesByDate(t *testing.T) { } func TestGetOrderbook(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() - + t.Parallel() var orderBook Orderbook var err error - if onlineTest { - orderBook, err = alpha.GetOrderbook("BTCUSD") + orderBook, err = a.GetOrderbook("BTCUSD") if err != nil { t.Errorf("Init error: %s", err) } - _, err = alpha.GetOrderbook("wigwham") + _, err = a.GetOrderbook("wigwham") if err == nil { t.Error("GetOrderbook() Expected error") } @@ -213,14 +198,12 @@ func TestGetOrderbook(t *testing.T) { } func TestGetProductPairs(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() - + t.Parallel() var products ProductPairs var err error if onlineTest { - products, err = alpha.GetProductPairs() + products, err = a.GetProductPairs() if err != nil { t.Errorf("Init error: %s", err) } @@ -253,14 +236,12 @@ func TestGetProductPairs(t *testing.T) { } func TestGetProducts(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() - + t.Parallel() var products Products var err error if onlineTest { - products, err = alpha.GetProducts() + products, err = a.GetProducts() if err != nil { t.Errorf("Init error: %s", err) } @@ -293,12 +274,9 @@ func TestGetProducts(t *testing.T) { } func TestCreateAccount(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } err := a.CreateAccount("test", "account", "something@something.com", "0292383745", "lolcat123") @@ -316,12 +294,9 @@ func TestCreateAccount(t *testing.T) { } func TestGetUserInfo(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetUserInfo() @@ -331,12 +306,9 @@ func TestGetUserInfo(t *testing.T) { } func TestSetUserInfo(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.SetUserInfo("bla", "bla", "1", "meh", true, true) @@ -346,12 +318,9 @@ func TestSetUserInfo(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetAccountInfo() @@ -361,12 +330,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestGetAccountTrades(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetAccountTrades("", 1, 2) @@ -376,12 +342,9 @@ func TestGetAccountTrades(t *testing.T) { } func TestGetDepositAddresses(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetDepositAddresses() @@ -391,12 +354,9 @@ func TestGetDepositAddresses(t *testing.T) { } func TestWithdrawCoins(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } err := a.WithdrawCoins("", "", "", 0.01) @@ -406,12 +366,9 @@ func TestWithdrawCoins(t *testing.T) { } func TestCreateOrder(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.CreateOrder("", "", order.Limit.String(), 0.01, 0) @@ -421,12 +378,9 @@ func TestCreateOrder(t *testing.T) { } func TestModifyExistingOrder(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.ModifyExistingOrder("", 1, 1) @@ -436,12 +390,9 @@ func TestModifyExistingOrder(t *testing.T) { } func TestCancelAllExistingOrders(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } err := a.CancelAllExistingOrders("") @@ -451,12 +402,9 @@ func TestCancelAllExistingOrders(t *testing.T) { } func TestGetOrders(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetOrders() @@ -466,12 +414,9 @@ func TestGetOrders(t *testing.T) { } func TestGetOrderFee(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetOrderFee("", "", 1, 1) @@ -481,45 +426,38 @@ func TestGetOrderFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() + t.Parallel() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := a.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } _, err := a.GetActiveOrders(&getOrdersRequest) - if areTestAPIKeysSet(a) && err != nil { + if areTestAPIKeysSet() && err != nil { t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet(a) && err == nil { + } else if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } } func TestGetOrderHistory(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } _, err := a.GetOrderHistory(&getOrdersRequest) - if areTestAPIKeysSet(a) && err != nil { + if areTestAPIKeysSet() && err != nil { t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet(a) && err == nil { + } else if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } } @@ -527,15 +465,9 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- -func areTestAPIKeysSet(a *Alphapoint) bool { - return a.ValidateAPICredentials() -} - func TestSubmitOrder(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - - if areTestAPIKeysSet(a) && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -553,10 +485,10 @@ func TestSubmitOrder(t *testing.T) { } response, err := a.SubmitOrder(orderSubmission) - if !areTestAPIKeysSet(a) && err == nil { + if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } - if areTestAPIKeysSet(a) && err != nil { + if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) if !response.IsOrderPlaced { @@ -566,15 +498,12 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - - if areTestAPIKeysSet(a) && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -583,24 +512,21 @@ func TestCancelExchangeOrder(t *testing.T) { } err := a.CancelOrder(orderCancellation) - if !areTestAPIKeysSet(a) && err == nil { + if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } - if areTestAPIKeysSet(a) && err != nil { + if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) } } func TestCancelAllExchangeOrders(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - - if areTestAPIKeysSet(a) && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -609,11 +535,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { } resp, err := a.CancelAllOrders(orderCancellation) - - if !areTestAPIKeysSet(a) && err == nil { + if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } - if areTestAPIKeysSet(a) && err != nil { + if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) } @@ -623,9 +548,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := a.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -633,10 +559,8 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() + t.Parallel() var withdrawCryptoRequest = exchange.CryptoWithdrawRequest{} - _, err := a.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not implemented', received %v", err) @@ -644,10 +568,8 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - - if areTestAPIKeysSet(a) && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -659,10 +581,8 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - - if areTestAPIKeysSet(a) && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 2eb89200..4df2c5d5 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -212,16 +212,18 @@ func (a *Alphapoint) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) s.OrderSide.String(), s.Amount, s.Price) - + if err != nil { + return submitOrderResponse, err + } if response > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index e6b29f10..c817c725 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -86,7 +86,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { t.Parallel() var feeBuilder = setFeeBuilder() a.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -171,9 +171,7 @@ func TestFormatWithdrawPermissions(t *testing.T) { t.Parallel() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.WithdrawCryptoWithEmailText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := a.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } @@ -254,7 +252,6 @@ func TestCancelExchangeOrder(t *testing.T) { } currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -280,7 +277,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { } currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 96b657ff..2a393832 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -2,6 +2,7 @@ package anx import ( "fmt" + "strconv" "strings" "sync" "time" @@ -346,16 +347,18 @@ func (a *ANX) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { false, "", false) - + if err != nil { + return submitOrderResponse, err + } if response != "" { submitOrderResponse.OrderID = response } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -391,9 +394,9 @@ func (a *ANX) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) return cancelAllOrdersResponse, err } - for _, order := range resp.OrderCancellationResponses { - if order.Error != CancelRequestSubmitted { - cancelAllOrdersResponse.Status[order.UUID] = order.Error + for i := range resp.OrderCancellationResponses { + if resp.OrderCancellationResponses[i].Error != CancelRequestSubmitted { + cancelAllOrdersResponse.Status[resp.OrderCancellationResponses[i].UUID] = resp.OrderCancellationResponses[i].Error } } @@ -414,7 +417,7 @@ func (a *ANX) GetDepositAddress(cryptocurrency currency.Code, _ string) (string, // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted func (a *ANX) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { - return a.Send(withdrawRequest.Currency.String(), withdrawRequest.Address, "", fmt.Sprintf("%v", withdrawRequest.Amount)) + return a.Send(withdrawRequest.Currency.String(), withdrawRequest.Address, "", strconv.FormatFloat(withdrawRequest.Amount, 'f', -1, 64)) } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 0f142019..0934d278 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -194,7 +194,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -282,7 +282,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { t.Parallel() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := b.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) @@ -382,7 +381,6 @@ func TestCancelExchangeOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -407,7 +405,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index ab437935..21d40cd3 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -53,8 +53,9 @@ func (b *Binance) WsConnect() error { kline + "/" + depth - for _, ePair := range b.GetEnabledPairs(asset.Spot) { - err = b.SeedLocalCache(ePair) + enabledPairs := b.GetEnabledPairs(asset.Spot) + for i := range enabledPairs { + err = b.SeedLocalCache(enabledPairs[i]) if err != nil { return err } diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 7d66a87b..59203ff1 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -426,16 +426,18 @@ func (b *Binance) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } response, err := b.NewOrder(&orderRequest) - + if err != nil { + return submitOrderResponse, err + } if response.OrderID > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true + if response.ExecutedQty == response.OrigQty { + submitOrderResponse.FullyMatched = true } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index f85e4c9d..2877b2cb 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -85,8 +85,9 @@ const ( // depending on some factors (e.g. servers load, endpoint, etc.). type Bitfinex struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection - WebsocketSubdChannels map[int]WebsocketChanInfo + WebsocketConn *wshandler.WebsocketConnection + AuthenticatedWebsocketConn *wshandler.WebsocketConnection + WebsocketSubdChannels map[int]WebsocketChanInfo } // GetPlatformStatus returns the Bifinex platform status @@ -358,11 +359,11 @@ func (b *Bitfinex) GetTradesV2(currencyPair string, timestampStart, timestampEnd } var tempHistory TradeStructureV2 - for _, data := range resp { - tempHistory.TID = int64(data[0].(float64)) - tempHistory.Timestamp = int64(data[1].(float64)) - tempHistory.Amount = data[2].(float64) - tempHistory.Price = data[3].(float64) + for i := range resp { + tempHistory.TID = int64(resp[i][0].(float64)) + tempHistory.Timestamp = int64(resp[i][1].(float64)) + tempHistory.Amount = resp[i][2].(float64) + tempHistory.Price = resp[i][3].(float64) tempHistory.Exchange = b.Name tempHistory.Type = order.Buy.String() diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 04f44be4..ce29001d 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -1,8 +1,10 @@ package bitfinex import ( + "log" "net/http" "net/url" + "os" "reflect" "testing" "time" @@ -25,37 +27,43 @@ const ( ) var b Bitfinex +var wsAuthExecuted bool -func TestSetup(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Bitfinex load config error", err) + log.Fatal("Bitfinex load config error", err) } bfxConfig, err := cfg.GetExchangeConfig("Bitfinex") if err != nil { - t.Error("Bitfinex Setup() init error") + log.Fatal("Bitfinex Setup() init error") } err = b.Setup(bfxConfig) if err != nil { - t.Fatal("Bitfinex setup error", err) + log.Fatal("Bitfinex setup error", err) } b.API.Credentials.Key = apiKey b.API.Credentials.Secret = apiSecret if !b.Enabled || b.API.AuthenticatedSupport || b.Verbose || b.Websocket.IsEnabled() || len(b.BaseCurrencies) < 1 { - t.Error("Bitfinex Setup values not set correctly") + log.Fatal("Bitfinex Setup values not set correctly") + } + + if areTestAPIKeysSet() { + b.API.AuthenticatedSupport = true + b.API.AuthenticatedWebsocketSupport = true } - b.API.AuthenticatedSupport = true - b.API.AuthenticatedWebsocketSupport = true // custom rate limit for testing b.Requester.SetRateLimit(true, time.Millisecond*300, 1) b.Requester.SetRateLimit(false, time.Millisecond*300, 1) + os.Exit(m.Run()) } func TestAppendOptionalDelimiter(t *testing.T) { + t.Parallel() curr1 := currency.NewPairFromString("BTCUSD") b.appendOptionalDelimiter(&curr1) if curr1.Delimiter != "" { @@ -71,7 +79,6 @@ func TestAppendOptionalDelimiter(t *testing.T) { func TestGetPlatformStatus(t *testing.T) { t.Parallel() - result, err := b.GetPlatformStatus() if err != nil { t.Errorf("TestGetPlatformStatus error: %s", err) @@ -653,7 +660,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -665,11 +672,10 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) var feeBuilder = setFeeBuilder() + t.Parallel() - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) @@ -738,20 +744,16 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() + t.Parallel() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -765,9 +767,7 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -787,9 +787,7 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -806,23 +804,25 @@ func TestSubmitOrder(t *testing.T) { ClientID: "meowOrder", } response, err := b.SubmitOrder(orderSubmission) - if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { - t.Errorf("Order failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { + + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not cancel orders: %v", err) + } + if areTestAPIKeysSet() && !response.IsOrderPlaced { + t.Error("Order not placed") + } + if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -840,15 +840,12 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrdera(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -871,6 +868,10 @@ func TestCancelAllExchangeOrdera(t *testing.T) { } func TestModifyOrder(t *testing.T) { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -878,8 +879,7 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -903,9 +903,7 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -938,9 +936,7 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -979,6 +975,7 @@ func TestWithdrawInternationalBank(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { + t.Parallel() if areTestAPIKeysSet() { _, err := b.GetDepositAddress(currency.BTC, "deposit") if err != nil { @@ -992,40 +989,168 @@ func TestGetDepositAddress(t *testing.T) { } } -// TestWsAuth dials websocket, sends login request. -func TestWsAuth(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) - } - b.WebsocketConn = &wshandler.WebsocketConnection{ +func setupWs() { + b.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ ExchangeName: b.Name, - URL: b.Websocket.GetWebsocketURL(), + URL: authenticatedBitfinexWebsocketEndpoint, Verbose: b.Verbose, ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, } var dialer websocket.Dialer - err := b.WebsocketConn.Dial(&dialer, http.Header{}) + err := b.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) if err != nil { - t.Fatal(err) + log.Fatal(err) } b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() + go b.WsReadData(b.AuthenticatedWebsocketConn) go b.WsDataHandler() - err = b.WsSendAuth() +} + +// TestWsAuth dials websocket, sends login request. +func TestWsAuth(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + runAuth(t) +} + +func runAuth(t *testing.T) { + setupWs() + err := b.WsSendAuth() if err != nil { t.Error(err) } timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout) select { case resp := <-b.Websocket.DataHandler: - if resp.(map[string]interface{})["event"] != "auth" && resp.(map[string]interface{})["status"] != "OK" { - t.Error("expected successful login") + if logResponse, ok := resp.(map[string]interface{}); ok { + if logResponse["event"] != "auth" && logResponse["status"] != "OK" { + t.Error("expected successful login") + } + } else { + t.Error("Unexpected response") } case <-timer.C: t.Error("Have not received a response") } timer.Stop() + wsAuthExecuted = true +} + +// TestWsPlaceOrder dials websocket, sends order request. +func TestWsPlaceOrder(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + _, err := b.WsNewOrder(&WsNewOrderRequest{ + CustomID: 1337, + Type: order.Buy.String(), + Symbol: "tBTCUSD", + Amount: 10, + Price: -10, + }) + if err != nil { + t.Error(err) + } +} + +// TestWsCancelOrder dials websocket, sends cancel request. +func TestWsCancelOrder(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsCancelOrder(1234) + if err != nil { + t.Error(err) + } +} + +// TestWsCancelOrder dials websocket, sends modify request. +func TestWsUpdateOrder(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsModifyOrder(&WsUpdateOrderRequest{ + OrderID: 1234, + Price: -111, + Amount: 111, + }) + if err != nil { + t.Error(err) + } +} + +// TestWsCancelAllOrders dials websocket, sends cancel all request. +func TestWsCancelAllOrders(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsCancelAllOrders() + if err != nil { + t.Error(err) + } +} + +// TestWsCancelAllOrders dials websocket, sends cancel all request. +func TestWsCancelMultiOrders(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsCancelMultiOrders([]int64{1, 2, 3, 4}) + if err != nil { + t.Error(err) + } +} + +// TestWsNewOffer dials websocket, sends new offer request. +func TestWsNewOffer(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsNewOffer(&WsNewOfferRequest{ + Type: order.Limit.String(), + Symbol: "fBTC", + Amount: -10, + Rate: 10, + Period: 30, + }) + if err != nil { + t.Error(err) + } + time.Sleep(time.Second) +} + +// TestWsCancelOffer dials websocket, sends cancel offer request. +func TestWsCancelOffer(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsCancelOffer(1234) + if err != nil { + t.Error(err) + } + time.Sleep(time.Second) } diff --git a/exchanges/bitfinex/bitfinex_types.go b/exchanges/bitfinex/bitfinex_types.go index a54e7584..23864bb4 100644 --- a/exchanges/bitfinex/bitfinex_types.go +++ b/exchanges/bitfinex/bitfinex_types.go @@ -385,7 +385,7 @@ type WebsocketChanInfo struct { // WebsocketBook holds booking information type WebsocketBook struct { Price float64 - Count int + ID int Amount float64 } @@ -397,6 +397,16 @@ type WebsocketTrade struct { Amount float64 } +// WebsocketCandle candle data +type WebsocketCandle struct { + Timestamp int64 + Open float64 + Close float64 + High float64 + Low float64 + Volume float64 +} + // WebsocketTicker holds ticker information type WebsocketTicker struct { Bid float64 @@ -416,7 +426,11 @@ type WebsocketPosition struct { Amount float64 Price float64 MarginFunding float64 - MarginFundingType int + MarginFundingType int64 + ProfitLoss float64 + ProfitLossPercent float64 + LiquidationPrice float64 + Leverage float64 } // WebsocketWallet holds wallet information @@ -459,6 +473,9 @@ type WebsocketTradeData struct { OrderID int64 AmountExecuted float64 PriceExecuted float64 + OrderType string + OrderPrice float64 + Maker bool Fee float64 FeeCurrency string } @@ -468,21 +485,215 @@ type ErrorCapture struct { Message string `json:"message"` } -// TimeInterval represents interval enum. -type TimeInterval string +// WebsocketHandshake defines the communication between the websocket API for +// initial connection +type WebsocketHandshake struct { + Event string `json:"event"` + Code int64 `json:"code"` + Version float64 `json:"version"` +} -// TimeInvterval vars -var ( - TimeIntervalMinute = TimeInterval("1m") - TimeIntervalFiveMinutes = TimeInterval("5m") - TimeIntervalFifteenMinutes = TimeInterval("15m") - TimeIntervalThirtyMinutes = TimeInterval("30m") - TimeIntervalHour = TimeInterval("1h") - TimeIntervalThreeHours = TimeInterval("3h") - TimeIntervalSixHours = TimeInterval("6h") - TimeIntervalTwelveHours = TimeInterval("12h") - TimeIntervalDay = TimeInterval("1d") - TimeIntervalSevenDays = TimeInterval("7d") - TimeIntervalFourteenDays = TimeInterval("14d") - TimeIntervalMonth = TimeInterval("1M") +const ( + authenticatedBitfinexWebsocketEndpoint = "wss://api.bitfinex.com/ws/2" + publicBitfinexWebsocketEndpoint = "wss://api-pub.bitfinex.com/ws/2" + pong = "pong" + wsHeartbeat = "hb" + wsPositionSnapshot = "ps" + wsPositionNew = "pn" + wsPositionUpdate = "pu" + wsPositionClose = "pc" + wsWalletSnapshot = "ws" + wsWalletUpdate = "wu" + wsTradeExecutionUpdate = "tu" + wsTradeExecuted = "te" + wsFundingCreditSnapshot = "fcs" + wsFundingCreditNew = "fcn" + wsFundingCreditUpdate = "fcu" + wsFundingCreditCancel = "fcc" + wsFundingLoanSnapshot = "fls" + wsFundingLoanNew = "fln" + wsFundingLoanUpdate = "flu" + wsFundingLoanCancel = "flc" + wsFundingTradeExecuted = "fte" + wsFundingTradeUpdate = "ftu" + wsFundingInfoUpdate = "fiu" + wsBalanceUpdate = "bu" + wsMarginInfoUpdate = "miu" + wsNotification = "n" + wsOrderNew = "on" + wsOrderUpdate = "ou" + wsOrderCancel = "oc" + wsFundingOrderSnapshot = "fos" + wsFundingOrderNew = "fon" + wsFundingOrderUpdate = "fou" + wsFundingOrderCancel = "foc" + wsCancelMultipleOrders = "oc_multi" + wsBook = "book" + wsCandles = "candles" + wsTicker = "ticker" + wsTrades = "trades" + wsError = "error" ) + +// WsAuthRequest container for WS auth request +type WsAuthRequest struct { + Event string `json:"event"` + APIKey string `json:"apiKey"` + AuthPayload string `json:"authPayload"` + AuthSig string `json:"authSig"` + AuthNonce string `json:"authNonce"` + DeadManSwitch int64 `json:"dms,omitempty"` +} + +// WsFundingOffer funding offer received via websocket +type WsFundingOffer struct { + ID int64 + Symbol string + Created int64 + Updated int64 + Amount float64 + AmountOrig float64 + Type string + Flags interface{} + Status string + Rate float64 + Period int64 + Notify bool + Hidden bool + Insure bool + Renew bool + RateReal float64 +} + +// WsCredit credit details received via websocket +type WsCredit struct { + ID int64 + Symbol string + Side string + Created int64 + Updated int64 + Amount float64 + Flags interface{} + Status string + Rate float64 + Period int64 + Opened int64 + LastPayout int64 + Notify bool + Hidden bool + Insure bool + Renew bool + RateReal float64 + NoClose bool + PositionPair string +} + +// WsWallet wallet update details received via websocket +type WsWallet struct { + Type string + Currency string + Balance float64 + UnsettledInterest float64 + BalanceAvailable float64 +} + +// WsBalanceInfo the total and net assets in your account received via websocket +type WsBalanceInfo struct { + TotalAssetsUnderManagement float64 + NetAssetsUnderManagement float64 +} + +// WsFundingInfo account funding info received via websocket +type WsFundingInfo struct { + Symbol string + YieldLoan float64 + YieldLend float64 + DurationLoan float64 + DurationLend float64 +} + +// WsMarginInfoBase account margin info received via websocket +type WsMarginInfoBase struct { + UserProfitLoss float64 + UserSwaps float64 + MarginBalance float64 + MarginNet float64 +} + +// WsMarginInfoBase recent funding trades received via websocket +type WsFundingTrade struct { + ID int64 + Symbol string + MTSCreated int64 + OfferID int64 + Amount float64 + Rate float64 + Period int64 + Maker bool +} + +// WsNewOrderRequest new order request... +type WsNewOrderRequest struct { + GroupID int64 `json:"gid,omitempty"` + CustomID int64 `json:"cid,omitempty"` + Type string `json:"type"` + Symbol string `json:"symbol"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + Leverage int64 `json:"lev,omitempty"` + TrailingPrice float64 `json:"price_trailing,string,omitempty"` + AuxiliaryLimitPrice float64 `json:"price_aux_limit,string,omitempty"` + StopPrice float64 `json:"price_oco_stop,string,omitempty"` + Flags int64 `json:"flags,omitempty"` + TimeInForce string `json:"tif,omitempty"` +} + +// WsUpdateOrderRequest update order request... +type WsUpdateOrderRequest struct { + OrderID int64 `json:"id,omitempty"` + CustomID int64 `json:"cid,omitempty"` + CustomIDDate string `json:"cid_date,omitempty"` + GroupID int64 `json:"gid,omitempty"` + Price float64 `json:"price,string,omitempty"` + Amount float64 `json:"amount,string,omitempty"` + Leverage int64 `json:"lev,omitempty"` + Delta float64 `json:"delta,string,omitempty"` + AuxiliaryLimitPrice float64 `json:"price_aux_limit,string,omitempty"` + TrailingPrice float64 `json:"price_trailing,string,omitempty"` + Flags int64 `json:"flags,omitempty"` + TimeInForce string `json:"tif,omitempty"` +} + +// WsCancelOrderRequest cancel order request... +type WsCancelOrderRequest struct { + OrderID int64 `json:"id,omitempty"` + CustomID int64 `json:"cid,omitempty"` + CustomIDDate string `json:"cid_date,omitempty"` +} + +// WsCancelGroupOrdersRequest cancel orders request... +type WsCancelGroupOrdersRequest struct { + OrderID []int64 `json:"id,omitempty"` + CustomID [][]int64 `json:"cid,omitempty"` + GroupOrderID []int64 `json:"gid,omitempty"` +} + +// WsNewOfferRequest new offer request +type WsNewOfferRequest struct { + Type string `json:"type,omitempty"` + Symbol string `json:"symbol,omitempty"` + Amount float64 `json:"amount,string,omitempty"` + Rate float64 `json:"rate,string,omitempty"` + Period float64 `json:"period,omitempty"` + Flags int64 `json:"flags,omitempty"` +} + +// WsCancelOfferRequest cancel offer request +type WsCancelOfferRequest struct { + OrderID int64 `json:"id"` +} + +// WsCancelAllOrdersRequest cancel all orders request +type WsCancelAllOrdersRequest struct { + All int64 `json:"all"` +} diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 3fb9a7f9..2b54be05 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -7,6 +7,7 @@ import ( "net/http" "reflect" "strconv" + "strings" "time" "github.com/gorilla/websocket" @@ -21,98 +22,7 @@ import ( log "github.com/thrasher-corp/gocryptotrader/logger" ) -const ( - bitfinexWebsocket = "wss://api.bitfinex.com/ws" - bitfinexWebsocketVersion = "1.1" - bitfinexWebsocketPositionSnapshot = "ps" - bitfinexWebsocketPositionNew = "pn" - bitfinexWebsocketPositionUpdate = "pu" - bitfinexWebsocketPositionClose = "pc" - bitfinexWebsocketWalletSnapshot = "ws" - bitfinexWebsocketWalletUpdate = "wu" - bitfinexWebsocketOrderSnapshot = "os" - bitfinexWebsocketOrderNew = "on" - bitfinexWebsocketOrderUpdate = "ou" - bitfinexWebsocketOrderCancel = "oc" - bitfinexWebsocketTradeExecuted = "te" - bitfinexWebsocketTradeExecutionUpdate = "tu" - bitfinexWebsocketTradeSnapshots = "ts" - bitfinexWebsocketHeartbeat = "hb" - bitfinexWebsocketAlertRestarting = "20051" - bitfinexWebsocketAlertRefreshing = "20060" - bitfinexWebsocketAlertResume = "20061" - bitfinexWebsocketUnknownEvent = "10000" - bitfinexWebsocketUnknownPair = "10001" - bitfinexWebsocketSubscriptionFailed = "10300" - bitfinexWebsocketAlreadySubscribed = "10301" - bitfinexWebsocketUnknownChannel = "10302" -) - -// WebsocketHandshake defines the communication between the websocket API for -// initial connection -type WebsocketHandshake struct { - Event string `json:"event"` - Code int64 `json:"code"` - Version float64 `json:"version"` -} - -var pongReceive chan struct{} - -// WsPingHandler sends a ping request to the websocket server -func (b *Bitfinex) WsPingHandler() error { - req := make(map[string]string) - req["event"] = "ping" - return b.WebsocketConn.SendMessage(req) -} - -// WsSendAuth sends a autheticated event payload -func (b *Bitfinex) WsSendAuth() error { - if !b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", b.Name) - } - req := make(map[string]interface{}) - payload := "AUTH" + strconv.FormatInt(time.Now().UnixNano(), 10)[:13] - req["event"] = "auth" - req["apiKey"] = b.API.Credentials.Key - - req["authSig"] = crypto.HexEncodeToString( - crypto.GetHMAC( - crypto.HashSHA512_384, - []byte(payload), - []byte(b.API.Credentials.Secret))) - - req["authPayload"] = payload - - err := b.WebsocketConn.SendMessage(req) - if err != nil { - b.Websocket.SetCanUseAuthenticatedEndpoints(false) - return err - } - return nil -} - -// WsSendUnauth sends an unauthenticated payload -func (b *Bitfinex) WsSendUnauth() error { - req := make(map[string]string) - req["event"] = "unauth" - - return b.WebsocketConn.SendMessage(req) -} - -// WsAddSubscriptionChannel adds a new subscription channel to the -// WebsocketSubdChannels map in bitfinex.go (Bitfinex struct) -func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) { - chanInfo := WebsocketChanInfo{Pair: pair, Channel: channel} - b.WebsocketSubdChannels[chanID] = chanInfo - - if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", - b.Name, - channel, - pair, - chanID) - } -} +var comms = make(chan wshandler.WebsocketResponse) // WsConnect starts a new websocket connection func (b *Bitfinex) WsConnect() error { @@ -121,70 +31,64 @@ func (b *Bitfinex) WsConnect() error { } var dialer websocket.Dialer - b.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, - URL: b.Websocket.GetWebsocketURL(), - ProxyURL: b.Websocket.GetProxyAddress(), - Verbose: b.Verbose, - } err := b.WebsocketConn.Dial(&dialer, http.Header{}) if err != nil { return fmt.Errorf("%v unable to connect to Websocket. Error: %s", b.Name, err) } + go b.WsReadData(b.WebsocketConn) - resp, err := b.WebsocketConn.ReadMessage() - if err != nil { - b.Websocket.ReadMessageErrors <- err - return fmt.Errorf("%v unable to read from Websocket. Error: %s", b.Name, err) - } - b.Websocket.TrafficAlert <- struct{}{} - var hs WebsocketHandshake - err = json.Unmarshal(resp.Raw, &hs) - if err != nil { - return err - } - - err = b.WsSendAuth() - if err != nil { - log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err) - } - - b.GenerateDefaultSubscriptions() - if hs.Event == "info" { - if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name) + if b.Websocket.CanUseAuthenticatedEndpoints() { + err = b.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) + if err != nil { + log.Errorf(log.ExchangeSys, "%v unable to connect to authenticated Websocket. Error: %s", b.Name, err) + b.Websocket.SetCanUseAuthenticatedEndpoints(false) + } + go b.WsReadData(b.AuthenticatedWebsocketConn) + err = b.WsSendAuth() + if err != nil { + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err) + b.Websocket.SetCanUseAuthenticatedEndpoints(false) } } - pongReceive = make(chan struct{}, 1) - + b.GenerateDefaultSubscriptions() go b.WsDataHandler() - return nil } +// WsReadData funnels both auth and public ws data into one manageable place +func (b *Bitfinex) WsReadData(ws *wshandler.WebsocketConnection) { + b.Websocket.Wg.Add(1) + defer b.Websocket.Wg.Done() + for { + select { + case <-b.Websocket.ShutdownC: + return + default: + resp, err := ws.ReadMessage() + if err != nil { + b.Websocket.DataHandler <- err + return + } + b.Websocket.TrafficAlert <- struct{}{} + comms <- resp + } + } +} + // WsDataHandler handles data from WsReadData func (b *Bitfinex) WsDataHandler() { b.Websocket.Wg.Add(1) - defer b.Websocket.Wg.Done() for { select { case <-b.Websocket.ShutdownC: return - - default: - stream, err := b.WebsocketConn.ReadMessage() - if err != nil { - b.Websocket.ReadMessageErrors <- err - return - } - b.Websocket.TrafficAlert <- struct{}{} - + case stream := <-comms: if stream.Type == websocket.TextMessage { var result interface{} - err = json.Unmarshal(stream.Raw, &result) + err := json.Unmarshal(stream.Raw, &result) if err != nil { b.Websocket.DataHandler <- err return @@ -195,10 +99,17 @@ func (b *Bitfinex) WsDataHandler() { event := eventData["event"] switch event { case "subscribed": - b.WsAddSubscriptionChannel(int(eventData["chanId"].(float64)), - eventData["channel"].(string), - eventData["pair"].(string)) - + if symbol, ok := eventData["pair"].(string); ok { + b.WsAddSubscriptionChannel(int(eventData["chanId"].(float64)), + eventData["channel"].(string), + symbol, + ) + } else if key, ok := eventData["key"].(string); ok { + b.WsAddSubscriptionChannel(int(eventData["chanId"].(float64)), + eventData["channel"].(string), + key, + ) + } case "auth": status := eventData["status"].(string) if status == "OK" { @@ -209,201 +120,104 @@ func (b *Bitfinex) WsDataHandler() { eventData["code"].(string)) } } - case "[]interface {}": chanData := result.([]interface{}) chanID := int(chanData[0].(float64)) - chanInfo, ok := b.WebsocketSubdChannels[chanID] - if !ok { + if !ok && chanID != 0 { b.Websocket.DataHandler <- fmt.Errorf("bitfinex.go error - Unable to locate chanID: %d", chanID) continue } - if len(chanData) == 2 { - if reflect.TypeOf(chanData[1]).String() == "string" { - if chanData[1].(string) == bitfinexWebsocketHeartbeat { - continue - } else if chanData[1].(string) == "pong" { - pongReceive <- struct{}{} - continue - } - } - } switch chanInfo.Channel { - case "book": + case wsBook: var newOrderbook []WebsocketBook curr := currency.NewPairFromString(chanInfo.Pair) - switch len(chanData) { - case 2: - data := chanData[1].([]interface{}) - for i := range data { - y := data[i].([]interface{}) + if obSnapBundle, ok := chanData[1].([]interface{}); ok { + switch snapshot := obSnapBundle[0].(type) { + case []interface{}: + for i := range snapshot { + obSnap := snapshot[i].([]interface{}) + newOrderbook = append(newOrderbook, WebsocketBook{ + ID: int(obSnap[0].(float64)), + Price: obSnap[1].(float64), + Amount: obSnap[2].(float64)}) + } + err := b.WsInsertSnapshot(curr, + asset.Spot, + newOrderbook) + if err != nil { + b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s", + err) + } + case float64: newOrderbook = append(newOrderbook, WebsocketBook{ - Price: y[0].(float64), - Count: int(y[1].(float64)), - Amount: y[2].(float64)}) - } - - err := b.WsInsertSnapshot(curr, - asset.Spot, - newOrderbook) - - if err != nil { - b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s", - err) - } - case 4: - newOrderbook = append(newOrderbook, WebsocketBook{ - Price: chanData[1].(float64), - Count: int(chanData[2].(float64)), - Amount: chanData[3].(float64)}) - err := b.WsUpdateOrderbook(curr, - asset.Spot, - newOrderbook) - - if err != nil { - b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go updating orderbook error: %s", - err) + ID: int(snapshot), + Price: obSnapBundle[1].(float64), + Amount: obSnapBundle[2].(float64)}) + err := b.WsUpdateOrderbook(curr, + asset.Spot, + newOrderbook) + if err != nil { + b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s", + err) + } } } - case "ticker": + continue + case wsCandles: + curr := currency.NewPairFromString(chanInfo.Pair) + if candleBundle, ok := chanData[1].([]interface{}); ok { + if len(candleBundle) == 0 { + continue + } + switch candleBundle[0].(type) { + case []interface{}: + for i := range candleBundle { + candle := candleBundle[i].([]interface{}) + b.Websocket.DataHandler <- wshandler.KlineData{ + Timestamp: time.Unix(0, candle[0].(int64)), + Exchange: b.Name, + AssetType: asset.Spot, + Pair: curr, + OpenPrice: candle[1].(float64), + ClosePrice: candle[2].(float64), + HighPrice: candle[3].(float64), + LowPrice: candle[4].(float64), + Volume: candle[5].(float64), + } + } + case float64: + b.Websocket.DataHandler <- wshandler.KlineData{ + Timestamp: time.Unix(0, candleBundle[0].(int64)), + Exchange: b.Name, + AssetType: asset.Spot, + Pair: curr, + OpenPrice: candleBundle[1].(float64), + ClosePrice: candleBundle[2].(float64), + HighPrice: candleBundle[3].(float64), + LowPrice: candleBundle[4].(float64), + Volume: candleBundle[5].(float64), + } + } + } + continue + case wsTicker: + tickerData := chanData[1].([]interface{}) b.Websocket.DataHandler <- wshandler.TickerData{ Exchange: b.Name, - Volume: chanData[8].(float64), - High: chanData[9].(float64), - Low: chanData[10].(float64), - Bid: chanData[1].(float64), - Ask: chanData[3].(float64), - Last: chanData[7].(float64), + Bid: tickerData[0].(float64), + Ask: tickerData[2].(float64), + Last: tickerData[6].(float64), + Volume: tickerData[7].(float64), + High: tickerData[8].(float64), + Low: tickerData[9].(float64), AssetType: asset.Spot, Pair: currency.NewPairFromString(chanInfo.Pair), } - - case "account": - switch chanData[1].(string) { - case bitfinexWebsocketPositionSnapshot: - var positionSnapshot []WebsocketPosition - data := chanData[2].([]interface{}) - for i := range data { - y := data[i].([]interface{}) - positionSnapshot = append(positionSnapshot, - WebsocketPosition{ - Pair: y[0].(string), - Status: y[1].(string), - Amount: y[2].(float64), - Price: y[3].(float64), - MarginFunding: y[4].(float64), - MarginFundingType: int(y[5].(float64))}) - } - - if len(positionSnapshot) == 0 { - continue - } - - b.Websocket.DataHandler <- positionSnapshot - - case bitfinexWebsocketPositionNew, bitfinexWebsocketPositionUpdate, bitfinexWebsocketPositionClose: - data := chanData[2].([]interface{}) - position := WebsocketPosition{ - Pair: data[0].(string), - Status: data[1].(string), - Amount: data[2].(float64), - Price: data[3].(float64), - MarginFunding: data[4].(float64), - MarginFundingType: int(data[5].(float64))} - - b.Websocket.DataHandler <- position - - case bitfinexWebsocketWalletSnapshot: - data := chanData[2].([]interface{}) - var walletSnapshot []WebsocketWallet - for i := range data { - y := data[i].([]interface{}) - walletSnapshot = append(walletSnapshot, - WebsocketWallet{ - Name: y[0].(string), - Currency: y[1].(string), - Balance: y[2].(float64), - UnsettledInterest: y[3].(float64)}) - } - - b.Websocket.DataHandler <- walletSnapshot - - case bitfinexWebsocketWalletUpdate: - data := chanData[2].([]interface{}) - wallet := WebsocketWallet{ - Name: data[0].(string), - Currency: data[1].(string), - Balance: data[2].(float64), - UnsettledInterest: data[3].(float64)} - - b.Websocket.DataHandler <- wallet - - case bitfinexWebsocketOrderSnapshot: - var orderSnapshot []WebsocketOrder - data := chanData[2].([]interface{}) - for i := range data { - y := data[i].([]interface{}) - orderSnapshot = append(orderSnapshot, - WebsocketOrder{ - OrderID: int64(y[0].(float64)), - Pair: y[1].(string), - Amount: y[2].(float64), - OrigAmount: y[3].(float64), - OrderType: y[4].(string), - Status: y[5].(string), - Price: y[6].(float64), - PriceAvg: y[7].(float64), - Timestamp: y[8].(string)}) - } - - b.Websocket.DataHandler <- orderSnapshot - - case bitfinexWebsocketOrderNew, bitfinexWebsocketOrderUpdate, bitfinexWebsocketOrderCancel: - data := chanData[2].([]interface{}) - order := WebsocketOrder{ - OrderID: int64(data[0].(float64)), - Pair: data[1].(string), - Amount: data[2].(float64), - OrigAmount: data[3].(float64), - OrderType: data[4].(string), - Status: data[5].(string), - Price: data[6].(float64), - PriceAvg: data[7].(float64), - Timestamp: data[8].(string), - Notify: int(data[9].(float64))} - - b.Websocket.DataHandler <- order - - case bitfinexWebsocketTradeExecuted: - data := chanData[2].([]interface{}) - trade := WebsocketTradeExecuted{ - TradeID: int64(data[0].(float64)), - Pair: data[1].(string), - Timestamp: int64(data[2].(float64)), - OrderID: int64(data[3].(float64)), - AmountExecuted: data[4].(float64), - PriceExecuted: data[5].(float64)} - - b.Websocket.DataHandler <- trade - case bitfinexWebsocketTradeSnapshots, bitfinexWebsocketTradeExecutionUpdate: - data := chanData[2].([]interface{}) - trade := WebsocketTradeData{ - TradeID: int64(data[0].(float64)), - Pair: data[1].(string), - Timestamp: int64(data[2].(float64)), - OrderID: int64(data[3].(float64)), - AmountExecuted: data[4].(float64), - PriceExecuted: data[5].(float64), - Fee: data[6].(float64), - FeeCurrency: data[7].(string)} - - b.Websocket.DataHandler <- trade - } - - case "trades": + continue + case wsTrades: var trades []WebsocketTrade switch len(chanData) { case 2: @@ -413,42 +227,370 @@ func (b *Bitfinex) WsDataHandler() { if _, ok := y[0].(string); ok { continue } - - id, _ := y[0].(float64) - trades = append(trades, WebsocketTrade{ - ID: int64(id), + ID: int64(y[0].(float64)), Timestamp: int64(y[1].(float64)), - Price: y[2].(float64), - Amount: y[3].(float64)}) + Price: y[3].(float64), + Amount: y[2].(float64)}) } - - case 7: - trade := WebsocketTrade{ - ID: int64(chanData[3].(float64)), - Timestamp: int64(chanData[4].(float64)), - Price: chanData[5].(float64), - Amount: chanData[6].(float64)} - trades = append(trades, trade) + case 3: + if chanData[1].(string) == wsTradeExecuted { + // the te update contains less data then the "tu" + continue + } + data := chanData[2].([]interface{}) + trades = append(trades, WebsocketTrade{ + ID: int64(data[0].(float64)), + Timestamp: int64(data[1].(float64)), + Price: data[3].(float64), + Amount: data[2].(float64)}) } - if len(trades) > 0 { - side := order.Buy - newAmount := trades[0].Amount - if newAmount < 0 { - side = order.Sell - newAmount *= -1 + for i := range trades { + side := order.Buy.String() + newAmount := trades[i].Amount + if newAmount < 0 { + side = order.Sell.String() + newAmount *= -1 + } + b.Websocket.DataHandler <- wshandler.TradeData{ + CurrencyPair: currency.NewPairFromString(chanInfo.Pair), + Timestamp: time.Unix(trades[i].Timestamp, 0), + Price: trades[i].Price, + Amount: newAmount, + Exchange: b.Name, + AssetType: asset.Spot, + Side: side, + } } - - b.Websocket.DataHandler <- wshandler.TradeData{ - CurrencyPair: currency.NewPairFromString(chanInfo.Pair), - Timestamp: time.Unix(trades[0].Timestamp, 0), - Price: trades[0].Price, - Amount: newAmount, - Exchange: b.Name, - AssetType: asset.Spot, - Side: side.String(), + continue + } + } + if authResp, ok := chanData[1].(string); ok { + switch authResp { + case wsHeartbeat, pong: + continue + case wsNotification: + notification := chanData[2].([]interface{}) + if data, ok := notification[4].([]interface{}); ok { + channelName := notification[1].(string) + switch { + case strings.Contains(channelName, wsOrderUpdate), + strings.Contains(channelName, wsOrderCancel), + strings.Contains(channelName, wsFundingOrderCancel): + if data[0] != nil && data[0].(float64) > 0 { + b.AuthenticatedWebsocketConn.AddResponseWithID(int64(data[0].(float64)), stream.Raw) + continue + } + case strings.Contains(channelName, wsOrderNew): + if data[2] != nil && data[2].(float64) > 0 { + b.AuthenticatedWebsocketConn.AddResponseWithID(int64(data[2].(float64)), stream.Raw) + continue + } + } + b.Websocket.DataHandler <- fmt.Errorf("%s - Unexpected data returned %s", b.Name, stream.Raw) + continue + } + if notification[5] != nil && strings.EqualFold(notification[5].(string), wsError) { + b.Websocket.DataHandler <- fmt.Errorf("%s - Error %s", b.Name, notification[6].(string)) + } + case wsPositionSnapshot: + var snapshot []WebsocketPosition + if snapBundle, ok := chanData[2].([]interface{}); ok && len(snapBundle) > 0 { + if _, ok := snapBundle[0].([]interface{}); ok { + for i := range snapBundle { + positionData := snapBundle[i].([]interface{}) + position := WebsocketPosition{ + Pair: positionData[0].(string), + Status: positionData[1].(string), + Amount: positionData[2].(float64), + Price: positionData[3].(float64), + MarginFunding: positionData[4].(float64), + MarginFundingType: int64(positionData[5].(float64)), + ProfitLoss: positionData[6].(float64), + ProfitLossPercent: positionData[7].(float64), + LiquidationPrice: positionData[8].(float64), + Leverage: positionData[9].(float64), + } + snapshot = append(snapshot, position) + } + b.Websocket.DataHandler <- snapshot + } + } + case wsPositionNew, wsPositionUpdate, wsPositionClose: + if positionData, ok := chanData[2].([]interface{}); ok && len(positionData) > 0 { + position := WebsocketPosition{ + Pair: positionData[0].(string), + Status: positionData[1].(string), + Amount: positionData[2].(float64), + Price: positionData[3].(float64), + MarginFunding: positionData[4].(float64), + MarginFundingType: int64(positionData[5].(float64)), + ProfitLoss: positionData[6].(float64), + ProfitLossPercent: positionData[7].(float64), + LiquidationPrice: positionData[8].(float64), + Leverage: positionData[9].(float64), + } + b.Websocket.DataHandler <- position + } + case wsTradeExecutionUpdate: + if tradeData, ok := chanData[2].([]interface{}); ok && len(tradeData) > 4 { + b.Websocket.DataHandler <- WebsocketTradeData{ + TradeID: int64(tradeData[0].(float64)), + Pair: tradeData[1].(string), + Timestamp: int64(tradeData[2].(float64)), + OrderID: int64(tradeData[3].(float64)), + AmountExecuted: tradeData[4].(float64), + PriceExecuted: tradeData[5].(float64), + OrderType: tradeData[6].(string), + OrderPrice: tradeData[7].(float64), + Maker: tradeData[8].(float64) == 1, + Fee: tradeData[9].(float64), + FeeCurrency: tradeData[10].(string), + } + } + case wsFundingOrderSnapshot: + var snapshot []WsFundingOffer + if snapBundle, ok := chanData[2].([]interface{}); ok && len(snapBundle) > 0 { + if _, ok := snapBundle[0].([]interface{}); ok { + for i := range snapBundle { + data := snapBundle[i].([]interface{}) + offer := WsFundingOffer{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Created: int64(data[2].(float64)), + Updated: int64(data[3].(float64)), + Amount: data[4].(float64), + AmountOrig: data[5].(float64), + Type: data[6].(string), + Flags: data[9].(float64), + Status: data[10].(string), + Rate: data[14].(float64), + Period: int64(data[15].(float64)), + Notify: data[16].(float64) == 1, + Hidden: data[17].(float64) == 1, + Insure: data[18].(float64) == 1, + Renew: data[19].(float64) == 1, + RateReal: data[20].(float64), + } + snapshot = append(snapshot, offer) + } + b.Websocket.DataHandler <- snapshot + } + } + case wsFundingOrderNew, wsFundingOrderUpdate, wsFundingOrderCancel: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + b.Websocket.DataHandler <- WsFundingOffer{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Created: int64(data[2].(float64)), + Updated: int64(data[3].(float64)), + Amount: data[4].(float64), + AmountOrig: data[5].(float64), + Type: data[6].(string), + Flags: data[9].(float64), + Status: data[10].(string), + Rate: data[14].(float64), + Period: int64(data[15].(float64)), + Notify: data[16].(float64) == 1, + Hidden: data[17].(float64) == 1, + Insure: data[18].(float64) == 1, + Renew: data[19].(float64) == 1, + RateReal: data[20].(float64), + } + } + case wsFundingCreditSnapshot: + var snapshot []WsCredit + if snapBundle, ok := chanData[2].([]interface{}); ok && len(snapBundle) > 0 { + if _, ok := snapBundle[0].([]interface{}); ok { + for i := range snapBundle { + data := snapBundle[i].([]interface{}) + credit := WsCredit{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Side: data[2].(string), + Created: int64(data[3].(float64)), + Updated: int64(data[4].(float64)), + Amount: data[5].(float64), + Flags: data[6].(string), + Status: data[7].(string), + Rate: data[11].(float64), + Period: int64(data[12].(float64)), + Opened: int64(data[13].(float64)), + LastPayout: int64(data[14].(float64)), + Notify: data[15].(float64) == 1, + Hidden: data[16].(float64) == 1, + Insure: data[17].(float64) == 1, + Renew: data[18].(float64) == 1, + RateReal: data[19].(float64), + NoClose: data[20].(float64) == 1, + PositionPair: data[21].(string), + } + snapshot = append(snapshot, credit) + } + b.Websocket.DataHandler <- snapshot + } + } + case wsFundingCreditNew, wsFundingCreditUpdate, wsFundingCreditCancel: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + b.Websocket.DataHandler <- WsCredit{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Side: data[2].(string), + Created: int64(data[3].(float64)), + Updated: int64(data[4].(float64)), + Amount: data[5].(float64), + Flags: data[6].(string), + Status: data[7].(string), + Rate: data[11].(float64), + Period: int64(data[12].(float64)), + Opened: int64(data[13].(float64)), + LastPayout: int64(data[14].(float64)), + Notify: data[15].(float64) == 1, + Hidden: data[16].(float64) == 1, + Insure: data[17].(float64) == 1, + Renew: data[18].(float64) == 1, + RateReal: data[19].(float64), + NoClose: data[20].(float64) == 1, + PositionPair: data[21].(string), + } + } + case wsFundingLoanSnapshot: + var snapshot []WsCredit + if snapBundle, ok := chanData[2].([]interface{}); ok && len(snapBundle) > 0 { + if _, ok := snapBundle[0].([]interface{}); ok { + for i := range snapBundle { + data := snapBundle[i].([]interface{}) + credit := WsCredit{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Side: data[2].(string), + Created: int64(data[3].(float64)), + Updated: int64(data[4].(float64)), + Amount: data[5].(float64), + Flags: data[6].(string), + Status: data[7].(string), + Rate: data[11].(float64), + Period: int64(data[12].(float64)), + Opened: int64(data[13].(float64)), + LastPayout: int64(data[14].(float64)), + Notify: data[15].(float64) == 1, + Hidden: data[16].(float64) == 1, + Insure: data[17].(float64) == 1, + Renew: data[18].(float64) == 1, + RateReal: data[19].(float64), + NoClose: data[20].(float64) == 1, + } + snapshot = append(snapshot, credit) + } + b.Websocket.DataHandler <- snapshot + } + } + case wsFundingLoanNew, wsFundingLoanUpdate, wsFundingLoanCancel: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + b.Websocket.DataHandler <- WsCredit{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Side: data[2].(string), + Created: int64(data[3].(float64)), + Updated: int64(data[4].(float64)), + Amount: data[5].(float64), + Flags: data[6].(string), + Status: data[7].(string), + Rate: data[11].(float64), + Period: int64(data[12].(float64)), + Opened: int64(data[13].(float64)), + LastPayout: int64(data[14].(float64)), + Notify: data[15].(float64) == 1, + Hidden: data[16].(float64) == 1, + Insure: data[17].(float64) == 1, + Renew: data[18].(float64) == 1, + RateReal: data[19].(float64), + NoClose: data[20].(float64) == 1, + } + } + case wsWalletSnapshot: + var snapshot []WsWallet + if snapBundle, ok := chanData[2].([]interface{}); ok && len(snapBundle) > 0 { + if _, ok := snapBundle[0].([]interface{}); ok { + for i := range snapBundle { + data := snapBundle[i].([]interface{}) + var balanceAvailable float64 + if _, ok := data[4].(float64); ok { + balanceAvailable = data[4].(float64) + } + wallet := WsWallet{ + Type: data[0].(string), + Currency: data[1].(string), + Balance: data[2].(float64), + UnsettledInterest: data[3].(float64), + BalanceAvailable: balanceAvailable, + } + snapshot = append(snapshot, wallet) + } + b.Websocket.DataHandler <- snapshot + } + } + case wsWalletUpdate: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + var balanceAvailable float64 + if _, ok := data[4].(float64); ok { + balanceAvailable = data[4].(float64) + } + b.Websocket.DataHandler <- WsWallet{ + Type: data[0].(string), + Currency: data[1].(string), + Balance: data[2].(float64), + UnsettledInterest: data[3].(float64), + BalanceAvailable: balanceAvailable, + } + } + case wsBalanceUpdate: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + b.Websocket.DataHandler <- WsBalanceInfo{ + TotalAssetsUnderManagement: data[0].(float64), + NetAssetsUnderManagement: data[1].(float64), + } + } + case wsMarginInfoUpdate: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + if data[0].(string) == "base" { + if infoBase, ok := chanData[2].([]interface{}); ok && len(infoBase) > 0 { + baseData := data[1].([]interface{}) + b.Websocket.DataHandler <- WsMarginInfoBase{ + UserProfitLoss: baseData[0].(float64), + UserSwaps: baseData[1].(float64), + MarginBalance: baseData[2].(float64), + MarginNet: baseData[3].(float64), + } + } + } + } + case wsFundingInfoUpdate: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + if data[0].(string) == "sym" { + symbolData := data[1].([]interface{}) + b.Websocket.DataHandler <- WsFundingInfo{ + YieldLoan: symbolData[0].(float64), + YieldLend: symbolData[1].(float64), + DurationLoan: symbolData[2].(float64), + DurationLend: symbolData[3].(float64), + } + } + } + case wsFundingTradeExecuted, wsFundingTradeUpdate: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + b.Websocket.DataHandler <- WsFundingTrade{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + MTSCreated: int64(data[2].(float64)), + OfferID: int64(data[3].(float64)), + Amount: data[4].(float64), + Rate: data[5].(float64), + Period: int64(data[6].(float64)), + Maker: data[7].(float64) == 1, + } } } } @@ -504,7 +646,7 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book for i := 0; i < len(book); i++ { switch { - case book[i].Count > 0: + case book[i].Price > 0: if book[i].Amount > 0 { // update bid orderbookUpdate.Bids = append(orderbookUpdate.Bids, orderbook.Item{Amount: book[i].Amount, Price: book[i].Price}) @@ -512,7 +654,7 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book // update ask orderbookUpdate.Asks = append(orderbookUpdate.Asks, orderbook.Item{Amount: book[i].Amount * -1, Price: book[i].Price}) } - case book[i].Count == 0: + case book[i].Price == 0: if book[i].Amount == 1 { // delete bid orderbookUpdate.Bids = append(orderbookUpdate.Bids, orderbook.Item{Amount: 0, Price: book[i].Price}) @@ -522,7 +664,6 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book } } } - orderbookUpdate.UpdateTime = time.Now() err := b.Websocket.Orderbook.Update(&orderbookUpdate) if err != nil { return err @@ -537,17 +678,29 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (b *Bitfinex) GenerateDefaultSubscriptions() { - var channels = []string{"book", "trades", "ticker"} + var channels = []string{ + wsBook, + wsTrades, + wsTicker, + wsCandles, + } var subscriptions []wshandler.WebsocketChannelSubscription for i := range channels { enabledPairs := b.GetEnabledPairs(asset.Spot) for j := range enabledPairs { + if strings.HasPrefix(enabledPairs[j].Base.String(), "f") { + log.Warnf(log.WebsocketMgr, + "%v - Websocket does not support funding currency %v, skipping", + b.Name, enabledPairs[j]) + continue + } b.appendOptionalDelimiter(&enabledPairs[j]) params := make(map[string]interface{}) - if channels[i] == "book" { - params["prec"] = "P0" + if channels[i] == wsBook { + params["prec"] = "R0" params["len"] = "100" } + subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ Channel: channels[i], Currency: enabledPairs[j], @@ -564,8 +717,14 @@ func (b *Bitfinex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscr req["event"] = "subscribe" req["channel"] = channelToSubscribe.Channel if channelToSubscribe.Currency.String() != "" { - req["pair"] = b.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String() + if channelToSubscribe.Channel == wsCandles { + // TODO: Add ability to select timescale + req["key"] = fmt.Sprintf("trade:1D:%v", + b.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String()) + } else { + req["symbol"] = b.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String() + } } if len(channelToSubscribe.Params) > 0 { for k, v := range channelToSubscribe.Params { @@ -588,3 +747,196 @@ func (b *Bitfinex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubs } return b.WebsocketConn.SendMessage(req) } + +// WsSendAuth sends a autheticated event payload +func (b *Bitfinex) WsSendAuth() error { + if !b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", b.Name) + } + nonce := strconv.FormatInt(time.Now().Unix(), 10) + payload := "AUTH" + nonce + request := WsAuthRequest{ + Event: "auth", + APIKey: b.API.Credentials.Key, + AuthPayload: payload, + AuthSig: crypto.HexEncodeToString( + crypto.GetHMAC( + crypto.HashSHA512_384, + []byte(payload), + []byte(b.API.Credentials.Secret))), + AuthNonce: nonce, + DeadManSwitch: 0, + } + err := b.AuthenticatedWebsocketConn.SendMessage(request) + if err != nil { + b.Websocket.SetCanUseAuthenticatedEndpoints(false) + return err + } + return nil +} + +// WsSendUnauth sends an unauthenticated payload +func (b *Bitfinex) WsSendUnauth() error { + req := make(map[string]string) + req["event"] = "unauth" + + return b.WebsocketConn.SendMessage(req) +} + +// WsAddSubscriptionChannel adds a new subscription channel to the +// WebsocketSubdChannels map in bitfinex.go (Bitfinex struct) +func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) { + chanInfo := WebsocketChanInfo{Pair: pair, Channel: channel} + b.WebsocketSubdChannels[chanID] = chanInfo + + if b.Verbose { + log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", + b.Name, + channel, + pair, + chanID) + } +} + +// WsNewOrder authenticated new order request +func (b *Bitfinex) WsNewOrder(data *WsNewOrderRequest) (string, error) { + data.CustomID = b.AuthenticatedWebsocketConn.GenerateMessageID(false) + request := makeRequestInterface(wsOrderNew, data) + resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(data.CustomID, request) + if err != nil { + return "", err + } + if resp == nil { + return "", errors.New(b.Name + " - Order message not returned") + } + var respData []interface{} + err = json.Unmarshal(resp, &respData) + if err != nil { + return "", err + } + responseDataDetail := respData[2].([]interface{}) + responseOrderDetail := responseDataDetail[4].([]interface{}) + var orderID string + if responseOrderDetail[0] != nil && responseOrderDetail[0].(float64) > 0 { + orderID = strconv.FormatFloat(responseOrderDetail[0].(float64), 'f', -1, 64) + } + errCode := responseDataDetail[6].(string) + errorMessage := responseDataDetail[7].(string) + + if strings.EqualFold(errCode, wsError) { + return orderID, errors.New(b.Name + " - " + errCode + ": " + errorMessage) + } + + return orderID, nil +} + +// WsModifyOrder authenticated modify order request +func (b *Bitfinex) WsModifyOrder(data *WsUpdateOrderRequest) error { + request := makeRequestInterface(wsOrderUpdate, data) + resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(data.OrderID, request) + if err != nil { + return err + } + if resp == nil { + return errors.New(b.Name + " - Order message not returned") + } + + var responseData []interface{} + err = json.Unmarshal(resp, &responseData) + if err != nil { + return err + } + responseOrderData := responseData[2].([]interface{}) + errCode := responseOrderData[6].(string) + errorMessage := responseOrderData[7].(string) + if strings.EqualFold(errCode, wsError) { + return errors.New(b.Name + " - " + errCode + ": " + errorMessage) + } + + return nil +} + +// WsCancelMultiOrders authenticated cancel multi order request +func (b *Bitfinex) WsCancelMultiOrders(orderIDs []int64) error { + cancel := WsCancelGroupOrdersRequest{ + OrderID: orderIDs, + } + request := makeRequestInterface(wsCancelMultipleOrders, cancel) + return b.AuthenticatedWebsocketConn.SendMessage(request) +} + +// WsCancelOrder authenticated cancel order request +func (b *Bitfinex) WsCancelOrder(orderID int64) error { + cancel := WsCancelOrderRequest{ + OrderID: orderID, + } + request := makeRequestInterface(wsOrderCancel, cancel) + resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(orderID, request) + if err != nil { + return err + } + if resp == nil { + return fmt.Errorf("%v - Order %v failed to cancel", b.Name, orderID) + } + var responseData []interface{} + err = json.Unmarshal(resp, &responseData) + if err != nil { + return err + } + responseOrderData := responseData[2].([]interface{}) + errCode := responseOrderData[6].(string) + errorMessage := responseOrderData[7].(string) + if strings.EqualFold(errCode, wsError) { + return errors.New(b.Name + " - " + errCode + ": " + errorMessage) + } + + return nil +} + +// WsCancelAllOrders authenticated cancel all orders request +func (b *Bitfinex) WsCancelAllOrders() error { + cancelAll := WsCancelAllOrdersRequest{All: 1} + request := makeRequestInterface(wsCancelMultipleOrders, cancelAll) + return b.AuthenticatedWebsocketConn.SendMessage(request) +} + +// WsNewOffer authenticated new offer request +func (b *Bitfinex) WsNewOffer(data *WsNewOfferRequest) error { + request := makeRequestInterface(wsFundingOrderNew, data) + return b.AuthenticatedWebsocketConn.SendMessage(request) +} + +// WsCancelOffer authenticated cancel offer request +func (b *Bitfinex) WsCancelOffer(orderID int64) error { + cancel := WsCancelOrderRequest{ + OrderID: orderID, + } + request := makeRequestInterface(wsFundingOrderCancel, cancel) + resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(orderID, request) + if err != nil { + return err + } + if resp == nil { + return fmt.Errorf("%v - Order %v failed to cancel", b.Name, orderID) + } + var responseData []interface{} + err = json.Unmarshal(resp, &responseData) + if err != nil { + return err + } + responseOrderData := responseData[2].([]interface{}) + errCode := responseOrderData[6].(string) + var errorMessage string + if responseOrderData[7] != nil { + errorMessage = responseOrderData[7].(string) + } + if strings.EqualFold(errCode, wsError) { + return errors.New(b.Name + " - " + errCode + ": " + errorMessage) + } + + return nil +} + +func makeRequestInterface(channelName string, data interface{}) []interface{} { + return []interface{}{0, channelName, nil, data} +} diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index f5337b75..95838ffc 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -87,7 +87,6 @@ func (b *Bitfinex) SetDefaults() { CancelOrder: true, SubmitOrder: true, SubmitOrders: true, - ModifyOrder: true, DepositHistory: true, WithdrawalHistory: true, TradeFetching: true, @@ -99,12 +98,21 @@ func (b *Bitfinex) SetDefaults() { CryptoWithdrawalFee: true, }, WebsocketCapabilities: protocol.Features{ + AccountBalance: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + ModifyOrder: true, TickerFetching: true, + KlineFetching: true, TradeFetching: true, OrderbookFetching: true, + AccountInfo: true, Subscribe: true, Unsubscribe: true, AuthenticatedEndpoints: true, + MessageCorrelation: true, + DeadMansSwitch: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.AutoWithdrawFiatWithAPIPermission, @@ -121,7 +129,7 @@ func (b *Bitfinex) SetDefaults() { b.API.Endpoints.URLDefault = bitfinexAPIURLBase b.API.Endpoints.URL = b.API.Endpoints.URLDefault - b.API.Endpoints.WebsocketURL = bitfinexWebsocket + b.API.Endpoints.WebsocketURL = publicBitfinexWebsocketEndpoint b.Websocket = wshandler.New() b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout @@ -146,7 +154,7 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error { Verbose: exch.Verbose, AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: bitfinexWebsocket, + DefaultURL: publicBitfinexWebsocketEndpoint, ExchangeName: exch.Name, RunningURL: exch.API.Endpoints.WebsocketURL, Connector: b.WsConnect, @@ -166,6 +174,14 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error { ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, } + b.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ + ExchangeName: b.Name, + URL: authenticatedBitfinexWebsocketEndpoint, + ProxyURL: b.Websocket.GetProxyAddress(), + Verbose: b.Verbose, + ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, + ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + } b.Websocket.Orderbook.Setup( exch.WebsocketOrderbookBufferLimit, @@ -318,6 +334,7 @@ func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (order func (b *Bitfinex) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo response.Exchange = b.Name + accountBalance, err := b.GetAccountBalance() if err != nil { return response, err @@ -358,39 +375,67 @@ func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([] } // SubmitOrder submits a new order -func (b *Bitfinex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { +func (b *Bitfinex) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - if err := s.Validate(); err != nil { + err := o.Validate() + if err != nil { return submitOrderResponse, err } + if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + submitOrderResponse.OrderID, err = b.WsNewOrder(&WsNewOrderRequest{ + CustomID: b.AuthenticatedWebsocketConn.GenerateMessageID(false), + Type: o.OrderType.String(), + Symbol: b.FormatExchangeCurrency(o.Pair, asset.Spot).String(), + Amount: o.Amount, + Price: o.Price, + }) + if err != nil { + return submitOrderResponse, err + } + } else { + var response Order + isBuying := o.OrderSide == order.Buy + b.appendOptionalDelimiter(&o.Pair) + response, err = b.NewOrder(o.Pair.String(), + o.Amount, + o.Price, + isBuying, + o.OrderType.String(), + false) + if err != nil { + return submitOrderResponse, err + } + if response.OrderID > 0 { + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) + } + if response.RemainingAmount == 0 { + submitOrderResponse.FullyMatched = true + } - var isBuying bool - if s.OrderSide == order.Buy { - isBuying = true - } - b.appendOptionalDelimiter(&s.Pair) - response, err := b.NewOrder(s.Pair.String(), - s.Amount, - s.Price, - isBuying, - s.OrderType.String(), - false) - - if response.OrderID > 0 { - submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) - } - - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } // ModifyOrder will allow of changing orderbook placement and limit to // market conversion func (b *Bitfinex) ModifyOrder(action *order.Modify) (string, error) { - return "", common.ErrFunctionNotSupported + orderIDInt, err := strconv.ParseInt(action.OrderID, 10, 64) + if err != nil { + return action.OrderID, err + } + if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + if action.Side == order.Sell && action.Amount > 0 { + action.Amount = -1 * action.Amount + } + err = b.WsModifyOrder(&WsUpdateOrderRequest{ + OrderID: orderIDInt, + Price: action.Price, + Amount: action.Amount, + }) + return action.OrderID, err + } + return "", common.ErrNotYetImplemented } // CancelOrder cancels an order by its corresponding ID number @@ -399,13 +444,22 @@ func (b *Bitfinex) CancelOrder(order *order.Cancel) error { if err != nil { return err } - _, err = b.CancelExistingOrder(orderIDInt) + if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + err = b.WsCancelOrder(orderIDInt) + } else { + _, err = b.CancelExistingOrder(orderIDInt) + } return err } // CancelAllOrders cancels all orders associated with a currency pair func (b *Bitfinex) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { - _, err := b.CancelAllExistingOrders() + var err error + if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + err = b.WsCancelAllOrders() + } else { + _, err = b.CancelAllExistingOrders() + } return order.CancelAllResponse{}, err } @@ -422,7 +476,8 @@ func (b *Bitfinex) GetDepositAddress(cryptocurrency currency.Code, accountID str return "", err } - resp, err := b.NewDeposit(method, accountID, 0) + var resp DepositResponse + resp, err = b.NewDeposit(method, accountID, 0) if err != nil { return "", err } @@ -547,7 +602,7 @@ func (b *Bitfinex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, orderDetail.Status = order.UnknownStatus } - // API docs discrepency. Example contains prefixed "exchange " + // API docs discrepancy. Example contains prefixed "exchange " // Return type suggests “market” / “limit” / “stop” / “trailing-stop” orderType := strings.Replace(resp[i].Type, "exchange ", "", 1) if orderType == "trailing-stop" { diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index 3364b944..31a87a5c 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -1,6 +1,8 @@ package bitflyer import ( + "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -9,7 +11,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" - log "github.com/thrasher-corp/gocryptotrader/logger" ) // Please supply your own keys here for due diligence testing @@ -21,19 +22,16 @@ const ( var b Bitflyer -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Bitflyer load config error", err) + log.Fatal("Bitflyer load config error", err) } bitflyerConfig, err := cfg.GetExchangeConfig("Bitflyer") if err != nil { - t.Error("bitflyer Setup() init error") + log.Fatal("bitflyer Setup() init error") } bitflyerConfig.API.AuthenticatedSupport = true @@ -42,8 +40,10 @@ func TestSetup(t *testing.T) { err = b.Setup(bitflyerConfig) if err != nil { - t.Fatal("Bitflyer setup error", err) + log.Fatal("Bitflyer setup error", err) } + + os.Exit(m.Run()) } func TestGetLatestBlockCA(t *testing.T) { @@ -85,7 +85,7 @@ func TestGetAddressInfoCA(t *testing.T) { t.Error("Bitflyer - GetAddressInfoCA() error:", err) } if v.UnconfirmedBalance == 0 || v.ConfirmedBalance == 0 { - log.Warn(log.ExchangeSys, "Donation wallet is empty :( - please consider donating") + t.Log("Donation wallet is empty :( - please consider donating") } } @@ -171,7 +171,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -183,11 +183,10 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() var feeBuilder = setFeeBuilder() - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { t.Error(err) @@ -256,20 +255,16 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() + t.Parallel() expectedResult := exchange.AutoWithdrawFiatText + " & " + exchange.WithdrawCryptoViaWebsiteOnlyText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -283,9 +278,7 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -303,9 +296,7 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -328,9 +319,7 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -351,9 +340,7 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -374,8 +361,11 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -385,10 +375,6 @@ func TestWithdraw(t *testing.T) { Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - _, err := b.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not Yet Implemented', received %v", err) @@ -396,6 +382,10 @@ func TestWithdraw(t *testing.T) { } func TestModifyOrder(t *testing.T) { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -403,9 +393,7 @@ func TestModifyOrder(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -419,9 +407,7 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index 8f82f248..b2331ba2 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -1,6 +1,8 @@ package bithumb import ( + "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -20,19 +22,16 @@ const ( var b Bithumb -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Bithumb load config error", err) + log.Fatal("Bithumb load config error", err) } bitConfig, err := cfg.GetExchangeConfig("Bithumb") if err != nil { - t.Error("Bithumb Setup() init error") + log.Fatal("Bithumb Setup() init error") } bitConfig.API.AuthenticatedSupport = true @@ -41,8 +40,10 @@ func TestSetup(t *testing.T) { err = b.Setup(bitConfig) if err != nil { - t.Fatal("Bithumb setup error", err) + log.Fatal("Bithumb setup error", err) } + + os.Exit(m.Run()) } func TestGetTradablePairs(t *testing.T) { @@ -87,7 +88,7 @@ func TestGetTransactionHistory(t *testing.T) { func TestGetAccountBalance(t *testing.T) { t.Parallel() - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { t.Skip() } @@ -98,11 +99,11 @@ func TestGetAccountBalance(t *testing.T) { } func TestGetWalletAddress(t *testing.T) { - if apiKey == "" || apiSecret == "" { + t.Parallel() + if !areTestAPIKeysSet() { t.Skip() } - t.Parallel() _, err := b.GetWalletAddress("") if err == nil { t.Error("Bithumb GetWalletAddress() Expected error") @@ -167,7 +168,7 @@ func TestWithdrawCrypto(t *testing.T) { func TestRequestKRWDepositDetails(t *testing.T) { t.Parallel() - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { t.Skip() } _, err := b.RequestKRWDepositDetails() @@ -213,7 +214,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -225,10 +226,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) var feeBuilder = setFeeBuilder() - // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.0025) || err != nil { t.Error(err) @@ -296,20 +294,16 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() + t.Parallel() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.AutoWithdrawFiatText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, OrderSide: order.Sell, @@ -324,9 +318,7 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -346,9 +338,7 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -373,15 +363,12 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -399,15 +386,12 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -431,7 +415,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestGetAccountInfo(t *testing.T) { t.Parallel() - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { _, err := b.GetAccountInfo() if err != nil { t.Error("Bithumb GetAccountInfo() error", err) @@ -445,6 +429,7 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + t.Parallel() curr := currency.NewPairFromString("BTCUSD") _, err := b.ModifyOrder(&order.Modify{ OrderID: "1337", @@ -458,8 +443,7 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -483,9 +467,7 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -519,9 +501,7 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -534,7 +514,8 @@ func TestWithdrawInternationalBank(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { - if apiKey != "" && apiSecret != "" { + t.Parallel() + if areTestAPIKeysSet() { _, err := b.GetDepositAddress(currency.BTC, "") if err != nil { t.Error("GetDepositAddress() error", err) diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 91f8f983..8d08da2b 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -220,9 +220,9 @@ func (b *Bithumb) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbo // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *Bithumb) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - currency := p.Base.String() + curr := p.Base.String() - orderbookNew, err := b.GetOrderBook(currency) + orderbookNew, err := b.GetOrderBook(curr) if err != nil { return orderBook, err } @@ -311,20 +311,25 @@ func (b *Bithumb) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { if s.OrderSide == order.Buy { var result MarketBuy result, err = b.MarketBuyOrder(s.Pair.Base.String(), s.Amount) + if err != nil { + return submitOrderResponse, err + } orderID = result.OrderID } else if s.OrderSide == order.Sell { var result MarketSell result, err = b.MarketSellOrder(s.Pair.Base.String(), s.Amount) + if err != nil { + return submitOrderResponse, err + } orderID = result.OrderID } - if orderID != "" { submitOrderResponse.OrderID = orderID + submitOrderResponse.FullyMatched = true } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + submitOrderResponse.IsOrderPlaced = true + + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -358,12 +363,13 @@ func (b *Bithumb) CancelAllOrders(orderCancellation *order.Cancel) (order.Cancel } var allOrders []OrderData - for _, currency := range b.GetEnabledPairs(asset.Spot) { + currs := b.GetEnabledPairs(asset.Spot) + for i := range currs { orders, err := b.GetOrders("", orderCancellation.Side.String(), "100", "", - currency.Base.String()) + currs[i].Base.String()) if err != nil { return cancelAllOrdersResponse, err } diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index c1dd5b64..f92fdee0 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -1,7 +1,9 @@ package bitmex import ( + "log" "net/http" + "os" "sync" "testing" "time" @@ -25,19 +27,16 @@ const ( var b Bitmex -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Bitmex load config error", err) + log.Fatal("Bitmex load config error", err) } bitmexConfig, err := cfg.GetExchangeConfig("Bitmex") if err != nil { - t.Error("Bitmex Setup() init error") + log.Fatal("Bitmex Setup() init error") } bitmexConfig.API.AuthenticatedSupport = true @@ -47,8 +46,9 @@ func TestSetup(t *testing.T) { err = b.Setup(bitmexConfig) if err != nil { - t.Fatal("Bitmex setup error", err) + log.Fatal("Bitmex setup error", err) } + os.Exit(m.Run()) } func TestStart(t *testing.T) { @@ -386,7 +386,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -398,9 +398,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.00075) || err != nil { @@ -469,21 +466,15 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.WithdrawCryptoWithEmailText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -497,9 +488,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, @@ -521,9 +509,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -548,15 +533,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "123456789012345678901234567890123456", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -574,15 +555,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "123456789012345678901234567890123456", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -605,7 +582,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { _, err := b.GetAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) @@ -619,6 +596,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := b.ModifyOrder(&order.Modify{OrderID: "1337"}) if err == nil { t.Error("ModifyOrder() error") @@ -626,9 +606,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) - withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -653,9 +630,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -668,9 +642,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -698,8 +669,6 @@ func TestGetDepositAddress(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { - b.SetDefaults() - TestSetup(t) if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 306a8a9a..4f65011d 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -432,15 +432,18 @@ func (b *Bitmex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } response, err := b.CreateOrder(&orderNewParams) + if err != nil { + return submitOrderResponse, err + } if response.OrderID != "" { submitOrderResponse.OrderID = response.OrderID } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 77d8b575..39d7dfd4 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -38,7 +38,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, @@ -319,7 +319,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.AutoWithdrawFiatText - withdrawPermissions := b.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index facc2950..5b74ddf6 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -364,16 +364,18 @@ func (b *Bitstamp) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.Amount, buy, market) - + if err != nil { + return submitOrderResponse, err + } if response.ID > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.ID, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true + submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index 4d9489d5..a564dd16 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -329,7 +329,7 @@ func (b *Bittrex) Withdraw(currency, paymentID, address string, quantity float64 var id UUID values := url.Values{} values.Set("currency", currency) - values.Set("quantity", fmt.Sprintf("%v", quantity)) + values.Set("quantity", strconv.FormatFloat(quantity, 'f', -1, 64)) values.Set("address", address) if len(paymentID) > 0 { values.Set("paymentid", paymentID) diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 976fe165..5cc15712 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -1,6 +1,8 @@ package bittrex import ( + "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -19,22 +21,16 @@ const ( var b Bittrex -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() - if b.Name != "Bittrex" { - t.Error("Bittrex - SetDefaults() error") - } -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Bittrex load config error", err) + log.Fatal("Bittrex load config error", err) } bConfig, err := cfg.GetExchangeConfig("Bittrex") if err != nil { - t.Error("Bittrex Setup() init error") + log.Fatal("Bittrex Setup() init error") } bConfig.API.Credentials.Key = apiKey bConfig.API.Credentials.Secret = apiSecret @@ -42,13 +38,15 @@ func TestSetup(t *testing.T) { err = b.Setup(bConfig) if err != nil { - t.Fatal("Bittrex setup error", err) + log.Fatal("Bittrex setup error", err) } if !b.IsEnabled() || !b.API.AuthenticatedSupport || b.Verbose || len(b.BaseCurrencies) < 1 { - t.Error("Bittrex Setup values not set correctly") + log.Fatal("Bittrex Setup values not set correctly") } + + os.Exit(m.Run()) } func TestGetMarkets(t *testing.T) { @@ -236,7 +234,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -248,11 +246,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var feeBuilder = setFeeBuilder() - // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.0025) || err != nil { t.Error(err) @@ -320,20 +314,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, @@ -351,9 +339,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -373,9 +358,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -401,15 +383,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -427,15 +405,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -458,6 +432,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -465,8 +442,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -490,9 +465,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -506,9 +478,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index da8a543b..0af7bb87 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -329,7 +329,6 @@ func (b *Bittrex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } buy := s.OrderSide == order.Buy - if s.OrderType != order.Limit { return submitOrderResponse, errors.New("limit orders only supported on exchange") @@ -346,16 +345,16 @@ func (b *Bittrex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.Amount, s.Price) } - + if err != nil { + return submitOrderResponse, err + } if response.Result.ID != "" { submitOrderResponse.OrderID = response.Result.ID } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/btse/btse_test.go b/exchanges/btse/btse_test.go index d97130b0..d0cd92fa 100644 --- a/exchanges/btse/btse_test.go +++ b/exchanges/btse/btse_test.go @@ -205,7 +205,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -320,7 +320,6 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-") - var orderCancellation = &order.Cancel{ OrderID: "b334ecef-2b42-4998-b8a4-b6b14f6d2671", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -341,7 +340,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-") - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 5a5ca079..02444533 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -3,6 +3,7 @@ package btse import ( "errors" "fmt" + "strconv" "strings" "sync" "time" @@ -357,7 +358,9 @@ func (b *BTSE) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { resp.IsOrderPlaced = true resp.OrderID = *r } - + if s.OrderType == order.Market { + resp.FullyMatched = true + } return resp, nil } @@ -475,7 +478,7 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) { } od.Trades = append(od.Trades, order.TradeHistory{ Timestamp: createdAt, - TID: fills[i].ID, + TID: strconv.FormatInt(fills[i].ID, 10), Price: fills[i].Price, Amount: fills[i].Amount, Exchange: b.Name, @@ -569,7 +572,7 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err } openOrder.Trades = append(openOrder.Trades, order.TradeHistory{ Timestamp: createdAt, - TID: fills[i].ID, + TID: strconv.FormatInt(fills[i].ID, 10), Price: fills[i].Price, Amount: fills[i].Amount, Exchange: b.Name, diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 95d84f4a..b594603c 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1,7 +1,9 @@ package coinbasepro import ( + "log" "net/http" + "os" "testing" "time" @@ -25,20 +27,18 @@ const ( testPair = "BTC-USD" ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { c.SetDefaults() c.Requester.SetRateLimit(false, time.Second, 1) -} -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("coinbasepro load config error", err) + log.Fatal("coinbasepro load config error", err) } gdxConfig, err := cfg.GetExchangeConfig("CoinbasePro") if err != nil { - t.Error("coinbasepro Setup() init error") + log.Fatal("coinbasepro Setup() init error") } gdxConfig.API.Credentials.Key = apiKey gdxConfig.API.Credentials.Secret = apiSecret @@ -47,8 +47,10 @@ func TestSetup(t *testing.T) { gdxConfig.API.AuthenticatedWebsocketSupport = true err = c.Setup(gdxConfig) if err != nil { - t.Fatal("CoinbasePro setup error", err) + log.Fatal("CoinbasePro setup error", err) } + + os.Exit(m.Run()) } func TestGetProducts(t *testing.T) { @@ -198,7 +200,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() c.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -210,12 +212,9 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - c.SetDefaults() - TestSetup(t) - var feeBuilder = setFeeBuilder() - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic if resp, err := c.GetFee(feeBuilder); resp != float64(0.003) || err != nil { t.Error(err) @@ -371,7 +370,6 @@ func TestCalculateTradingFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - c.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText withdrawPermissions := c.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { @@ -380,9 +378,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { } func TestGetActiveOrders(t *testing.T) { - c.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, @@ -398,9 +393,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - c.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, @@ -422,9 +414,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -450,15 +439,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -476,15 +461,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -507,6 +488,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := c.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -514,8 +498,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - c.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -539,9 +521,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -564,9 +543,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -597,8 +573,6 @@ func TestGetDepositAddress(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { - c.SetDefaults() - TestSetup(t) if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 96c80b44..6994211b 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -3,7 +3,6 @@ package coinbasepro import ( "encoding/json" "errors" - "fmt" "net/http" "strconv" "time" @@ -311,7 +310,7 @@ func (c *CoinbasePro) Subscribe(channelToSubscribe wshandler.WebsocketChannelSub }, } if channelToSubscribe.Channel == "user" || channelToSubscribe.Channel == "full" { - n := fmt.Sprintf("%v", time.Now().Unix()) + n := strconv.FormatInt(time.Now().Unix(), 10) message := n + "GET" + "/users/self/verify" hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(message), []byte(c.API.Credentials.Secret)) diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index a66ebf41..959a3de8 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -400,16 +400,19 @@ func (c *CoinbasePro) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) default: err = errors.New("order type not supported") } - + if err != nil { + return submitOrderResponse, err + } + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true + } if response != "" { submitOrderResponse.OrderID = response } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -509,7 +512,7 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta var orders []order.Detail for i := range respOrders { - currency := currency.NewPairDelimiter(respOrders[i].ProductID, + curr := currency.NewPairDelimiter(respOrders[i].ProductID, c.GetPairFormat(asset.Spot, false).Delimiter) orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) orderType := order.Type(strings.ToUpper(respOrders[i].Type)) @@ -530,7 +533,7 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta OrderType: orderType, OrderDate: orderDate, OrderSide: orderSide, - CurrencyPair: currency, + CurrencyPair: curr, Exchange: c.Name, }) } @@ -545,9 +548,9 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta // Can Limit response to specific order status func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var respOrders []GeneralizedOrderResponse - for _, currency := range req.Currencies { + for i := range req.Currencies { resp, err := c.GetOrders([]string{"done", "settled"}, - c.FormatExchangeCurrency(currency, asset.Spot).String()) + c.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String()) if err != nil { return nil, err } @@ -556,7 +559,7 @@ func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Deta var orders []order.Detail for i := range respOrders { - currency := currency.NewPairDelimiter(respOrders[i].ProductID, + curr := currency.NewPairDelimiter(respOrders[i].ProductID, c.GetPairFormat(asset.Spot, false).Delimiter) orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) orderType := order.Type(strings.ToUpper(respOrders[i].Type)) @@ -577,7 +580,7 @@ func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Deta OrderType: orderType, OrderDate: orderDate, OrderSide: orderSide, - CurrencyPair: currency, + CurrencyPair: curr, Exchange: c.Name, }) } diff --git a/exchanges/coinbene/coinbene.go b/exchanges/coinbene/coinbene.go index 4bb0653e..482edab7 100644 --- a/exchanges/coinbene/coinbene.go +++ b/exchanges/coinbene/coinbene.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -27,8 +28,6 @@ const ( coinbeneAPIURL = "https://openapi-exchange.coinbene.com/api/exchange/" coinbeneAuthPath = "/api/exchange/v2" coinbeneAPIVersion = "v2" - buy = "buy" - sell = "sell" // Public endpoints coinbeneFetchTicker = "/market/ticker/one" @@ -114,9 +113,9 @@ func (c *Coinbene) PlaceOrder(price, quantity float64, symbol, direction, client params := url.Values{} params.Set("symbol", symbol) switch direction { - case sell: + case order.Buy.Lower(): params.Set("direction", "2") - case buy: + case order.Sell.Lower(): params.Set("direction", "1") default: return resp, diff --git a/exchanges/coinbene/coinbene_test.go b/exchanges/coinbene/coinbene_test.go index b870ce71..5c77b5f1 100644 --- a/exchanges/coinbene/coinbene_test.go +++ b/exchanges/coinbene/coinbene_test.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Please supply your own keys here for due diligence testing @@ -103,7 +104,7 @@ func TestPlaceOrder(t *testing.T) { if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") } - _, err := c.PlaceOrder(140, 1, btcusdt, "buy", "") + _, err := c.PlaceOrder(1, 1, btcusdt, order.Buy.Lower(), "") if err != nil { t.Error(err) } diff --git a/exchanges/coinbene/coinbene_wrapper.go b/exchanges/coinbene/coinbene_wrapper.go index d88c751f..373222f1 100644 --- a/exchanges/coinbene/coinbene_wrapper.go +++ b/exchanges/coinbene/coinbene_wrapper.go @@ -3,6 +3,7 @@ package coinbene import ( "fmt" "strconv" + "strings" "sync" "time" @@ -504,9 +505,9 @@ func (c *Coinbene) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([] for y := range tempData.OpenOrders { tempResp.Exchange = c.Name tempResp.CurrencyPair = getOrdersRequest.Currencies[x] - tempResp.OrderSide = buy - if tempData.OpenOrders[y].OrderType == sell { - tempResp.OrderSide = sell + tempResp.OrderSide = order.Buy + if strings.EqualFold(tempData.OpenOrders[y].OrderType, order.Sell.String()) { + tempResp.OrderSide = order.Sell } t, err = time.Parse(time.RFC3339, tempData.OpenOrders[y].OrderTime) if err != nil { @@ -556,7 +557,7 @@ func (c *Coinbene) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([] tempResp.Exchange = c.Name tempResp.CurrencyPair = getOrdersRequest.Currencies[x] tempResp.OrderSide = order.Buy - if tempData.Data[y].OrderType == sell { + if strings.EqualFold(tempData.Data[y].OrderType, order.Sell.String()) { tempResp.OrderSide = order.Sell } t, err = time.Parse(time.RFC3339, tempData.Data[y].OrderTime) diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 4ba2ec97..7cb7157e 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "github.com/thrasher-corp/gocryptotrader/common/crypto" @@ -105,9 +106,8 @@ func (c *COINUT) GetTrades(instrumentID int) (Trades, error) { } // GetUserBalance returns the full user balance -func (c *COINUT) GetUserBalance() (UserBalance, error) { - result := UserBalance{} - +func (c *COINUT) GetUserBalance() (*UserBalance, error) { + var result *UserBalance return result, c.SendHTTPRequest(coinutBalance, nil, true, &result) } @@ -117,26 +117,16 @@ func (c *COINUT) NewOrder(instrumentID int64, quantity, price float64, buy bool, params := make(map[string]interface{}) params["inst_id"] = instrumentID if price > 0 { - params["price"] = fmt.Sprintf("%v", price) + params["price"] = strconv.FormatFloat(price, 'f', -1, 64) } - params["qty"] = fmt.Sprintf("%v", quantity) + params["qty"] = strconv.FormatFloat(quantity, 'f', -1, 64) params["side"] = order.Buy.String() if !buy { params["side"] = order.Sell.String() } params["client_ord_id"] = orderID - err := c.SendHTTPRequest(coinutOrder, params, true, &result) - if _, ok := result.(OrderRejectResponse); ok { - return result.(OrderRejectResponse), err - } - if _, ok := result.(OrderFilledResponse); ok { - return result.(OrderFilledResponse), err - } - if _, ok := result.(OrdersBase); ok { - return result.(OrdersBase), err - } - return result, err + return result, c.SendHTTPRequest(coinutOrder, params, true, &result) } // NewOrders places multiple orders on the exchange @@ -425,3 +415,77 @@ func getInternationalBankDepositFee(c currency.Code, amount float64) float64 { return fee } + +// IsLoaded returns whether or not the instrument map has been seeded +func (i *instrumentMap) IsLoaded() bool { + i.m.Lock() + defer i.m.Unlock() + return i.Loaded +} + +// Seed seeds the instrument map +func (i *instrumentMap) Seed(curr string, id int64) { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + i.Instruments = make(map[string]int64) + } + + // check to see if the instrument already exists + _, ok := i.Instruments[curr] + if ok { + return + } + + i.Instruments[curr] = id + i.Loaded = true +} + +// LookupInstrument looks up an instrument based on an id +func (i *instrumentMap) LookupInstrument(id int64) string { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + return "" + } + + for k, v := range i.Instruments { + if v == id { + return k + } + } + return "" +} + +// LookupID looks up an ID based on a string +func (i *instrumentMap) LookupID(curr string) int64 { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + return 0 + } + + if ic, ok := i.Instruments[curr]; ok { + return ic + } + return 0 +} + +// GetInstrumentIDs returns a list of IDs +func (i *instrumentMap) GetInstrumentIDs() []int64 { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + return nil + } + + var instruments []int64 + for _, x := range i.Instruments { + instruments = append(instruments, x) + } + return instruments +} diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 622d75c1..c3616708 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -1,7 +1,9 @@ package coinut import ( + "log" "net/http" + "os" "testing" "github.com/gorilla/websocket" @@ -24,45 +26,42 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { c.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Coinut load config error", err) + log.Fatal("Coinut load config error", err) } bConfig, err := cfg.GetExchangeConfig("COINUT") if err != nil { - t.Error("Coinut Setup() init error") + log.Fatal("Coinut Setup() init error") } bConfig.API.AuthenticatedSupport = true bConfig.API.AuthenticatedWebsocketSupport = true bConfig.API.Credentials.Key = apiKey bConfig.API.Credentials.ClientID = clientID - bConfig.Verbose = true err = c.Setup(bConfig) if err != nil { - t.Fatal("Coinut setup error", err) + log.Fatal("Coinut setup error", err) } - if !c.IsEnabled() || !c.Verbose || - c.Websocket.IsEnabled() || len(c.BaseCurrencies) < 1 { - t.Error("Coinut Setup values not set correctly") - } + c.SeedInstruments() + + os.Exit(m.Run()) } func setupWSTestAuth(t *testing.T) { if wsSetupRan { return } - c.SetDefaults() - TestSetup(t) + if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } + if areTestAPIKeysSet() { + c.Websocket.SetCanUseAuthenticatedEndpoints(true) + } c.WebsocketConn = &wshandler.WebsocketConnection{ ExchangeName: c.Name, URL: coinutWebsocketURL, @@ -71,6 +70,7 @@ func setupWSTestAuth(t *testing.T) { ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, } + var dialer websocket.Dialer err := c.WebsocketConn.Dial(&dialer, http.Header{}) if err != nil { @@ -84,6 +84,10 @@ func setupWSTestAuth(t *testing.T) { t.Error(err) } wsSetupRan = true + _, err = c.WsGetInstruments() + if err != nil { + t.Error(err) + } } func TestGetInstruments(t *testing.T) { @@ -130,12 +134,8 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - c.SetDefaults() - TestSetup(t) t.Parallel() - var feeBuilder = setFeeBuilder() - // CryptocurrencyTradeFee Basic if resp, err := c.GetFee(feeBuilder); resp != float64(0.001) || err != nil { t.Error(err) @@ -248,38 +248,29 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - c.SetDefaults() expectedResult := exchange.WithdrawCryptoViaWebsiteOnlyText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := c.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - c.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } - _, err := c.GetActiveOrders(&getOrdersRequest) if areTestAPIKeysSet() && err != nil { t.Errorf("Could not get open orders: %s", err) } } -func TestGetOrderHistory(t *testing.T) { - c.SetDefaults() - TestSetup(t) - +func TestGetOrderHistoryWrapper(t *testing.T) { + setupWSTestAuth(t) var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, - currency.LTC)}, + currency.USD)}, } _, err := c.GetOrderHistory(&getOrdersRequest) @@ -295,9 +286,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -311,7 +299,7 @@ func TestSubmitOrder(t *testing.T) { OrderType: order.Limit, Price: 1, Amount: 1, - ClientID: "meowOrder", + ClientID: "123", } response, err := c.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { @@ -322,15 +310,10 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - + currencyPair := currency.NewPair(currency.BTC, currency.USD) var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -348,15 +331,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -393,6 +372,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := c.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -400,8 +382,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - c.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -422,9 +402,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -437,9 +414,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -473,14 +447,14 @@ func TestWsAuthSubmitOrder(t *testing.T) { if !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - order := WsSubmitOrderParameters{ + ord := WsSubmitOrderParameters{ Amount: 1, Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 1, Price: 1, Side: order.Buy, } - _, err := c.wsSubmitOrder(&order) + _, err := c.wsSubmitOrder(&ord) if err != nil { t.Error(err) } @@ -513,12 +487,13 @@ func TestWsAuthSubmitOrders(t *testing.T) { } // TestWsAuthCancelOrders dials websocket, cancels orders +// doesn't care about if the order cancellations fail func TestWsAuthCancelOrders(t *testing.T) { setupWSTestAuth(t) if !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - order := WsCancelOrderParameters{ + ord := WsCancelOrderParameters{ Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 1, } @@ -526,9 +501,28 @@ func TestWsAuthCancelOrders(t *testing.T) { Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 2, } - _, errs := c.wsCancelOrders([]WsCancelOrderParameters{order, order2}) - if len(errs) > 0 { - t.Error(errs) + resp, err := c.wsCancelOrders([]WsCancelOrderParameters{ord, order2}) + if err != nil { + t.Error(err) + } + if resp.Status[0] != "OK" { + t.Error("Order failed to cancel") + } +} + +// TestWsAuthCancelOrders dials websocket, cancels orders +// Checks that the wrapper oversight works +func TestWsAuthCancelOrdersWrapper(t *testing.T) { + setupWSTestAuth(t) + if !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } + orderDetails := order.Cancel{ + CurrencyPair: currency.NewPair(currency.LTC, currency.BTC), + } + _, err := c.CancelAllOrders(&orderDetails) + if err != nil { + t.Error(err) } } @@ -538,20 +532,23 @@ func TestWsAuthCancelOrder(t *testing.T) { if !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - order := WsCancelOrderParameters{ + ord := &WsCancelOrderParameters{ Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 1, } - err := c.wsCancelOrder(order) + resp, err := c.wsCancelOrder(ord) if err != nil { t.Error(err) } + if len(resp.Status) >= 1 && resp.Status[0] != "OK" { + t.Errorf("Failed to cancel order") + } } // TestWsAuthGetOpenOrders dials websocket, retrieves open orders func TestWsAuthGetOpenOrders(t *testing.T) { setupWSTestAuth(t) - err := c.wsGetOpenOrders(currency.NewPair(currency.LTC, currency.BTC)) + _, err := c.wsGetOpenOrders(currency.NewPair(currency.LTC, currency.BTC).String()) if err != nil { t.Error(err) } @@ -573,7 +570,6 @@ func TestCurrencyMapIsLoaded(t *testing.T) { func TestCurrencyMapSeed(t *testing.T) { t.Parallel() var i instrumentMap - // Test non-seeded lookups if id := i.LookupInstrument(1234); id != "" { t.Error("unexpected result") diff --git a/exchanges/coinut/coinut_types.go b/exchanges/coinut/coinut_types.go index b42d3425..a647afdf 100644 --- a/exchanges/coinut/coinut_types.go +++ b/exchanges/coinut/coinut_types.go @@ -1,7 +1,6 @@ package coinut import ( - "strings" "sync" "github.com/thrasher-corp/gocryptotrader/currency" @@ -177,7 +176,7 @@ type CancelOrdersResponse struct { Results []struct { OrderID int64 `json:"order_id"` Status string `json:"status"` - InstrumentID int `json:"inst_id"` + InstrumentID int64 `json:"inst_id"` } `json:"results"` } @@ -372,10 +371,10 @@ type WsTradeUpdate struct { // WsInstrumentList defines instrument list type WsInstrumentList struct { - Spot map[string][]WsSupportedCurrency `json:"SPOT"` - Nonce int64 `json:"nonce"` - Reply string `json:"inst_list"` - Status []interface{} `json:"status"` + Spot map[string][]InstrumentBase `json:"SPOT"` + Nonce int64 `json:"nonce,omitempty"` + Reply string `json:"inst_list,omitempty"` + Status []interface{} `json:"status,omitempty"` } // WsSupportedCurrency defines supported currency on the exchange @@ -536,14 +535,15 @@ type WsOrderFilledResponse struct { // WsOrderData ws response data type WsOrderData struct { - ClientOrdID int64 `json:"client_ord_id"` - InstID int64 `json:"inst_id"` - OpenQty float64 `json:"open_qty,string"` - OrderID int64 `json:"order_id"` - Price float64 `json:"price,string"` - Qty float64 `json:"qty,string"` - Side string `json:"side"` - Timestamp int64 `json:"timestamp"` + ClientOrdID int64 `json:"client_ord_id"` + InstID int64 `json:"inst_id"` + OpenQty float64 `json:"open_qty,string"` + OrderID int64 `json:"order_id"` + Price float64 `json:"price,string"` + Qty float64 `json:"qty,string"` + Side string `json:"side"` + Timestamp int64 `json:"timestamp"` + Status []string `json:"status"` } // WsOrderFilledCommissionData ws response data @@ -656,20 +656,20 @@ type WsNewOrderResponse struct { // WsGetAccountBalanceResponse contains values of each currency type WsGetAccountBalanceResponse struct { - BCH string `json:"BCH"` - BTC string `json:"BTC"` - BTG string `json:"BTG"` - CAD string `json:"CAD"` - ETC string `json:"ETC"` - ETH string `json:"ETH"` - LCH string `json:"LCH"` - LTC string `json:"LTC"` - MYR string `json:"MYR"` - SGD string `json:"SGD"` - USD string `json:"USD"` - USDT string `json:"USDT"` - XMR string `json:"XMR"` - ZEC string `json:"ZEC"` + BCH float64 `json:"BCH,string"` + BTC float64 `json:"BTC,string"` + BTG float64 `json:"BTG,string"` + CAD float64 `json:"CAD,string"` + ETC float64 `json:"ETC,string"` + ETH float64 `json:"ETH,string"` + LCH float64 `json:"LCH,string"` + LTC float64 `json:"LTC,string"` + MYR float64 `json:"MYR,string"` + SGD float64 `json:"SGD,string"` + USD float64 `json:"USD,string"` + USDT float64 `json:"USDT,string"` + XMR float64 `json:"XMR,string"` + ZEC float64 `json:"ZEC,string"` Nonce int64 `json:"nonce"` Reply string `json:"reply"` Status []string `json:"status"` @@ -681,79 +681,3 @@ type instrumentMap struct { Loaded bool m sync.Mutex } - -// IsLoaded returns whether or not the instrument map has been seeded -func (i *instrumentMap) IsLoaded() bool { - i.m.Lock() - defer i.m.Unlock() - return i.Loaded -} - -// Seed seeds the instrument map -func (i *instrumentMap) Seed(currency string, id int64) { - i.m.Lock() - defer i.m.Unlock() - - if !i.Loaded { - i.Instruments = make(map[string]int64) - } - - // check to see if the instrument already exists - _, ok := i.Instruments[currency] - if ok { - return - } - - i.Instruments[currency] = id - i.Loaded = true -} - -// LookupInstrument looks up an instrument based on an id -func (i *instrumentMap) LookupInstrument(id int64) string { - i.m.Lock() - defer i.m.Unlock() - - if !i.Loaded { - return "" - } - - for k, v := range i.Instruments { - if v == id { - return k - } - } - return "" -} - -// LookupID looks up an ID based on a string -func (i *instrumentMap) LookupID(currency string) int64 { - i.m.Lock() - defer i.m.Unlock() - - if !i.Loaded { - return 0 - } - - for k, v := range i.Instruments { - if strings.EqualFold(currency, k) { - return v - } - } - return 0 -} - -// GetInstrumentIDs returns a list of IDs -func (i *instrumentMap) GetInstrumentIDs() []int64 { - i.m.Lock() - defer i.m.Unlock() - - if !i.Loaded { - return nil - } - - var instruments []int64 - for _, x := range i.Instruments { - instruments = append(instruments, x) - } - return instruments -} diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index 6d242239..c5dfbb19 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -17,6 +17,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" + log "github.com/thrasher-corp/gocryptotrader/logger" ) const ( @@ -25,8 +26,7 @@ const ( ) var ( - channels map[string]chan []byte - wsInstrumentMap instrumentMap + channels map[string]chan []byte ) // NOTE for speed considerations @@ -46,13 +46,17 @@ func (c *COINUT) WsConnect() error { } go c.WsHandleData() - if !wsInstrumentMap.IsLoaded() { - err = c.WsSetInstrumentList() + if !c.instrumentMap.IsLoaded() { + _, err = c.WsGetInstruments() if err != nil { return err } } - c.wsAuthenticate() + err = c.wsAuthenticate() + if err != nil { + c.Websocket.SetCanUseAuthenticatedEndpoints(false) + log.Error(log.WebsocketMgr, err) + } c.GenerateDefaultSubscriptions() // define bi-directional communication @@ -135,7 +139,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { return } - currencyPair := wsInstrumentMap.LookupInstrument(ticker.InstID) + currencyPair := c.instrumentMap.LookupInstrument(ticker.InstID) c.Websocket.DataHandler <- wshandler.TickerData{ Exchange: c.Name, Volume: ticker.Volume24, @@ -164,7 +168,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } - currencyPair := wsInstrumentMap.LookupInstrument(orderbooksnapshot.InstID) + currencyPair := c.instrumentMap.LookupInstrument(orderbooksnapshot.InstID) c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Exchange: c.Name, Asset: asset.Spot, @@ -184,7 +188,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } - currencyPair := wsInstrumentMap.LookupInstrument(orderbookUpdate.InstID) + currencyPair := c.instrumentMap.LookupInstrument(orderbookUpdate.InstID) c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Exchange: c.Name, Asset: asset.Spot, @@ -207,7 +211,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } - currencyPair := wsInstrumentMap.LookupInstrument(tradeUpdate.InstID) + currencyPair := c.instrumentMap.LookupInstrument(tradeUpdate.InstID) c.Websocket.DataHandler <- wshandler.TradeData{ Timestamp: time.Unix(tradeUpdate.Timestamp, 0), CurrencyPair: currency.NewPairFromFormattedPairs(currencyPair, @@ -238,8 +242,9 @@ func (c *COINUT) GetNonce() int64 { return int64(c.Nonce.Get()) } -// WsSetInstrumentList fetches instrument list and propagates a local cache -func (c *COINUT) WsSetInstrumentList() error { +// WsGetInstruments fetches instrument list and propagates a local cache +func (c *COINUT) WsGetInstruments() (Instruments, error) { + var list Instruments request := wsRequest{ Request: "inst_list", SecType: strings.ToUpper(asset.Spot.String()), @@ -247,20 +252,19 @@ func (c *COINUT) WsSetInstrumentList() error { } resp, err := c.WebsocketConn.SendMessageReturnResponse(request.Nonce, request) if err != nil { - return err + return list, err } - var list WsInstrumentList err = json.Unmarshal(resp, &list) if err != nil { - return err + return list, err } - for curr, data := range list.Spot { - wsInstrumentMap.Seed(curr, data[0].InstID) + for curr, data := range list.Instruments { + c.instrumentMap.Seed(curr, data[0].InstID) } - if len(wsInstrumentMap.GetInstrumentIDs()) == 0 { - return errors.New("instrument list failed to populate") + if len(c.instrumentMap.GetInstrumentIDs()) == 0 { + return list, errors.New("instrument list failed to populate") } - return nil + return list, nil } // WsProcessOrderbookSnapshot processes the orderbook snapshot @@ -285,7 +289,7 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { newOrderBook.Asks = asks newOrderBook.Bids = bids newOrderBook.Pair = currency.NewPairFromFormattedPairs( - wsInstrumentMap.LookupInstrument(ob.InstID), + c.instrumentMap.LookupInstrument(ob.InstID), c.GetEnabledPairs(asset.Spot), c.GetPairFormat(asset.Spot, true), ) @@ -298,7 +302,7 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { // WsProcessOrderbookUpdate process an orderbook update func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error { p := currency.NewPairFromFormattedPairs( - wsInstrumentMap.LookupInstrument(update.InstID), + c.instrumentMap.LookupInstrument(update.InstID), c.GetEnabledPairs(asset.Spot), c.GetPairFormat(asset.Spot, true), ) @@ -335,7 +339,7 @@ func (c *COINUT) GenerateDefaultSubscriptions() { func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { subscribe := wsRequest{ Request: channelToSubscribe.Channel, - InstID: wsInstrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, + InstID: c.instrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String()), Subscribe: true, Nonce: c.WebsocketConn.GenerateMessageID(false), @@ -347,7 +351,7 @@ func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscrip func (c *COINUT) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { subscribe := wsRequest{ Request: channelToSubscribe.Channel, - InstID: wsInstrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, + InstID: c.instrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String()), Subscribe: false, Nonce: c.WebsocketConn.GenerateMessageID(false), @@ -406,7 +410,7 @@ func (c *COINUT) wsAuthenticate() error { return nil } -func (c *COINUT) wsGetAccountBalance() (*WsGetAccountBalanceResponse, error) { +func (c *COINUT) wsGetAccountBalance() (*UserBalance, error) { if !c.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authorised to submit order", c.Name) } @@ -418,7 +422,7 @@ func (c *COINUT) wsGetAccountBalance() (*WsGetAccountBalanceResponse, error) { if err != nil { return nil, err } - var response WsGetAccountBalanceResponse + var response UserBalance err = json.Unmarshal(resp, &response) if err != nil { return nil, err @@ -429,21 +433,21 @@ func (c *COINUT) wsGetAccountBalance() (*WsGetAccountBalanceResponse, error) { return &response, nil } -func (c *COINUT) wsSubmitOrder(order *WsSubmitOrderParameters) (*WsStandardOrderResponse, error) { +func (c *COINUT) wsSubmitOrder(o *WsSubmitOrderParameters) (*WsStandardOrderResponse, error) { if !c.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authorised to submit order", c.Name) } - curr := c.FormatExchangeCurrency(order.Currency, asset.Spot).String() + curr := c.FormatExchangeCurrency(o.Currency, asset.Spot).String() var orderSubmissionRequest WsSubmitOrderRequest orderSubmissionRequest.Request = "new_order" orderSubmissionRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) - orderSubmissionRequest.InstID = wsInstrumentMap.LookupID(curr) - orderSubmissionRequest.Qty = order.Amount - orderSubmissionRequest.Price = order.Price - orderSubmissionRequest.Side = string(order.Side) + orderSubmissionRequest.InstID = c.instrumentMap.LookupID(curr) + orderSubmissionRequest.Qty = o.Amount + orderSubmissionRequest.Price = o.Price + orderSubmissionRequest.Side = string(o.Side) - if order.OrderID > 0 { - orderSubmissionRequest.OrderID = order.OrderID + if o.OrderID > 0 { + orderSubmissionRequest.OrderID = o.OrderID } resp, err := c.WebsocketConn.SendMessageReturnResponse(orderSubmissionRequest.Nonce, orderSubmissionRequest) if err != nil { @@ -548,7 +552,7 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO Qty: orders[i].Amount, Price: orders[i].Price, Side: string(orders[i].Side), - InstID: wsInstrumentMap.LookupID(curr), + InstID: c.instrumentMap.LookupID(curr), ClientOrdID: i + 1, }) } @@ -585,7 +589,7 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO if len(standardOrder.Reasons) > 0 && standardOrder.Reasons[0] != "" { errors = append(errors, fmt.Errorf("%v order submission failed for currency %v and orderID %v, message %v ", c.Name, - wsInstrumentMap.LookupInstrument(standardOrder.InstID), + c.instrumentMap.LookupInstrument(standardOrder.InstID), standardOrder.OrderID, standardOrder.Reasons[0])) @@ -597,73 +601,73 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO return ordersResponse, errors } -func (c *COINUT) wsGetOpenOrders(p currency.Pair) error { +func (c *COINUT) wsGetOpenOrders(curr string) (*WsUserOpenOrdersResponse, error) { + var response *WsUserOpenOrdersResponse if !c.Websocket.CanUseAuthenticatedEndpoints() { - return fmt.Errorf("%v not authorised to get open orders", c.Name) + return response, fmt.Errorf("%v not authorised to get open orders", c.Name) } - curr := c.FormatExchangeCurrency(p, asset.Spot).String() var openOrdersRequest WsGetOpenOrdersRequest openOrdersRequest.Request = "user_open_orders" openOrdersRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) - openOrdersRequest.InstID = wsInstrumentMap.LookupID(curr) + openOrdersRequest.InstID = c.instrumentMap.LookupID(curr) resp, err := c.WebsocketConn.SendMessageReturnResponse(openOrdersRequest.Nonce, openOrdersRequest) if err != nil { - return err + return response, err } - var response map[string]interface{} err = json.Unmarshal(resp, &response) if err != nil { - return err + return response, err } - if response["status"].([]interface{})[0] != "OK" { - return fmt.Errorf("%v get open orders failed for currency %v", + if response.Status[0] != "OK" { + return response, fmt.Errorf("%v get open orders failed for currency %v", c.Name, - p) + curr) } - return nil + return response, nil } -func (c *COINUT) wsCancelOrder(cancellation WsCancelOrderParameters) error { +func (c *COINUT) wsCancelOrder(cancellation *WsCancelOrderParameters) (*CancelOrdersResponse, error) { + var response *CancelOrdersResponse if !c.Websocket.CanUseAuthenticatedEndpoints() { - return fmt.Errorf("%v not authorised to cancel order", c.Name) + return response, fmt.Errorf("%v not authorised to cancel order", c.Name) } - currency := c.FormatExchangeCurrency(cancellation.Currency, asset.Spot).String() + curr := c.FormatExchangeCurrency(cancellation.Currency, asset.Spot).String() var cancellationRequest WsCancelOrderRequest cancellationRequest.Request = "cancel_order" - cancellationRequest.InstID = wsInstrumentMap.LookupID(currency) + cancellationRequest.InstID = c.instrumentMap.LookupID(curr) cancellationRequest.OrderID = cancellation.OrderID cancellationRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) resp, err := c.WebsocketConn.SendMessageReturnResponse(cancellationRequest.Nonce, cancellationRequest) if err != nil { - return err + return response, err } - var response map[string]interface{} err = json.Unmarshal(resp, &response) if err != nil { - return err + return response, err } - if response["status"].([]interface{})[0] != "OK" { - return fmt.Errorf("%v order cancellation failed for currency %v and orderID %v, message %v", + if response.Status[0] != "OK" { + return response, fmt.Errorf("%v order cancellation failed for currency %v and orderID %v, message %v", c.Name, cancellation.Currency, cancellation.OrderID, - response["status"]) + response.Status[0]) } - return nil + return response, nil } -func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*WsCancelOrdersResponse, []error) { - var errors []error +func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*CancelOrdersResponse, error) { + var err error + var response *CancelOrdersResponse if !c.Websocket.CanUseAuthenticatedEndpoints() { - return nil, errors + return nil, err } - cancelOrderRequest := WsCancelOrdersRequest{} + var cancelOrderRequest WsCancelOrdersRequest for i := range cancellations { - currency := c.FormatExchangeCurrency(cancellations[i].Currency, asset.Spot).String() + curr := c.FormatExchangeCurrency(cancellations[i].Currency, asset.Spot).String() cancelOrderRequest.Entries = append(cancelOrderRequest.Entries, WsCancelOrdersRequestEntry{ - InstID: wsInstrumentMap.LookupID(currency), + InstID: c.instrumentMap.LookupID(curr), OrderID: cancellations[i].OrderID, }) } @@ -672,53 +676,40 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*WsCan cancelOrderRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) resp, err := c.WebsocketConn.SendMessageReturnResponse(cancelOrderRequest.Nonce, cancelOrderRequest) if err != nil { - return nil, []error{err} + return response, err } - var response WsCancelOrdersResponse err = json.Unmarshal(resp, &response) if err != nil { - return nil, []error{err} + return response, err } - if response.Status[0] != "OK" { - return &response, []error{err} - } - for i := range response.Results { - if response.Results[i].Status != "OK" { - errors = append(errors, fmt.Errorf("%v order cancellation failed for currency %v and orderID %v, message %v", - c.Name, - wsInstrumentMap.LookupInstrument(response.Results[i].InstID), - response.Results[i].OrderID, - response.Results[i].Status)) - } - } - return &response, errors + return response, err } -func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) error { +func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) (*WsTradeHistoryResponse, error) { + var response *WsTradeHistoryResponse if !c.Websocket.CanUseAuthenticatedEndpoints() { - return fmt.Errorf("%v not authorised to get trade history", c.Name) + return response, fmt.Errorf("%v not authorised to get trade history", c.Name) } curr := c.FormatExchangeCurrency(p, asset.Spot).String() var request WsTradeHistoryRequest request.Request = "trade_history" - request.InstID = wsInstrumentMap.LookupID(curr) + request.InstID = c.instrumentMap.LookupID(curr) request.Nonce = c.WebsocketConn.GenerateMessageID(false) request.Start = start request.Limit = limit resp, err := c.WebsocketConn.SendMessageReturnResponse(request.Nonce, request) if err != nil { - return err + return response, err } - var response map[string]interface{} err = json.Unmarshal(resp, &response) if err != nil { - return err + return response, err } - if response["status"].([]interface{})[0] != "OK" { - return fmt.Errorf("%v get trade history failed for %v", + if response.Status[0] != "OK" { + return response, fmt.Errorf("%v get trade history failed for %v", c.Name, request) } - return nil + return response, nil } diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index c5c3e03e..aaa0b0e3 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -88,15 +88,20 @@ func (c *COINUT) SetDefaults() { FiatWithdrawalFee: true, }, WebsocketCapabilities: protocol.Features{ + AccountBalance: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + SubmitOrders: true, + UserTradeHistory: true, TickerFetching: true, - OrderbookFetching: true, TradeFetching: true, + OrderbookFetching: true, + AccountInfo: true, Subscribe: true, Unsubscribe: true, AuthenticatedEndpoints: true, - SubmitOrder: true, - SubmitOrders: true, - CancelOrder: true, MessageCorrelation: true, }, WithdrawPermissions: exchange.WithdrawCryptoViaWebsiteOnly | @@ -217,15 +222,25 @@ func (c *COINUT) Run() { // FetchTradablePairs returns a list of the exchanges tradable pairs func (c *COINUT) FetchTradablePairs(asset asset.Item) ([]string, error) { - i, err := c.GetInstruments() - if err != nil { - return nil, err + var instruments map[string][]InstrumentBase + var resp Instruments + var err error + if c.Websocket.IsConnected() { + resp, err = c.WsGetInstruments() + if err != nil { + return nil, err + } + } else { + resp, err = c.GetInstruments() + if err != nil { + return nil, err + } } - + instruments = resp.Instruments var pairs []string - for _, y := range i.Instruments { - c.instrumentMap.Seed(y[0].Base+y[0].Quote, y[0].InstID) - p := y[0].Base + c.GetPairFormat(asset, false).Delimiter + y[0].Quote + for i := range instruments { + c.instrumentMap.Seed(instruments[i][0].Base+instruments[i][0].Quote, instruments[i][0].InstID) + p := instruments[i][0].Base + c.GetPairFormat(asset, false).Delimiter + instruments[i][0].Quote pairs = append(pairs, p) } @@ -248,9 +263,20 @@ func (c *COINUT) UpdateTradablePairs(forceUpdate bool) error { // COINUT exchange func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - bal, err := c.GetUserBalance() - if err != nil { - return info, err + var bal *UserBalance + var err error + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var resp *UserBalance + resp, err = c.wsGetAccountBalance() + if err != nil { + return info, err + } + bal = resp + } else { + bal, err = c.GetUserBalance() + if err != nil { + return info, err + } } var balances = []exchange.AccountCurrencyInfo{ @@ -322,12 +348,9 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { // UpdateTicker updates and returns the ticker for a currency pair func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - - if !c.instrumentMap.IsLoaded() { - err := c.SeedInstruments() - if err != nil { - return tickerPrice, err - } + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return tickerPrice, err } instID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(p, @@ -335,8 +358,8 @@ func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri if instID == 0 { return tickerPrice, errors.New("unable to lookup instrument ID") } - - tick, err := c.GetInstrumentTicker(instID) + var tick Ticker + tick, err = c.GetInstrumentTicker(instID) if err != nil { return tickerPrice, err } @@ -379,12 +402,9 @@ func (c *COINUT) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderboo // UpdateOrderbook updates and returns the orderbook for a currency pair func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - - if !c.instrumentMap.IsLoaded() { - err := c.SeedInstruments() - if err != nil { - return orderBook, err - } + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return orderBook, err } instID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(p, @@ -430,69 +450,74 @@ func (c *COINUT) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex } // SubmitOrder submits a new order -func (c *COINUT) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { +func (c *COINUT) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - if err := s.Validate(); err != nil { - return submitOrderResponse, err + var err error + if _, err = strconv.Atoi(o.ClientID); err != nil { + return submitOrderResponse, fmt.Errorf("%s - ClientID must be a number, received: %s", c.Name, o.ClientID) } + err = o.Validate() - var APIresponse interface{} - isBuyOrder := s.OrderSide == order.Buy - clientIDInt, err := strconv.ParseUint(s.ClientID, 0, 32) if err != nil { return submitOrderResponse, err } - clientIDUint := uint32(clientIDInt) - - if !c.instrumentMap.IsLoaded() { - err = c.SeedInstruments() + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var response *WsStandardOrderResponse + response, err = c.wsSubmitOrder(&WsSubmitOrderParameters{ + Currency: o.Pair, + Side: o.OrderSide, + Amount: o.Amount, + Price: o.Price, + }) if err != nil { return submitOrderResponse, err } - } - - currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(s.Pair, - asset.Spot).String()) - if currencyID == 0 { - return submitOrderResponse, errLookupInstrumentID - } - - switch s.OrderType { - case order.Limit: - APIresponse, err = c.NewOrder(currencyID, - s.Amount, - s.Price, - isBuyOrder, - clientIDUint) - case order.Market: - APIresponse, err = c.NewOrder(currencyID, - s.Amount, - 0, - isBuyOrder, - clientIDUint) - } - - switch apiResp := APIresponse.(type) { - case OrdersBase: - orderResult := apiResp - submitOrderResponse.OrderID = strconv.FormatInt(orderResult.OrderID, 10) - case OrderFilledResponse: - orderResult := apiResp - submitOrderResponse.OrderID = strconv.FormatInt(orderResult.Order.OrderID, 10) - case OrderRejectResponse: - orderResult := apiResp - submitOrderResponse.OrderID = strconv.FormatInt(orderResult.OrderID, 10) - err = fmt.Errorf("orderID: %d was rejected: %v", - orderResult.OrderID, - orderResult.Reasons) - } - - if err == nil { + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) submitOrderResponse.IsOrderPlaced = true - } + } else { + err = c.loadInstrumentsIfNotLoaded() + if err != nil { + return submitOrderResponse, err + } - return submitOrderResponse, err + currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(o.Pair, + asset.Spot).String()) + if currencyID == 0 { + return submitOrderResponse, errLookupInstrumentID + } + + var APIResponse interface{} + var clientIDInt uint64 + isBuyOrder := o.OrderSide == order.Buy + clientIDInt, err = strconv.ParseUint(o.ClientID, 0, 32) + if err != nil { + return submitOrderResponse, err + } + clientIDUint := uint32(clientIDInt) + APIResponse, err = c.NewOrder(currencyID, o.Amount, o.Price, + isBuyOrder, clientIDUint) + if err != nil { + return submitOrderResponse, err + } + responseMap := APIResponse.(map[string]interface{}) + switch responseMap["reply"].(string) { + case "order_rejected": + return submitOrderResponse, fmt.Errorf("clientOrderID: %v was rejected: %v", o.ClientID, responseMap["reasons"]) + case "order_filled": + orderID := responseMap["order_id"].(float64) + submitOrderResponse.OrderID = strconv.FormatFloat(orderID, 'f', -1, 64) + submitOrderResponse.IsOrderPlaced = true + submitOrderResponse.FullyMatched = true + return submitOrderResponse, nil + case "order_accepted": + orderID := responseMap["order_id"].(float64) + submitOrderResponse.OrderID = strconv.FormatFloat(orderID, 'f', -1, 64) + submitOrderResponse.IsOrderPlaced = true + return submitOrderResponse, nil + } + } + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -502,76 +527,108 @@ func (c *COINUT) ModifyOrder(action *order.Modify) (string, error) { } // CancelOrder cancels an order by its corresponding ID number -func (c *COINUT) CancelOrder(order *order.Cancel) error { - orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) +func (c *COINUT) CancelOrder(o *order.Cancel) error { + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return err + } + orderIDInt, err := strconv.ParseInt(o.OrderID, 10, 64) if err != nil { return err } - if !c.instrumentMap.IsLoaded() { - err = c.SeedInstruments() + currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency( + o.CurrencyPair, + asset.Spot).String(), + ) + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var resp *CancelOrdersResponse + resp, err = c.wsCancelOrder(&WsCancelOrderParameters{ + Currency: o.CurrencyPair, + OrderID: orderIDInt, + }) + if err != nil { + return err + } + if len(resp.Status) >= 1 && resp.Status[0] != "OK" { + return errors.New(c.Name + " - Failed to cancel order " + o.OrderID) + } + } else { + if currencyID == 0 { + return errLookupInstrumentID + } + _, err = c.CancelExistingOrder(currencyID, orderIDInt) if err != nil { return err } } - currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency( - order.CurrencyPair, - asset.Spot).String(), - ) - if currencyID == 0 { - return errLookupInstrumentID - } - _, err = c.CancelExistingOrder(currencyID, orderIDInt) - return err + return nil } // CancelAllOrders cancels all orders associated with a currency pair -func (c *COINUT) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { - // TODO, this is a terrible implementation. Requires DB to improve - // Coinut provides no way of retrieving orders without a currency - // So we need to retrieve all currencies, then retrieve orders for each - // currency then cancel. Advisable to never use this until DB due to - // performance. - cancelAllOrdersResponse := order.CancelAllResponse{ - Status: make(map[string]string), +func (c *COINUT) CancelAllOrders(details *order.Cancel) (order.CancelAllResponse, error) { + var cancelAllOrdersResponse order.CancelAllResponse + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return cancelAllOrdersResponse, err } - - if !c.instrumentMap.IsLoaded() { - err := c.SeedInstruments() + cancelAllOrdersResponse.Status = make(map[string]string) + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + openOrders, err := c.wsGetOpenOrders(details.CurrencyPair.String()) if err != nil { return cancelAllOrdersResponse, err } - } - - var allTheOrders []OrderResponse - ids := c.instrumentMap.GetInstrumentIDs() - for x := range ids { - openOrders, err := c.GetOpenOrders(ids[x]) + var ordersToCancel []WsCancelOrderParameters + for i := range openOrders.Orders { + if openOrders.Orders[i].InstID == c.instrumentMap.LookupID(c.FormatExchangeCurrency(details.CurrencyPair, asset.Spot).String()) { + ordersToCancel = append(ordersToCancel, WsCancelOrderParameters{ + Currency: details.CurrencyPair, + OrderID: openOrders.Orders[i].OrderID, + }) + } + } + resp, err := c.wsCancelOrders(ordersToCancel) if err != nil { return cancelAllOrdersResponse, err } - allTheOrders = append(allTheOrders, openOrders.Orders...) - } - - var allTheOrdersToCancel []CancelOrders - for _, orderToCancel := range allTheOrders { - cancelOrder := CancelOrders{ - InstrumentID: orderToCancel.InstrumentID, - OrderID: orderToCancel.OrderID, + for i := range resp.Results { + if openOrders.Orders[i].Status[0] != "OK" { + cancelAllOrdersResponse.Status[strconv.FormatInt(openOrders.Orders[i].OrderID, 10)] = strings.Join(openOrders.Orders[i].Status, ",") + } } - allTheOrdersToCancel = append(allTheOrdersToCancel, cancelOrder) - } - - if len(allTheOrdersToCancel) > 0 { - resp, err := c.CancelOrders(allTheOrdersToCancel) - if err != nil { - return cancelAllOrdersResponse, err + } else { + var allTheOrders []OrderResponse + ids := c.instrumentMap.GetInstrumentIDs() + for x := range ids { + if ids[x] == c.instrumentMap.LookupID(c.FormatExchangeCurrency(details.CurrencyPair, asset.Spot).String()) { + openOrders, err := c.GetOpenOrders(ids[x]) + if err != nil { + return cancelAllOrdersResponse, err + } + allTheOrders = append(allTheOrders, openOrders.Orders...) + } } - for _, order := range resp.Results { - if order.Status != "OK" { - cancelAllOrdersResponse.Status[strconv.FormatInt(order.OrderID, 10)] = order.Status + var allTheOrdersToCancel []CancelOrders + for i := range allTheOrders { + cancelOrder := CancelOrders{ + InstrumentID: allTheOrders[i].InstrumentID, + OrderID: allTheOrders[i].OrderID, + } + allTheOrdersToCancel = append(allTheOrdersToCancel, cancelOrder) + } + + if len(allTheOrdersToCancel) > 0 { + resp, err := c.CancelOrders(allTheOrdersToCancel) + if err != nil { + return cancelAllOrdersResponse, err + } + + for i := range resp.Results { + if resp.Results[i].Status != "OK" { + cancelAllOrdersResponse.Status[strconv.FormatInt(resp.Results[i].OrderID, 10)] = resp.Results[i].Status + } } } } @@ -623,51 +680,81 @@ func (c *COINUT) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) // GetActiveOrders retrieves any orders that are active/open func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { - if !c.instrumentMap.IsLoaded() { - err := c.SeedInstruments() - if err != nil { - return nil, err - } + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return nil, err } - - var instrumentsToUse []int64 - if len(req.Currencies) > 0 { - for x := range req.Currencies { - currency := c.FormatExchangeCurrency(req.Currencies[x], - asset.Spot).String() - instrumentsToUse = append(instrumentsToUse, - c.instrumentMap.LookupID(currency)) + var orders []order.Detail + var currenciesToCheck []string + if len(req.Currencies) == 0 { + for i := range req.Currencies { + currenciesToCheck = append(currenciesToCheck, c.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String()) } } else { - instrumentsToUse = c.instrumentMap.GetInstrumentIDs() - } - - if len(instrumentsToUse) == 0 { - return nil, errors.New("no instrument IDs to use") - } - - var orders []order.Detail - for x := range instrumentsToUse { - openOrders, err := c.GetOpenOrders(instrumentsToUse[x]) - if err != nil { - return nil, err + for k := range c.instrumentMap.Instruments { + currenciesToCheck = append(currenciesToCheck, k) } - for y := range openOrders.Orders { - curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) - p := currency.NewPairFromFormattedPairs(curr, - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true)) - orderSide := order.Side(strings.ToUpper(openOrders.Orders[y].Side)) - orderDate := time.Unix(openOrders.Orders[y].Timestamp, 0) - orders = append(orders, order.Detail{ - ID: strconv.FormatInt(openOrders.Orders[y].OrderID, 10), - Amount: openOrders.Orders[y].Quantity, - Price: openOrders.Orders[y].Price, - Exchange: c.Name, - OrderSide: orderSide, - OrderDate: orderDate, - CurrencyPair: p, - }) + } + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + for x := range currenciesToCheck { + openOrders, err := c.wsGetOpenOrders(currenciesToCheck[x]) + if err != nil { + return nil, err + } + for i := range openOrders.Orders { + orders = append(orders, order.Detail{ + Exchange: c.Name, + ID: strconv.FormatInt(openOrders.Orders[i].OrderID, 10), + CurrencyPair: c.FormatExchangeCurrency(currency.NewPairFromString(currenciesToCheck[x]), asset.Spot), + OrderSide: order.Side(openOrders.Orders[i].Side), + OrderDate: time.Unix(0, openOrders.Orders[i].Timestamp), + Status: order.Active, + Price: openOrders.Orders[i].Price, + Amount: openOrders.Orders[i].Qty, + ExecutedAmount: openOrders.Orders[i].Qty - openOrders.Orders[i].OpenQty, + RemainingAmount: openOrders.Orders[i].OpenQty, + }) + } + } + } else { + var instrumentsToUse []int64 + if len(req.Currencies) > 0 { + for x := range req.Currencies { + curr := c.FormatExchangeCurrency(req.Currencies[x], + asset.Spot).String() + instrumentsToUse = append(instrumentsToUse, + c.instrumentMap.LookupID(curr)) + } + } else { + instrumentsToUse = c.instrumentMap.GetInstrumentIDs() + } + + if len(instrumentsToUse) == 0 { + return nil, errors.New("no instrument IDs to use") + } + + for x := range instrumentsToUse { + openOrders, err := c.GetOpenOrders(instrumentsToUse[x]) + if err != nil { + return nil, err + } + for y := range openOrders.Orders { + curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) + p := currency.NewPairFromFormattedPairs(curr, + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true)) + orderSide := order.Side(strings.ToUpper(openOrders.Orders[y].Side)) + orderDate := time.Unix(openOrders.Orders[y].Timestamp, 0) + orders = append(orders, order.Detail{ + ID: strconv.FormatInt(openOrders.Orders[y].OrderID, 10), + Amount: openOrders.Orders[y].Quantity, + Price: openOrders.Orders[y].Price, + Exchange: c.Name, + OrderSide: orderSide, + OrderDate: orderDate, + CurrencyPair: p, + }) + } } } @@ -679,51 +766,78 @@ func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e // GetOrderHistory retrieves account order information // Can Limit response to specific order status func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { - if !c.instrumentMap.IsLoaded() { - err := c.SeedInstruments() - if err != nil { - return nil, err - } + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return nil, err } - - var instrumentsToUse []int64 - if len(req.Currencies) > 0 { - for x := range req.Currencies { - currency := c.FormatExchangeCurrency(req.Currencies[x], - asset.Spot).String() - instrumentsToUse = append(instrumentsToUse, - c.instrumentMap.LookupID(currency)) + var allOrders []order.Detail + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + for i := range req.Currencies { + for j := int64(0); ; j += 100 { + trades, err := c.wsGetTradeHistory(req.Currencies[i], j, 100) + if err != nil { + return allOrders, err + } + for x := range trades.Trades { + curr := c.instrumentMap.LookupInstrument(trades.Trades[x].InstID) + allOrders = append(allOrders, order.Detail{ + Exchange: c.Name, + ID: strconv.FormatInt(trades.Trades[x].OrderID, 10), + CurrencyPair: currency.NewPairFromString(curr), + OrderSide: order.Side(trades.Trades[x].Side), + OrderDate: time.Unix(0, trades.Trades[x].Timestamp), + Status: order.Filled, + Price: trades.Trades[x].Price, + Amount: trades.Trades[x].Qty, + ExecutedAmount: trades.Trades[x].Qty, + RemainingAmount: trades.Trades[x].OpenQty, + }) + } + if len(trades.Trades) < 100 { + break + } + } } } else { - instrumentsToUse = c.instrumentMap.GetInstrumentIDs() - } - - if len(instrumentsToUse) == 0 { - return nil, errors.New("no instrument IDs to use") - } - - var allOrders []order.Detail - for x := range instrumentsToUse { - orders, err := c.GetTradeHistory(instrumentsToUse[x], -1, -1) - if err != nil { - return nil, err + var instrumentsToUse []int64 + if len(req.Currencies) > 0 { + for x := range req.Currencies { + curr := c.FormatExchangeCurrency(req.Currencies[x], + asset.Spot).String() + instrumentID := c.instrumentMap.LookupID(curr) + if instrumentID > 0 { + instrumentsToUse = append(instrumentsToUse, instrumentID) + } + } + } else { + instrumentsToUse = c.instrumentMap.GetInstrumentIDs() } - for y := range orders.Trades { - curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) - p := currency.NewPairFromFormattedPairs(curr, - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true)) - orderSide := order.Side(strings.ToUpper(orders.Trades[y].Order.Side)) - orderDate := time.Unix(orders.Trades[y].Order.Timestamp, 0) - allOrders = append(allOrders, order.Detail{ - ID: strconv.FormatInt(orders.Trades[y].Order.OrderID, 10), - Amount: orders.Trades[y].Order.Quantity, - Price: orders.Trades[y].Order.Price, - Exchange: c.Name, - OrderSide: orderSide, - OrderDate: orderDate, - CurrencyPair: p, - }) + + if len(instrumentsToUse) == 0 { + return nil, errors.New("no instrument IDs to use") + } + for x := range instrumentsToUse { + orders, err := c.GetTradeHistory(instrumentsToUse[x], -1, -1) + if err != nil { + return nil, err + } + for y := range orders.Trades { + curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) + p := currency.NewPairFromFormattedPairs(curr, + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true)) + orderSide := order.Side(strings.ToUpper(orders.Trades[y].Order.Side)) + orderDate := time.Unix(orders.Trades[y].Order.Timestamp, 0) + allOrders = append(allOrders, order.Detail{ + ID: strconv.FormatInt(orders.Trades[y].Order.OrderID, 10), + Amount: orders.Trades[y].Order.Quantity, + Price: orders.Trades[y].Order.Price, + Exchange: c.Name, + OrderSide: orderSide, + OrderDate: orderDate, + CurrencyPair: p, + }) + } } } @@ -755,3 +869,20 @@ func (c *COINUT) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, e func (c *COINUT) AuthenticateWebsocket() error { return c.wsAuthenticate() } + +func (c *COINUT) loadInstrumentsIfNotLoaded() error { + if !c.instrumentMap.IsLoaded() { + if c.Websocket.IsConnected() { + _, err := c.WsGetInstruments() + if err != nil { + return err + } + } else { + err := c.SeedInstruments() + if err != nil { + return err + } + } + } + return nil +} diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index 8b5e8839..804f7921 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -14,27 +14,8 @@ import ( const ( RestAuthentication uint8 = 0 WebsocketAuthentication uint8 = 1 -) - -// FeeType custom type for calculating fees based on method -type FeeType uint8 - -// Const declarations for fee types -const ( - BankFee FeeType = iota - InternationalBankDepositFee - InternationalBankWithdrawalFee - CryptocurrencyTradeFee - CyptocurrencyDepositFee - CryptocurrencyWithdrawalFee - OfflineTradeFee -) - -// InternationalBankTransactionType custom type for calculating fees based on fiat transaction types -type InternationalBankTransactionType uint8 - -// Const declarations for international transaction types -const ( + // Repeated exchange strings + // FeeType custom type for calculating fees based on method WireTransfer InternationalBankTransactionType = iota PerfectMoney Neteller @@ -53,26 +34,15 @@ const ( WesternUnion MoneyGram Contact -) - -// FeeBuilder is the type which holds all parameters required to calculate a fee -// for an exchange -type FeeBuilder struct { - FeeType FeeType - // Used for calculating crypto trading fees, deposits & withdrawals - Pair currency.Pair - IsMaker bool - // Fiat currency used for bank deposits & withdrawals - FiatCurrency currency.Code - BankTransactionType InternationalBankTransactionType - // Used to multiply for fee calculations - PurchasePrice float64 - Amount float64 -} - -// Definitions for each type of withdrawal method for a given exchange -const ( - // No withdraw + // Const declarations for fee types + BankFee FeeType = iota + InternationalBankDepositFee + InternationalBankWithdrawalFee + CryptocurrencyTradeFee + CyptocurrencyDepositFee + CryptocurrencyWithdrawalFee + OfflineTradeFee + // Definitions for each type of withdrawal method for a given exchange NoAPIWithdrawalMethods uint32 = 0 NoAPIWithdrawalMethodsText string = "NONE, WEBSITE ONLY" AutoWithdrawCrypto uint32 = (1 << 0) @@ -113,10 +83,29 @@ const ( WithdrawFiatViaWebsiteOnlyText string = "WITHDRAW FIAT VIA WEBSITE ONLY" NoFiatWithdrawals uint32 = (1 << 18) NoFiatWithdrawalsText string = "NO FIAT WITHDRAWAL" - - UnknownWithdrawalTypeText string = "UNKNOWN" + UnknownWithdrawalTypeText string = "UNKNOWN" ) +type FeeType uint8 + +// InternationalBankTransactionType custom type for calculating fees based on fiat transaction types +type InternationalBankTransactionType uint8 + +// FeeBuilder is the type which holds all parameters required to calculate a fee +// for an exchange +type FeeBuilder struct { + FeeType FeeType + // Used for calculating crypto trading fees, deposits & withdrawals + Pair currency.Pair + IsMaker bool + // Fiat currency used for bank deposits & withdrawals + FiatCurrency currency.Code + BankTransactionType InternationalBankTransactionType + // Used to multiply for fee calculations + PurchasePrice float64 + Amount float64 +} + // AccountInfo is a Generic type to hold each exchange's holdings in // all enabled currencies type AccountInfo struct { diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index 0d009164..2a5fa1d9 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -2,6 +2,7 @@ package exmo import ( "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -21,11 +22,8 @@ var ( e EXMO ) -func TestDefault(t *testing.T) { +func TestMain(m *testing.M) { e.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { @@ -33,17 +31,19 @@ func TestSetup(t *testing.T) { } exmoConf, err := cfg.GetExchangeConfig("EXMO") if err != nil { - t.Error("Exmo Setup() init error") + log.Fatal("Exmo Setup() init error") } err = e.Setup(exmoConf) if err != nil { - t.Fatal("Exmo setup error", err) + log.Fatal("Exmo setup error", err) } e.API.AuthenticatedSupport = true e.API.Credentials.Key = APIKey e.API.Credentials.Secret = APISecret + + os.Exit(m.Run()) } func TestGetTrades(t *testing.T) { @@ -88,10 +88,9 @@ func TestGetCurrency(t *testing.T) { func TestGetUserInfo(t *testing.T) { t.Parallel() - if APIKey == "" || APISecret == "" { + if !areTestAPIKeysSet() { t.Skip() } - TestSetup(t) _, err := e.GetUserInfo() if err != nil { t.Errorf("Err: %s", err) @@ -100,10 +99,9 @@ func TestGetUserInfo(t *testing.T) { func TestGetRequiredAmount(t *testing.T) { t.Parallel() - if APIKey == "" || APISecret == "" { + if !areTestAPIKeysSet() { t.Skip() } - TestSetup(t) _, err := e.GetRequiredAmount("BTC_USD", 100) if err != nil { t.Errorf("Err: %s", err) @@ -125,7 +123,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() e.GetFeeByType(feeBuilder) - if APIKey == "" || APISecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -137,8 +135,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - e.SetDefaults() - TestSetup(t) t.Parallel() var feeBuilder = setFeeBuilder() @@ -255,20 +251,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - e.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := e.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - e.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -282,9 +272,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - e.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -307,8 +294,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - e.SetDefaults() - TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -334,14 +319,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - e.SetDefaults() - TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -359,9 +341,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - e.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -389,6 +368,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := e.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -396,8 +378,10 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - e.SetDefaults() - TestSetup(t) + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -407,10 +391,6 @@ func TestWithdraw(t *testing.T) { Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - _, err := e.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") @@ -421,9 +401,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - e.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -436,9 +413,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - e.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index e228fc7a..374cbf81 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -238,10 +238,10 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook if err != nil { return orderBook, err } - - for _, x := range e.GetEnabledPairs(assetType) { - currency := e.FormatExchangeCurrency(x, assetType) - data, ok := result[currency.String()] + enabledPairs := e.GetEnabledPairs(assetType) + for i := range enabledPairs { + curr := e.FormatExchangeCurrency(enabledPairs[i], assetType) + data, ok := result[curr.String()] if !ok { continue } @@ -266,7 +266,7 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook } orderBook.Bids = obItems - orderBook.Pair = x + orderBook.Pair = enabledPairs[i] orderBook.ExchangeName = e.Name orderBook.AssetType = assetType @@ -344,15 +344,18 @@ func (e *EXMO) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { oT, s.Price, s.Amount) + if err != nil { + return submitOrderResponse, err + } if response > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true + submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -382,10 +385,10 @@ func (e *EXMO) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) return cancelAllOrdersResponse, err } - for _, order := range openOrders { - err = e.CancelExistingOrder(order.OrderID) + for i := range openOrders { + err = e.CancelExistingOrder(openOrders[i].OrderID) if err != nil { - cancelAllOrdersResponse.Status[strconv.FormatInt(order.OrderID, 10)] = err.Error() + cancelAllOrdersResponse.Status[strconv.FormatInt(openOrders[i].OrderID, 10)] = err.Error() } } @@ -486,13 +489,13 @@ func (e *EXMO) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, err } var allTrades []UserTrades - for _, currency := range req.Currencies { - resp, err := e.GetUserTrades(e.FormatExchangeCurrency(currency, asset.Spot).String(), "", "10000") + for i := range req.Currencies { + resp, err := e.GetUserTrades(e.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String(), "", "10000") if err != nil { return nil, err } - for _, order := range resp { - allTrades = append(allTrades, order...) + for j := range resp { + allTrades = append(allTrades, resp[j]...) } } diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 7437e6da..cfa6cf2f 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -1,7 +1,9 @@ package gateio import ( + "log" "net/http" + "os" "testing" "github.com/gorilla/websocket" @@ -25,29 +27,28 @@ const ( var g Gateio var wsSetupRan bool -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { g.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("GateIO load config error", err) + log.Fatal("GateIO load config error", err) } - gateioConfig, err := cfg.GetExchangeConfig("GateIO") + gConf, err := cfg.GetExchangeConfig("GateIO") if err != nil { - t.Error("GateIO Setup() init error") + log.Fatal("GateIO Setup() init error") } - gateioConfig.API.AuthenticatedSupport = true - gateioConfig.API.AuthenticatedWebsocketSupport = true - gateioConfig.API.Credentials.Key = apiKey - gateioConfig.API.Credentials.Secret = apiSecret + gConf.API.AuthenticatedSupport = true + gConf.API.AuthenticatedWebsocketSupport = true + gConf.API.Credentials.Key = apiKey + gConf.API.Credentials.Secret = apiSecret - err = g.Setup(gateioConfig) + err = g.Setup(gConf) if err != nil { - t.Fatal("GateIO setup error", err) + log.Fatal("GateIO setup error", err) } + + os.Exit(m.Run()) } func TestGetSymbols(t *testing.T) { @@ -100,7 +101,7 @@ func TestCancelExistingOrder(t *testing.T) { func TestGetBalances(t *testing.T) { t.Parallel() - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { t.Skip() } @@ -173,7 +174,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() g.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -185,9 +186,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - g.SetDefaults() - TestSetup(t) - var feeBuilder = setFeeBuilder() if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic @@ -265,20 +263,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - g.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := g.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - g.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -292,9 +284,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - g.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -318,9 +307,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip() } @@ -346,15 +332,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip() } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -372,15 +354,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip() } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -417,6 +395,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := g.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -424,8 +405,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - g.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -449,9 +428,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -464,9 +440,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -492,9 +465,6 @@ func TestGetDepositAddress(t *testing.T) { } } func TestGetOrderInfo(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if !areTestAPIKeysSet() { t.Skip("no API keys set skipping test") } @@ -509,8 +479,6 @@ func TestGetOrderInfo(t *testing.T) { // TestWsGetBalance dials websocket, sends balance request. func TestWsGetBalance(t *testing.T) { - g.SetDefaults() - TestSetup(t) if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -541,12 +509,14 @@ func TestWsGetBalance(t *testing.T) { if err != nil { t.Error(err) } + _, err = g.wsGetBalance([]string{}) + if err != nil { + t.Error(err) + } } // TestWsGetOrderInfo dials websocket, sends order info request. func TestWsGetOrderInfo(t *testing.T) { - g.SetDefaults() - TestSetup(t) if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -573,7 +543,7 @@ func TestWsGetOrderInfo(t *testing.T) { if resp.Result.Status != "success" { t.Fatal("Unsuccessful login") } - _, err = g.wsGetOrderInfo("EOS_USDT", 0, 10) + _, err = g.wsGetOrderInfo("EOS_USDT", 0, 1000) if err != nil { t.Error(err) } @@ -583,8 +553,6 @@ func setupWSTestAuth(t *testing.T) { if wsSetupRan { return } - g.SetDefaults() - TestSetup(t) if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport { t.Skip(wshandler.WebsocketNotEnabled) } diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index 8336d8e5..283cfbf4 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -445,19 +445,19 @@ type WebSocketOrderQueryResult struct { // WebSocketOrderQueryRecords contains order information from a order.query websocket request type WebSocketOrderQueryRecords struct { - ID int `json:"id"` + ID int64 `json:"id"` Market string `json:"market"` - User int `json:"user"` + User int64 `json:"user"` Ctime float64 `json:"ctime"` Mtime float64 `json:"mtime"` - Price string `json:"price"` - Amount string `json:"amount"` - Left string `json:"left"` - DealFee string `json:"dealFee"` - OrderType int `json:"orderType"` - Type int `json:"type"` - FilledAmount string `json:"filledAmount"` - FilledTotal string `json:"filledTotal"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + Left float64 `json:"left,string"` + DealFee float64 `json:"dealFee,string"` + OrderType int64 `json:"orderType"` + Type int64 `json:"type"` + FilledAmount float64 `json:"filledAmount,string"` + FilledTotal float64 `json:"filledTotal,string"` } // WebsocketAuthenticationResponse contains the result of a login request @@ -473,14 +473,14 @@ type WebsocketAuthenticationResponse struct { type wsGetBalanceRequest struct { ID int64 `json:"id"` Method string `json:"method"` - Params []string `json:"params,omitempty"` + Params []string `json:"params"` } // WsGetBalanceResponse stores WS GetBalance response type WsGetBalanceResponse struct { - Error interface{} `json:"error"` - Result map[currency.Code]WsGetBalanceResponseData `json:"result,omitempty"` - ID int64 `json:"id"` + Error interface{} `json:"error"` + Result map[string]WsGetBalanceResponseData `json:"result"` + ID int64 `json:"id"` } // WsGetBalanceResponseData contains currency data diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 04545179..8959f1f1 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -40,6 +40,7 @@ func (g *Gateio) WsConnect() error { _, err = g.wsServerSignIn() if err != nil { log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", g.Name, err) + g.Websocket.SetCanUseAuthenticatedEndpoints(false) } g.GenerateAuthenticatedSubscriptions() g.GenerateDefaultSubscriptions() @@ -416,7 +417,7 @@ func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) (*WebSocketOrd if !g.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authorised to get order info", g.Name) } - order := WebsocketRequest{ + ord := WebsocketRequest{ ID: g.WebsocketConn.GenerateMessageID(true), Method: "order.query", Params: []interface{}{ @@ -425,7 +426,7 @@ func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) (*WebSocketOrd limit, }, } - resp, err := g.WebsocketConn.SendMessageReturnResponse(order.ID, order) + resp, err := g.WebsocketConn.SendMessageReturnResponse(ord.ID, ord) if err != nil { return nil, err } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 91970c94..5d0d8384 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -9,6 +9,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -99,6 +100,8 @@ func (g *Gateio) SetDefaults() { Unsubscribe: true, AuthenticatedEndpoints: true, MessageCorrelation: true, + GetOrder: true, + AccountBalance: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.NoFiatWithdrawals, @@ -269,9 +272,9 @@ func (g *Gateio) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderboo // UpdateOrderbook updates and returns the orderbook for a currency pair func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - currency := g.FormatExchangeCurrency(p, assetType).String() + curr := g.FormatExchangeCurrency(p, assetType).String() - orderbookNew, err := g.GetOrderbook(currency) + orderbookNew, err := g.GetOrderbook(curr) if err != nil { return orderBook, err } @@ -306,61 +309,78 @@ func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo // ZB exchange func (g *Gateio) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - - balance, err := g.GetBalances() - if err != nil { - return info, err - } - var balances []exchange.AccountCurrencyInfo - switch l := balance.Locked.(type) { - case map[string]interface{}: - for x := range l { - lockedF, err := strconv.ParseFloat(l[x].(string), 64) - if err != nil { - return info, err - } - - balances = append(balances, exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(x), - Hold: lockedF, + if g.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + resp, err := g.wsGetBalance([]string{}) + if err != nil { + return info, err + } + var currData []exchange.AccountCurrencyInfo + for k := range resp.Result { + currData = append(currData, exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(k), + TotalValue: resp.Result[k].Available + resp.Result[k].Freeze, + Hold: resp.Result[k].Freeze, }) } - default: - break - } + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: currData, + }) + } else { + balance, err := g.GetBalances() + if err != nil { + return info, err + } - switch v := balance.Available.(type) { - case map[string]interface{}: - for x := range v { - availAmount, err := strconv.ParseFloat(v[x].(string), 64) - if err != nil { - return info, err - } - - var updated bool - for i := range balances { - if balances[i].CurrencyName == currency.NewCode(x) { - balances[i].TotalValue = balances[i].Hold + availAmount - updated = true - break + switch l := balance.Locked.(type) { + case map[string]interface{}: + for x := range l { + lockedF, err := strconv.ParseFloat(l[x].(string), 64) + if err != nil { + return info, err } - } - if !updated { + balances = append(balances, exchange.AccountCurrencyInfo{ CurrencyName: currency.NewCode(x), - TotalValue: availAmount, + Hold: lockedF, }) } + default: + break } - default: - break - } - info.Accounts = append(info.Accounts, exchange.Account{ - Currencies: balances, - }) + switch v := balance.Available.(type) { + case map[string]interface{}: + for x := range v { + availAmount, err := strconv.ParseFloat(v[x].(string), 64) + if err != nil { + return info, err + } + + var updated bool + for i := range balances { + if balances[i].CurrencyName == currency.NewCode(x) { + balances[i].TotalValue = balances[i].Hold + availAmount + updated = true + break + } + } + if !updated { + balances = append(balances, exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(x), + TotalValue: availAmount, + }) + } + } + default: + break + } + + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: balances, + }) + } info.Exchange = g.Name @@ -401,15 +421,18 @@ func (g *Gateio) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } response, err := g.SpotNewOrder(spotNewOrderRequestParams) + if err != nil { + return submitOrderResponse, err + } if response.OrderNumber > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true + if response.LeftAmount == 0 { + submitOrderResponse.FullyMatched = true } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -457,7 +480,6 @@ func (g *Gateio) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, erro // GetOrderInfo returns information on a current open order func (g *Gateio) GetOrderInfo(orderID string) (order.Detail, error) { var orderDetail order.Detail - orders, err := g.GetOpenOrders("") if err != nil { return orderDetail, errors.New("failed to get open orders") @@ -534,40 +556,79 @@ func (g *Gateio) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) // GetActiveOrders retrieves any orders that are active/open func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { + var orders []order.Detail var currPair string if len(req.Currencies) == 1 { currPair = req.Currencies[0].String() } + if g.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + for i := 0; ; i += 100 { + resp, err := g.wsGetOrderInfo(req.OrderType.String(), i, 100) + if err != nil { + return orders, err + } - resp, err := g.GetOpenOrders(currPair) - if err != nil { - return nil, err - } - - var orders []order.Detail - for i := range resp.Orders { - if resp.Orders[i].Status != "open" { - continue + for j := range resp.WebSocketOrderQueryRecords { + orderSide := order.Buy + if resp.WebSocketOrderQueryRecords[j].Type == 1 { + orderSide = order.Sell + } + orderType := order.Market + if resp.WebSocketOrderQueryRecords[j].OrderType == 1 { + orderType = order.Limit + } + firstNum, decNum, err := convert.SplitFloatDecimals(resp.WebSocketOrderQueryRecords[j].Ctime) + if err != nil { + return orders, err + } + orderDate := time.Unix(firstNum, decNum) + orders = append(orders, order.Detail{ + Exchange: g.Name, + AccountID: strconv.FormatInt(resp.WebSocketOrderQueryRecords[j].User, 10), + ID: strconv.FormatInt(resp.WebSocketOrderQueryRecords[j].ID, 10), + CurrencyPair: currency.NewPairFromString(resp.WebSocketOrderQueryRecords[j].Market), + OrderSide: orderSide, + OrderType: orderType, + OrderDate: orderDate, + Price: resp.WebSocketOrderQueryRecords[j].Price, + Amount: resp.WebSocketOrderQueryRecords[j].Amount, + ExecutedAmount: resp.WebSocketOrderQueryRecords[j].FilledAmount, + RemainingAmount: resp.WebSocketOrderQueryRecords[j].Left, + Fee: resp.WebSocketOrderQueryRecords[j].DealFee, + }) + } + if len(resp.WebSocketOrderQueryRecords) < 100 { + break + } + } + } else { + resp, err := g.GetOpenOrders(currPair) + if err != nil { + return nil, err } - symbol := currency.NewPairDelimiter(resp.Orders[i].CurrencyPair, - g.GetPairFormat(asset.Spot, false).Delimiter) - side := order.Side(strings.ToUpper(resp.Orders[i].Type)) - orderDate := time.Unix(resp.Orders[i].Timestamp, 0) + for i := range resp.Orders { + if resp.Orders[i].Status != "open" { + continue + } - orders = append(orders, order.Detail{ - ID: resp.Orders[i].OrderNumber, - Amount: resp.Orders[i].Amount, - Price: resp.Orders[i].Rate, - RemainingAmount: resp.Orders[i].FilledAmount, - OrderDate: orderDate, - OrderSide: side, - Exchange: g.Name, - CurrencyPair: symbol, - Status: order.Status(resp.Orders[i].Status), - }) + symbol := currency.NewPairDelimiter(resp.Orders[i].CurrencyPair, + g.GetPairFormat(asset.Spot, false).Delimiter) + side := order.Side(strings.ToUpper(resp.Orders[i].Type)) + orderDate := time.Unix(resp.Orders[i].Timestamp, 0) + orders = append(orders, order.Detail{ + ID: resp.Orders[i].OrderNumber, + Amount: resp.Orders[i].Amount, + Price: resp.Orders[i].Rate, + RemainingAmount: resp.Orders[i].FilledAmount, + OrderDate: orderDate, + OrderSide: side, + Exchange: g.Name, + CurrencyPair: symbol, + Status: order.Status(resp.Orders[i].Status), + }) + } } - order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil @@ -577,8 +638,8 @@ func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e // Can Limit response to specific order status func (g *Gateio) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var trades []TradesResponse - for _, currency := range req.Currencies { - resp, err := g.GetTradeHistory(currency.String()) + for i := range req.Currencies { + resp, err := g.GetTradeHistory(req.Currencies[i].String()) if err != nil { return nil, err } diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index dec36bab..02704567 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -336,9 +336,7 @@ func TestFormatWithdrawPermissions(t *testing.T) { exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := g.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, @@ -425,7 +423,6 @@ func TestCancelExchangeOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "266029865", } @@ -448,7 +445,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 9815ab36..36bf2cb2 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -32,7 +32,7 @@ const ( ) // Instantiates a communications channel between websocket connections -var comms = make(chan ReadData, 1) +var comms = make(chan ReadData) var responseMaxLimit time.Duration var responseCheckTimeout time.Duration @@ -62,13 +62,13 @@ func (g *Gemini) WsConnect() error { // WsSubscribe subscribes to the full websocket suite on gemini exchange func (g *Gemini) WsSubscribe(dialer *websocket.Dialer) error { enabledCurrencies := g.GetEnabledPairs(asset.Spot) - for i, c := range enabledCurrencies { + for i := range enabledCurrencies { val := url.Values{} val.Set("heartbeat", "true") endpoint := fmt.Sprintf("%s%s/%s?%s", g.API.Endpoints.WebsocketURL, geminiWsMarketData, - c.String(), + enabledCurrencies[i].String(), val.Encode()) connection := &wshandler.WebsocketConnection{ ExchangeName: g.Name, @@ -82,7 +82,7 @@ func (g *Gemini) WsSubscribe(dialer *websocket.Dialer) error { return fmt.Errorf("%v Websocket connection %v error. Error %v", g.Name, endpoint, err) } - go g.WsReadData(connection, c) + go g.WsReadData(connection, enabledCurrencies[i]) if len(enabledCurrencies)-1 == i { return nil } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 16e66d45..d730b463 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -333,13 +333,16 @@ func (g *Gemini) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.Price, s.OrderSide.String(), "exchange limit") + if err != nil { + return submitOrderResponse, err + } if response > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + + submitOrderResponse.IsOrderPlaced = true + + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -369,8 +372,8 @@ func (g *Gemini) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, erro return cancelAllOrdersResponse, err } - for _, order := range resp.Details.CancelRejects { - cancelAllOrdersResponse.Status[order] = "Could not cancel order" + for i := range resp.Details.CancelRejects { + cancelAllOrdersResponse.Status[resp.Details.CancelRejects[i]] = "Could not cancel order" } return cancelAllOrdersResponse, nil diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index f6145843..63649393 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -315,7 +315,7 @@ func (h *HitBTC) GetOpenOrders(currency string) ([]OrderHistoryResponse, error) // PlaceOrder places an order on the exchange func (h *HitBTC) PlaceOrder(currency string, rate, amount float64, orderType, side string) (OrderResponse, error) { - result := OrderResponse{} + var result OrderResponse values := url.Values{} values.Set("symbol", currency) diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index 976de532..208d5fdc 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -1,7 +1,9 @@ package hitbtc import ( + "log" "net/http" + "os" "testing" "time" @@ -26,19 +28,16 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { h.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("HitBTC load config error", err) + log.Fatal("HitBTC load config error", err) } hitbtcConfig, err := cfg.GetExchangeConfig("HitBTC") if err != nil { - t.Error("HitBTC Setup() init error") + log.Fatal("HitBTC Setup() init error") } hitbtcConfig.API.AuthenticatedSupport = true hitbtcConfig.API.AuthenticatedWebsocketSupport = true @@ -47,8 +46,10 @@ func TestSetup(t *testing.T) { err = h.Setup(hitbtcConfig) if err != nil { - t.Fatal("HitBTC setup error", err) + log.Fatal("HitBTC setup error", err) } + + os.Exit(m.Run()) } func TestGetOrderbook(t *testing.T) { @@ -94,7 +95,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() h.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -106,9 +107,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestUpdateTicker(t *testing.T) { - h.SetDefaults() - TestSetup(t) - h.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTC-USD", "XRP-USD"}), true) _, err := h.UpdateTicker(currency.NewPair(currency.BTC, currency.USD), asset.Spot) if err != nil { @@ -136,9 +134,6 @@ func TestGetSingularTicker(t *testing.T) { } func TestGetFee(t *testing.T) { - h.SetDefaults() - TestSetup(t) - var feeBuilder = setFeeBuilder() if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic @@ -219,20 +214,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - h.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := h.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - h.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.ETH, currency.BTC)}, @@ -247,9 +236,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - h.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.ETH, currency.BTC)}, @@ -270,9 +256,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -297,15 +280,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -323,15 +302,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -354,6 +329,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := h.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -361,8 +339,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - h.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -386,9 +362,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -401,9 +374,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -432,8 +402,6 @@ func setupWsAuth(t *testing.T) { if wsSetupRan { return } - TestSetDefaults(t) - TestSetup(t) if !h.Websocket.IsEnabled() && !h.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index e77522ff..aa24a6dc 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "time" @@ -395,7 +396,7 @@ func (h *HitBTC) wsLogin() error { return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", h.Name) } h.Websocket.SetCanUseAuthenticatedEndpoints(true) - nonce := fmt.Sprintf("%v", time.Now().Unix()) + nonce := strconv.FormatInt(time.Now().Unix(), 10) hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(nonce), []byte(h.API.Credentials.Secret)) request := WsLoginRequest{ Method: "login", @@ -483,7 +484,7 @@ func (h *HitBTC) wsReplaceOrder(clientOrderID string, quantity, price float64) ( Method: "cancelReplaceOrder", Params: WsReplaceOrderRequestData{ ClientOrderID: clientOrderID, - RequestClientID: fmt.Sprintf("%v", time.Now().Unix()), + RequestClientID: strconv.FormatInt(time.Now().Unix(), 10), Quantity: quantity, Price: price, }, diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index bd823364..c1cc92b6 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -373,24 +373,42 @@ func (h *HitBTC) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex } // SubmitOrder submits a new order -func (h *HitBTC) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { +func (h *HitBTC) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - if err := s.Validate(); err != nil { + err := o.Validate() + if err != nil { return submitOrderResponse, err } + if h.Websocket.IsConnected() && h.Websocket.CanUseAuthenticatedEndpoints() { + var response *WsSubmitOrderSuccessResponse + response, err = h.wsPlaceOrder(o.Pair, o.OrderSide.String(), o.Amount, o.Price) + if err != nil { + return submitOrderResponse, err + } + submitOrderResponse.OrderID = strconv.FormatInt(response.ID, 10) + if response.Result.CumQuantity == o.Amount { + submitOrderResponse.FullyMatched = true + } + } else { + var response OrderResponse + response, err = h.PlaceOrder(o.Pair.String(), + o.Price, + o.Amount, + strings.ToLower(o.OrderType.String()), + strings.ToLower(o.OrderSide.String())) + if err != nil { + return submitOrderResponse, err + } + if response.OrderNumber > 0 { + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) + } + if o.OrderType == order.Market { + submitOrderResponse.FullyMatched = true + } + } + submitOrderResponse.IsOrderPlaced = true - response, err := h.PlaceOrder(s.Pair.String(), - s.Price, - s.Amount, - s.OrderType.Lower(), - s.OrderSide.Lower()) - if response.OrderNumber > 0 { - submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) - } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index f01482ea..22121506 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -44,7 +44,7 @@ const ( huobiOrderCancel = "order/orders/%s/submitcancel" huobiOrderCancelBatch = "order/orders/batchcancel" huobiBatchCancelOpenOrders = "order/orders/batchCancelOpenOrders" - huobiGetOrder = "order/orders/%s" + huobiGetOrder = "order/orders/getClientOrder" huobiGetOrderMatch = "order/orders/%s/matchresults" huobiGetOrders = "order/orders" huobiGetOpenOrders = "order/openOrders" @@ -443,8 +443,9 @@ func (h *HUOBI) GetOrder(orderID int64) (OrderInfo, error) { } var result response - endpoint := fmt.Sprintf(huobiGetOrder, strconv.FormatInt(orderID, 10)) - err := h.SendAuthenticatedHTTPRequest(http.MethodGet, endpoint, url.Values{}, nil, &result) + urlVal := url.Values{} + urlVal.Set("clientOrderId", strconv.FormatInt(orderID, 10)) + err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOrder, urlVal, nil, &result) if result.ErrorMessage != "" { return result.Order, errors.New(result.ErrorMessage) @@ -514,7 +515,7 @@ func (h *HUOBI) GetOrders(symbol, types, start, end, states, from, direct, size } // GetOpenOrders returns a list of orders -func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int) ([]OrderInfo, error) { +func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int64) ([]OrderInfo, error) { type response struct { Response Orders []OrderInfo `json:"data"` @@ -526,7 +527,7 @@ func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int) ([]Order if len(side) > 0 { vals.Set("side", side) } - vals.Set("size", fmt.Sprintf("%v", size)) + vals.Set("size", strconv.FormatInt(size, 10)) var result response err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOpenOrders, vals, nil, &result) @@ -855,8 +856,7 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url values.Set("PrivateSignature", crypto.Base64Encode(privSig)) } - urlPath := fmt.Sprintf("%s%s", common.EncodeURLValues(h.API.Endpoints.URL, values), - endpoint) + urlPath := h.API.Endpoints.URL + common.EncodeURLValues(endpoint, values) var body []byte diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 91079f85..714af623 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -7,6 +7,7 @@ import ( "encoding/pem" "io/ioutil" "log" + "os" "strconv" "strings" "testing" @@ -33,11 +34,8 @@ const ( var h HUOBI var wsSetupRan bool -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { h.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { @@ -45,7 +43,7 @@ func TestSetup(t *testing.T) { } hConfig, err := cfg.GetExchangeConfig("Huobi") if err != nil { - t.Error("Huobi Setup() init error") + log.Fatal("Huobi Setup() init error") } hConfig.API.AuthenticatedSupport = true hConfig.API.AuthenticatedWebsocketSupport = true @@ -54,16 +52,16 @@ func TestSetup(t *testing.T) { err = h.Setup(hConfig) if err != nil { - t.Fatal("Huobi setup error", err) + log.Fatal("Huobi setup error", err) } + + os.Exit(m.Run()) } func setupWsTests(t *testing.T) { if wsSetupRan { return } - TestSetDefaults(t) - TestSetup(t) if !h.Websocket.IsEnabled() && !h.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -195,8 +193,7 @@ func TestGetTimestamp(t *testing.T) { func TestGetAccounts(t *testing.T) { t.Parallel() - - if !h.ValidateAPICredentials() { + if !h.ValidateAPICredentials() || !canManipulateRealOrders { t.Skip() } @@ -208,8 +205,7 @@ func TestGetAccounts(t *testing.T) { func TestGetAccountBalance(t *testing.T) { t.Parallel() - - if !h.ValidateAPICredentials() { + if !h.ValidateAPICredentials() || !canManipulateRealOrders { t.Skip() } @@ -227,7 +223,6 @@ func TestGetAccountBalance(t *testing.T) { func TestGetAggregatedBalance(t *testing.T) { t.Parallel() - if !h.ValidateAPICredentials() { t.Skip() } @@ -240,8 +235,7 @@ func TestGetAggregatedBalance(t *testing.T) { func TestSpotNewOrder(t *testing.T) { t.Parallel() - - if !h.ValidateAPICredentials() { + if !h.ValidateAPICredentials() || !canManipulateRealOrders { t.Skip() } @@ -261,7 +255,9 @@ func TestSpotNewOrder(t *testing.T) { func TestCancelExistingOrder(t *testing.T) { t.Parallel() - + if !h.ValidateAPICredentials() || !canManipulateRealOrders { + t.Skip() + } _, err := h.CancelExistingOrder(1337) if err == nil { t.Error("Huobi TestCancelExistingOrder Expected error") @@ -270,16 +266,17 @@ func TestCancelExistingOrder(t *testing.T) { func TestGetOrder(t *testing.T) { t.Parallel() - + if !h.ValidateAPICredentials() || !canManipulateRealOrders { + t.Skip() + } _, err := h.GetOrder(1337) - if err == nil { - t.Error("Huobi TestCancelOrder Expected error") + if err != nil { + t.Error(err) } } func TestGetMarginLoanOrders(t *testing.T) { t.Parallel() - if !h.ValidateAPICredentials() { t.Skip() } @@ -292,7 +289,6 @@ func TestGetMarginLoanOrders(t *testing.T) { func TestGetMarginAccountBalance(t *testing.T) { t.Parallel() - if !h.ValidateAPICredentials() { t.Skip() } @@ -305,7 +301,9 @@ func TestGetMarginAccountBalance(t *testing.T) { func TestCancelWithdraw(t *testing.T) { t.Parallel() - + if !h.ValidateAPICredentials() || !canManipulateRealOrders { + t.Skip() + } _, err := h.CancelWithdraw(1337) if err == nil { t.Error("Huobi TestCancelWithdraw Expected error") @@ -314,7 +312,6 @@ func TestCancelWithdraw(t *testing.T) { func TestPEMLoadAndSign(t *testing.T) { t.Parallel() - pemKey := strings.NewReader(h.API.Credentials.PEMKey) pemBytes, err := ioutil.ReadAll(pemKey) if err != nil { @@ -355,7 +352,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() h.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -443,20 +440,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - h.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := h.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - h.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)}, @@ -471,9 +462,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - h.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)}, @@ -494,9 +482,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if !h.ValidateAPICredentials() { t.Skip() } @@ -528,15 +513,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -555,8 +536,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - h.SetDefaults() - TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -584,7 +563,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { _, err := h.GetAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") @@ -598,6 +577,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := h.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -605,8 +587,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - h.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -630,9 +610,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -645,9 +622,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -669,7 +643,7 @@ func TestGetDepositAddress(t *testing.T) { // TestWsGetAccountsList connects to WS, logs in, gets account list func TestWsGetAccountsList(t *testing.T) { setupWsTests(t) - resp, err := h.wsGetAccountsList(currency.NewPairFromString("ethbtc")) + resp, err := h.wsGetAccountsList() if err != nil { t.Fatal(err) } diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index c4d516bc..9ad0f9f2 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -170,24 +170,23 @@ type CancelOrderBatch struct { // OrderInfo stores the order info type OrderInfo struct { - ID int `json:"id"` + ID int64 `json:"id"` Symbol string `json:"symbol"` - AccountID float64 `json:"account-id"` + AccountID int64 `json:"account-id"` Amount float64 `json:"amount,string"` Price float64 `json:"price,string"` CreatedAt int64 `json:"created-at"` Type string `json:"type"` FieldAmount float64 `json:"field-amount,string"` FieldCashAmount float64 `json:"field-cash-amount,string"` - Fieldees float64 `json:"field-fees,string"` FilledAmount float64 `json:"filled-amount,string"` FilledCashAmount float64 `json:"filled-cash-amount,string"` FilledFees float64 `json:"filled-fees,string"` FinishedAt int64 `json:"finished-at"` - UserID int `json:"user-id"` + UserID int64 `json:"user-id"` Source string `json:"source"` State string `json:"state"` - CanceledAt int `json:"canceled-at"` + CanceledAt int64 `json:"canceled-at"` Exchange string `json:"exchange"` Batch string `json:"batch"` } @@ -547,31 +546,13 @@ type WsAuthenticatedAccountsListResponseDataList struct { // WsAuthenticatedOrdersListResponse response from OrdersList authenticated endpoint type WsAuthenticatedOrdersListResponse struct { WsAuthenticatedDataResponse - Data []WsAuthenticatedOrdersListResponseData `json:"data"` -} - -// WsAuthenticatedOrdersListResponseData contains order details -type WsAuthenticatedOrdersListResponseData struct { - ID int64 `json:"id"` - Symbol string `json:"symbol"` - AccountID int64 `json:"account-id"` - Amount float64 `json:"amount,string"` - Price float64 `json:"price,string"` - CreatedAt int64 `json:"created-at"` - Type string `json:"type"` - FilledAmount float64 `json:"filled-amount,string"` - FilledCashAmount float64 `json:"filled-cash-amount,string"` - FilledFees float64 `json:"filled-fees,string"` - FinishedAt int64 `json:"finished-at"` - Source string `json:"source"` - State string `json:"state"` - CanceledAt int64 `json:"canceled-at"` + Data []OrderInfo `json:"data"` } // WsAuthenticatedOrderDetailResponse response from OrderDetail authenticated endpoint type WsAuthenticatedOrderDetailResponse struct { WsAuthenticatedDataResponse - Data WsAuthenticatedOrdersListResponseData `json:"data"` + Data OrderInfo `json:"data"` } // WsPong sent for pong messages diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index fa7022f4..93dda0a8 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -49,7 +49,7 @@ const ( ) // Instantiates a communications channel between websocket connections -var comms = make(chan WsMessage, 1) +var comms = make(chan WsMessage) // WsConnect initiates a new websocket connection func (h *HUOBI) WsConnect() error { @@ -68,6 +68,7 @@ func (h *HUOBI) WsConnect() error { err = h.wsLogin() if err != nil { log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err) + h.Websocket.SetCanUseAuthenticatedEndpoints(false) } go h.WsHandleData() @@ -434,7 +435,7 @@ func (h *HUOBI) wsAuthenticatedSubscribe(operation, endpoint, topic string) erro return h.AuthenticatedWebsocketConn.SendMessage(request) } -func (h *HUOBI) wsGetAccountsList(pair currency.Pair) (*WsAuthenticatedAccountsListResponse, error) { +func (h *HUOBI) wsGetAccountsList() (*WsAuthenticatedAccountsListResponse, error) { if !h.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authenticated cannot get accounts list", h.Name) } @@ -446,7 +447,6 @@ func (h *HUOBI) wsGetAccountsList(pair currency.Pair) (*WsAuthenticatedAccountsL SignatureVersion: signatureVersion, Timestamp: timestamp, Topic: wsAccountsList, - Symbol: h.FormatExchangeCurrency(pair, asset.Spot).String(), } hmac := h.wsGenerateSignature(timestamp, wsAccountListEndpoint) request.Signature = crypto.Base64Encode(hmac) diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index e83c5e8b..c2429488 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" "time" @@ -96,6 +97,8 @@ func (h *HUOBI) SetDefaults() { AuthenticatedEndpoints: true, AccountInfo: true, MessageCorrelation: true, + GetOrder: true, + GetOrders: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | exchange.NoFiatWithdrawals, @@ -393,62 +396,83 @@ func (h *HUOBI) GetAccountID() ([]Account, error) { func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo info.Exchange = h.Name - - accounts, err := h.GetAccountID() - if err != nil { - return info, err - } - - for i := range accounts { - var acc exchange.Account - acc.ID = strconv.FormatInt(accounts[i].ID, 10) - balances, err := h.GetAccountBalance(acc.ID) + if h.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + resp, err := h.wsGetAccountsList() if err != nil { return info, err } - var currencyDetails []exchange.AccountCurrencyInfo - for j := range balances { - var frozen bool - if balances[j].Type == "frozen" { - frozen = true + for i := range resp.Data { + if len(resp.Data[i].List) == 0 { + continue + } + currData := exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(resp.Data[i].List[0].Currency), + TotalValue: resp.Data[i].List[0].Balance, + } + if len(resp.Data[i].List) > 1 && resp.Data[i].List[1].Type == "frozen" { + currData.Hold = resp.Data[i].List[1].Balance + } + currencyDetails = append(currencyDetails, currData) + } + var acc exchange.Account + acc.Currencies = currencyDetails + info.Accounts = append(info.Accounts, acc) + } else { + accounts, err := h.GetAccountID() + if err != nil { + return info, err + } + for i := range accounts { + var acc exchange.Account + acc.ID = strconv.FormatInt(accounts[i].ID, 10) + balances, err := h.GetAccountBalance(acc.ID) + if err != nil { + return info, err } - var updated bool - for i := range currencyDetails { - if currencyDetails[i].CurrencyName == currency.NewCode(balances[j].Currency) { - if frozen { - currencyDetails[i].Hold = balances[j].Balance - } else { - currencyDetails[i].TotalValue = balances[j].Balance + var currencyDetails []exchange.AccountCurrencyInfo + for j := range balances { + var frozen bool + if balances[j].Type == "frozen" { + frozen = true + } + + var updated bool + for i := range currencyDetails { + if currencyDetails[i].CurrencyName == currency.NewCode(balances[j].Currency) { + if frozen { + currencyDetails[i].Hold = balances[j].Balance + } else { + currencyDetails[i].TotalValue = balances[j].Balance + } + updated = true } - updated = true + } + + if updated { + continue + } + + if frozen { + currencyDetails = append(currencyDetails, + exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(balances[j].Currency), + Hold: balances[j].Balance, + }) + } else { + currencyDetails = append(currencyDetails, + exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(balances[j].Currency), + TotalValue: balances[j].Balance, + }) } } - if updated { - continue - } - - if frozen { - currencyDetails = append(currencyDetails, - exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(balances[j].Currency), - Hold: balances[j].Balance, - }) - } else { - currencyDetails = append(currencyDetails, - exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(balances[j].Currency), - TotalValue: balances[j].Balance, - }) - } + acc.Currencies = currencyDetails + info.Accounts = append(info.Accounts, acc) } - - acc.Currencies = currencyDetails - info.Accounts = append(info.Accounts, acc) } - return info, nil } @@ -498,13 +522,18 @@ func (h *HUOBI) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { params.Type = formattedType response, err := h.SpotNewOrder(params) + if err != nil { + return submitOrderResponse, err + } if response > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true + + submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -527,9 +556,10 @@ func (h *HUOBI) CancelOrder(order *order.Cancel) error { // CancelAllOrders cancels all orders associated with a currency pair func (h *HUOBI) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { var cancelAllOrdersResponse order.CancelAllResponse - for _, currency := range h.GetEnabledPairs(asset.Spot) { + enabledPairs := h.GetEnabledPairs(asset.Spot) + for i := range enabledPairs { resp, err := h.CancelOpenOrdersBatch(orderCancellation.AccountID, - h.FormatExchangeCurrency(currency, asset.Spot).String()) + h.FormatExchangeCurrency(enabledPairs[i], asset.Spot).String()) if err != nil { return cancelAllOrdersResponse, err } @@ -551,7 +581,56 @@ func (h *HUOBI) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAl // GetOrderInfo returns information on a current open order func (h *HUOBI) GetOrderInfo(orderID string) (order.Detail, error) { var orderDetail order.Detail - return orderDetail, common.ErrNotYetImplemented + var respData *OrderInfo + if h.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + resp, err := h.wsGetOrderDetails(orderID) + if err != nil { + return orderDetail, err + } + respData = &resp.Data + } else { + oID, err := strconv.ParseInt(orderID, 10, 64) + if err != nil { + return orderDetail, err + } + resp, err := h.GetOrder(oID) + if err != nil { + return orderDetail, err + } + respData = &resp + } + if respData.ID == 0 { + return orderDetail, fmt.Errorf("%s - order not found for orderid %s", h.Name, orderID) + } + + typeDetails := strings.Split(respData.Type, "-") + orderSide, err := order.StringToOrderSide(typeDetails[0]) + if err != nil { + return orderDetail, err + } + orderType, err := order.StringToOrderType(typeDetails[1]) + if err != nil { + return orderDetail, err + } + orderStatus, err := order.StringToOrderStatus(respData.State) + if err != nil { + return orderDetail, err + } + orderDetail = order.Detail{ + Exchange: h.Name, + ID: strconv.FormatInt(respData.ID, 10), + AccountID: strconv.FormatInt(respData.AccountID, 10), + CurrencyPair: currency.NewPairFromString(respData.Symbol), + OrderType: orderType, + OrderSide: orderSide, + OrderDate: time.Unix(respData.CreatedAt, 0), + Status: orderStatus, + Price: respData.Price, + Amount: respData.Amount, + ExecutedAmount: respData.FilledAmount, + Fee: respData.FilledFees, + } + return orderDetail, nil } // GetDepositAddress returns a deposit address for a specified currency @@ -607,32 +686,72 @@ func (h *HUOBI) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er var orders []order.Detail - for i := range req.Currencies { - resp, err := h.GetOpenOrders(h.API.Credentials.ClientID, - req.Currencies[i].Lower().String(), - side, - 500) - if err != nil { - return nil, err + if h.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + for i := range req.Currencies { + resp, err := h.wsGetOrdersList(-1, req.Currencies[i]) + if err != nil { + return orders, err + } + for j := range resp.Data { + sideData := strings.Split(resp.Data[j].OrderState, "-") + side = sideData[0] + orderSide, err := order.StringToOrderSide(side) + if err != nil { + return orders, err + } + orderType, err := order.StringToOrderType(sideData[1]) + if err != nil { + return orders, err + } + orderStatus, err := order.StringToOrderStatus(resp.Data[j].OrderState) + if err != nil { + return orders, err + } + orders = append(orders, order.Detail{ + Exchange: h.Name, + AccountID: strconv.FormatInt(resp.Data[j].AccountID, 10), + ID: strconv.FormatInt(resp.Data[j].OrderID, 10), + CurrencyPair: req.Currencies[i], + OrderType: orderType, + OrderSide: orderSide, + OrderDate: time.Unix(resp.Data[j].CreatedAt, 0), + Status: orderStatus, + Price: resp.Data[j].Price, + Amount: resp.Data[j].OrderAmount, + ExecutedAmount: resp.Data[j].FilledAmount, + RemainingAmount: resp.Data[j].UnfilledAmount, + Fee: resp.Data[j].FilledFees, + }) + } } - - for i := range resp { - orderDetail := order.Detail{ - ID: strconv.FormatInt(int64(resp[i].ID), 10), - Price: resp[i].Price, - Amount: resp[i].Amount, - CurrencyPair: req.Currencies[i], - Exchange: h.Name, - ExecutedAmount: resp[i].FilledAmount, - OrderDate: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)), - Status: order.Status(resp[i].State), - AccountID: strconv.FormatFloat(resp[i].AccountID, 'f', -1, 64), - Fee: resp[i].FilledFees, + } else { + for i := range req.Currencies { + resp, err := h.GetOpenOrders(h.API.Credentials.ClientID, + req.Currencies[i].Lower().String(), + side, + 500) + if err != nil { + return nil, err } - setOrderSideAndType(resp[i].Type, &orderDetail) + for i := range resp { + orderDetail := order.Detail{ + ID: strconv.FormatInt(resp[i].ID, 10), + Price: resp[i].Price, + Amount: resp[i].Amount, + CurrencyPair: req.Currencies[i], + Exchange: h.Name, + ExecutedAmount: resp[i].FilledAmount, + OrderDate: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)), + Status: order.Status(resp[i].State), + AccountID: strconv.FormatInt(resp[i].AccountID, 10), + Fee: resp[i].FilledFees, + } - orders = append(orders, orderDetail) + setOrderSideAndType(resp[i].Type, &orderDetail) + + orders = append(orders, orderDetail) + } } } @@ -664,7 +783,7 @@ func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er for i := range resp { orderDetail := order.Detail{ - ID: strconv.FormatInt(int64(resp[i].ID), 10), + ID: strconv.FormatInt(resp[i].ID, 10), Price: resp[i].Price, Amount: resp[i].Amount, CurrencyPair: req.Currencies[i], @@ -672,7 +791,7 @@ func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er ExecutedAmount: resp[i].FilledAmount, OrderDate: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)), Status: order.Status(resp[i].State), - AccountID: strconv.FormatFloat(resp[i].AccountID, 'f', -1, 64), + AccountID: strconv.FormatInt(resp[i].AccountID, 10), Fee: resp[i].FilledFees, } diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index 42f29425..85cfd4d6 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -1,7 +1,9 @@ package itbit import ( + "log" "net/url" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -21,19 +23,16 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { i.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Itbit load config error", err) + log.Fatal("Itbit load config error", err) } itbitConfig, err := cfg.GetExchangeConfig("ITBIT") if err != nil { - t.Error("Itbit Setup() init error") + log.Fatal("Itbit Setup() init error") } itbitConfig.API.AuthenticatedSupport = true itbitConfig.API.Credentials.Key = apiKey @@ -42,8 +41,10 @@ func TestSetup(t *testing.T) { err = i.Setup(itbitConfig) if err != nil { - t.Fatal("Itbit setup error", err) + log.Fatal("Itbit setup error", err) } + + os.Exit(m.Run()) } func TestGetTicker(t *testing.T) { @@ -167,7 +168,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() i.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -255,20 +256,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - i.SetDefaults() expectedResult := exchange.WithdrawCryptoViaWebsiteOnlyText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := i.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - i.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -282,9 +277,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - i.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -304,8 +296,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - i.SetDefaults() - TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -330,15 +320,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - i.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -357,15 +343,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - i.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -388,7 +370,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - if apiKey != "" || apiSecret != "" || clientID != "" { + if areTestAPIKeysSet() { _, err := i.GetAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") @@ -397,6 +379,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := i.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -404,8 +389,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - i.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -426,9 +409,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - i.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -441,9 +421,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - i.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 1168ebc4..918b877d 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -331,14 +331,18 @@ func (i *ItBit) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.Price, s.Pair.String(), "") + if err != nil { + return submitOrderResponse, err + } if response.ID != "" { submitOrderResponse.OrderID = response.ID } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + if response.AmountFilled == s.Amount { + submitOrderResponse.FullyMatched = true + } + submitOrderResponse.IsOrderPlaced = true + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 9bc63241..46e532db 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -47,6 +47,7 @@ const ( krakenDepositAddresses = "DepositAddresses" krakenWithdrawStatus = "WithdrawStatus" krakenWithdrawCancel = "WithdrawCancel" + krakenWebsocketToken = "GetWebSocketsToken" krakenAuthRate = 0 krakenUnauthRate = 0 @@ -57,8 +58,9 @@ var assetPairMap map[string]string // Kraken is the overarching type across the alphapoint package type Kraken struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection - wsRequestMtx sync.Mutex + WebsocketConn *wshandler.WebsocketConnection + AuthenticatedWebsocketConn *wshandler.WebsocketConnection + wsRequestMtx sync.Mutex } // GetServerTime returns current server time @@ -1036,3 +1038,14 @@ func (k *Kraken) WithdrawCancel(c currency.Code, refID string) (bool, error) { return response.Result, GetError(response.Error) } + +func (k *Kraken) GetWebsocketToken() (string, error) { + var response WsTokenResponse + if err := k.SendAuthenticatedHTTPRequest(krakenWebsocketToken, url.Values{}, &response); err != nil { + return "", err + } + if len(response.Error) > 0 { + return "", fmt.Errorf("%s - %v", k.Name, response.Error) + } + return response.Result.Token, nil +} diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 41b22ed7..227bde0b 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -3,6 +3,8 @@ package kraken import ( "log" "net/http" + "os" + "strings" "testing" "github.com/gorilla/websocket" @@ -25,13 +27,9 @@ const ( canManipulateRealOrders = false ) -// TestSetDefaults setup func -func TestSetDefaults(t *testing.T) { - k.SetDefaults() -} - // TestSetup setup func -func TestSetup(t *testing.T) { +func TestMain(m *testing.M) { + k.SetDefaults() cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { @@ -39,19 +37,19 @@ func TestSetup(t *testing.T) { } krakenConfig, err := cfg.GetExchangeConfig("Kraken") if err != nil { - t.Error("kraken Setup() init error", err) + log.Fatal("kraken Setup() init error", err) } krakenConfig.API.AuthenticatedSupport = true krakenConfig.API.Credentials.Key = apiKey krakenConfig.API.Credentials.Secret = apiSecret krakenConfig.API.Credentials.ClientID = clientID krakenConfig.API.Endpoints.WebsocketURL = k.API.Endpoints.WebsocketURL - subscribeToDefaultChannels = false - err = k.Setup(krakenConfig) if err != nil { - t.Fatal("Kraken setup error", err) + log.Fatal("Kraken setup error", err) } + + os.Exit(m.Run()) } // TestGetServerTime API endpoint test @@ -278,7 +276,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() k.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -290,8 +288,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - k.SetDefaults() - TestSetup(t) var feeBuilder = setFeeBuilder() if areTestAPIKeysSet() { @@ -373,11 +369,8 @@ func TestGetFee(t *testing.T) { // TestFormatWithdrawPermissions logic test func TestFormatWithdrawPermissions(t *testing.T) { - k.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.AutoWithdrawFiatWithSetupText + " & " + exchange.WithdrawFiatWith2FAText - withdrawPermissions := k.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } @@ -385,9 +378,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { // TestGetActiveOrders wrapper test func TestGetActiveOrders(t *testing.T) { - k.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -402,9 +392,6 @@ func TestGetActiveOrders(t *testing.T) { // TestGetOrderHistory wrapper test func TestGetOrderHistory(t *testing.T) { - k.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -417,6 +404,21 @@ func TestGetOrderHistory(t *testing.T) { } } +// TestGetOrderHistory wrapper test +func TestGetOrderInfo(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } + + _, err := k.GetOrderInfo("ImACoolOrderID") + if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting error") + } + if areTestAPIKeysSet() && !strings.Contains(err.Error(), "- Order ID not found:") { + t.Error("Expected Order ID not found error") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -425,9 +427,6 @@ func areTestAPIKeysSet() bool { // TestSubmitOrder wrapper test func TestSubmitOrder(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -453,15 +452,11 @@ func TestSubmitOrder(t *testing.T) { // TestCancelExchangeOrder wrapper test func TestCancelExchangeOrder(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -480,15 +475,11 @@ func TestCancelExchangeOrder(t *testing.T) { // TestCancelAllExchangeOrders wrapper test func TestCancelAllExchangeOrders(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -512,7 +503,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { // TestGetAccountInfo wrapper test func TestGetAccountInfo(t *testing.T) { - if apiKey != "" || apiSecret != "" || clientID != "" { + if areTestAPIKeysSet() || clientID != "" { _, err := k.GetAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) @@ -527,6 +518,9 @@ func TestGetAccountInfo(t *testing.T) { // TestModifyOrder wrapper test func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := k.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -535,9 +529,6 @@ func TestModifyOrder(t *testing.T) { // TestWithdraw wrapper test func TestWithdraw(t *testing.T) { - k.SetDefaults() - TestSetup(t) - withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -563,9 +554,6 @@ func TestWithdraw(t *testing.T) { // TestWithdrawFiat wrapper test func TestWithdrawFiat(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -590,9 +578,6 @@ func TestWithdrawFiat(t *testing.T) { // TestWithdrawInternationalBank wrapper test func TestWithdrawInternationalBank(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -632,9 +617,6 @@ func TestGetDepositAddress(t *testing.T) { // TestWithdrawStatus wrapper test func TestWithdrawStatus(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() { _, err := k.WithdrawStatus(currency.BTC, "") if err != nil { @@ -650,8 +632,6 @@ func TestWithdrawStatus(t *testing.T) { // TestWithdrawCancel wrapper test func TestWithdrawCancel(t *testing.T) { - k.SetDefaults() - TestSetup(t) _, err := k.WithdrawCancel(currency.BTC, "") if areTestAPIKeysSet() && err == nil { t.Error("WithdrawCancel() error cannot be nil") @@ -666,12 +646,11 @@ func setupWsTests(t *testing.T) { if wsSetupRan { return } - TestSetDefaults(t) - TestSetup(t) if !k.Websocket.IsEnabled() && !k.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() + comms = make(chan wshandler.WebsocketResponse, sharedtestvalues.WebsocketChannelOverrideCapacity) k.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() k.WebsocketConn = &wshandler.WebsocketConnection{ ExchangeName: k.Name, @@ -680,12 +659,33 @@ func setupWsTests(t *testing.T) { ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, } + k.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ + ExchangeName: k.Name, + URL: krakenAuthWSURL, + Verbose: k.Verbose, + ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, + ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, + } var dialer websocket.Dialer err := k.WebsocketConn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } + err = k.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) + if err != nil { + t.Fatal(err) + } + + token, err := k.GetWebsocketToken() + if err != nil { + t.Error(err) + } + authToken = token + + go k.WsReadData(k.WebsocketConn) + go k.WsReadData(k.AuthenticatedWebsocketConn) go k.WsHandleData() + go k.wsPingHandler() wsSetupRan = true } @@ -700,3 +700,38 @@ func TestWebsocketSubscribe(t *testing.T) { t.Error(err) } } + +func TestGetWSToken(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required, skipping") + } + resp, err := k.GetWebsocketToken() + if err != nil { + t.Error(err) + } + if resp == "" { + t.Error("Token not returned") + } +} + +func TestWsAddOrder(t *testing.T) { + setupWsTests(t) + _, err := k.wsAddOrder(&WsAddOrderRequest{ + OrderType: order.Limit.Lower(), + OrderSide: order.Buy.Lower(), + Pair: "XBT/USD", + Price: -100, + }) + if err != nil { + t.Error(err) + } +} + +func TestWsCancelOrder(t *testing.T) { + setupWsTests(t) + err := k.wsCancelOrders([]string{"1337"}) + if err != nil { + t.Error(err) + } +} diff --git a/exchanges/kraken/kraken_types.go b/exchanges/kraken/kraken_types.go index e816afd4..d950a6a9 100644 --- a/exchanges/kraken/kraken_types.go +++ b/exchanges/kraken/kraken_types.go @@ -1,6 +1,10 @@ package kraken -import "github.com/thrasher-corp/gocryptotrader/currency" +import ( + "time" + + "github.com/thrasher-corp/gocryptotrader/currency" +) // TimeResponse type type TimeResponse struct { @@ -391,7 +395,7 @@ type WithdrawStatusResponse struct { type WebsocketSubscriptionEventRequest struct { Event string `json:"event"` // subscribe RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message. - Pairs []string `json:"pair"` // Array of currency pairs (pair1,pair2,pair3). + Pairs []string `json:"pair,omitempty"` // Array of currency pairs (pair1,pair2,pair3). Subscription WebsocketSubscriptionData `json:"subscription,omitempty"` } @@ -413,6 +417,8 @@ type WebsocketSubscriptionData struct { Name string `json:"name,omitempty"` // ticker|ohlc|trade|book|spread|*, * for all (ohlc interval value is 1 if all channels subscribed) Interval int64 `json:"interval,omitempty"` // Optional - Time interval associated with ohlc subscription in minutes. Default 1. Valid Interval values: 1|5|15|30|60|240|1440|10080|21600 Depth int64 `json:"depth,omitempty"` // Optional - depth associated with book subscription in number of levels each side, default 10. Valid Options are: 10, 25, 100, 500, 1000 + Token string `json:"token,omitempty"` // Optional used for authenticated requests + } // WebsocketEventResponse holds all data response types @@ -459,3 +465,103 @@ type WebsocketChannelData struct { Pair currency.Pair ChannelID int64 } + +// WsTokenResponse holds the WS auth token +type WsTokenResponse struct { + Error []string `json:"error"` + Result struct { + Expires int64 `json:"expires"` + Token string `json:"token"` + } `json:"result"` +} + +// WsOwnTrade ws auth owntrade data +type WsOwnTrade struct { + Cost float64 `json:"cost,string"` + Fee float64 `json:"fee,string"` + Margin float64 `json:"margin,string"` + OrderTransactionID string `json:"ordertxid"` + OrderType string `json:"ordertype"` + Pair string `json:"pair"` + PostTransactionID string `json:"postxid"` + Price float64 `json:"price,string"` + Time time.Time `json:"time"` + Type string `json:"type"` + Vol float64 `json:"vol,string"` +} + +// WsOpenOrders ws auth open order data +type WsOpenOrders struct { + Cost float64 `json:"cost,string"` + Description WsOpenOrderDescription `json:"descr"` + ExpireTime time.Time `json:"expiretm"` + Fee float64 `json:"fee,string"` + LimitPrice float64 `json:"limitprice,string"` + Misc string `json:"misc"` + OFlags string `json:"oflags"` + OpenTime time.Time `json:"opentm"` + Price float64 `json:"price,string"` + RefID string `json:"refid"` + StartTime time.Time `json:"starttm"` + Status string `json:"status"` + StopPrice float64 `json:"stopprice,string"` + UserReference float64 `json:"userref"` + Volume float64 `json:"vol,string"` + ExecutedVolume float64 `json:"vol_exec,string"` +} + +// WsOpenOrderDescription additional data for WsOpenOrders +type WsOpenOrderDescription struct { + Close string `json:"close"` + Leverage string `json:"leverage"` + Order string `json:"order"` + OrderType string `json:"ordertype"` + Pair string `json:"pair"` + Price float64 `json:"price,string"` + Price2 float64 `json:"price2,string"` + Type string `json:"type"` +} + +// WsAddOrderRequest request type for ws adding order +type WsAddOrderRequest struct { + Event string `json:"event"` + Token string `json:"token"` + OrderType string `json:"ordertype"` + OrderSide string `json:"type"` + Pair string `json:"pair"` + Price float64 `json:"price,omitempty"` // optional + Price2 float64 `json:"price2,omitempty"` // optional + Volume float64 `json:"volume,omitempty"` + Leverage float64 `json:"leverage,omitempty"` // optional + OFlags string `json:"oflags,omitempty"` // optional + StartTime string `json:"starttm,omitempty"` // optional + ExpireTime string `json:"expiretm,omitempty"` // optional + UserReferenceID string `json:"userref,omitempty"` // optional + Validate string `json:"validate,omitempty"` // optional + CloseOrderType string `json:"close[ordertype],omitempty"` // optional + ClosePrice float64 `json:"close[price],omitempty"` // optional + ClosePrice2 float64 `json:"close[price2],omitempty"` // optional +} + +// WsAddOrderResponse response data for ws order +type WsAddOrderResponse struct { + Description string `json:"descr"` + Event string `json:"event"` + Status string `json:"status"` + TransactionID string `json:"txid"` + ErrorMessage string `json:"errorMessage"` +} + +// WsCancelOrderRequest request for ws cancel order +type WsCancelOrderRequest struct { + Event string `json:"event"` + Token string `json:"token"` + TransactionIDs []string `json:"txid"` +} + +// WsCancelOrderResponse response data for ws cancel order +type WsCancelOrderResponse struct { + Event string `json:"event"` + Status string `json:"status"` + ErrorMessage string `json:"errorMessage"` +} diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index 1be268b1..8faa8761 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -10,7 +10,9 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -21,11 +23,9 @@ import ( // List of all websocket channels to subscribe to const ( krakenWSURL = "wss://ws.kraken.com" + krakenAuthWSURL = "wss://ws-auth.kraken.com" krakenWSSandboxURL = "wss://sandbox.kraken.com" - krakenWSSupportedVersion = "0.2.0" - // If a checksum fails, then resubscribing to the channel fails, fatal after these attempts - krakenWsResubscribeFailureLimit = 3 - krakenWsResubscribeDelayInSeconds = 3 + krakenWSSupportedVersion = "0.3.0" // WS endpoints krakenWsHeartbeat = "heartbeat" krakenWsPing = "ping" @@ -39,18 +39,22 @@ const ( krakenWsTrade = "trade" krakenWsSpread = "spread" krakenWsOrderbook = "book" - - orderbookBufferLimit = 3 - krakenWsRateLimit = 50 + krakenWsOwnTrades = "ownTrades" + krakenWsOpenOrders = "openOrders" + krakenWsAddOrder = "addOrder" + krakenWsCancelOrder = "cancelOrder" + krakenWsRateLimit = 50 ) // orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time var subscriptionChannelPair []WebsocketChannelData -var subscribeToDefaultChannels = true +var comms = make(chan wshandler.WebsocketResponse) +var authToken string // Channels require a topic and a currency // Format [[ticker,but-t4u],[orderbook,nce-btt]] var defaultSubscribedChannels = []string{krakenWsTicker, krakenWsTrade, krakenWsOrderbook, krakenWsOHLC, krakenWsSpread} +var authenticatedChannels = []string{krakenWsOwnTrades, krakenWsOpenOrders} // WsConnect initiates a websocket connection func (k *Kraken) WsConnect() error { @@ -62,15 +66,86 @@ func (k *Kraken) WsConnect() error { if err != nil { return err } - go k.WsHandleData() - go k.wsPingHandler() - if subscribeToDefaultChannels { - k.GenerateDefaultSubscriptions() + if k.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + authToken, err = k.GetWebsocketToken() + if err != nil { + k.Websocket.SetCanUseAuthenticatedEndpoints(false) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", k.Name, err) + } + err = k.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) + if err != nil { + k.Websocket.SetCanUseAuthenticatedEndpoints(false) + log.Errorf(log.ExchangeSys, "%v - failed to connect to authenticated endpoint: %v\n", k.Name, err) + } + go k.WsReadData(k.AuthenticatedWebsocketConn) + k.GenerateAuthenticatedSubscriptions() } + go k.WsReadData(k.WebsocketConn) + go k.WsHandleData() + go k.wsPingHandler() + k.GenerateDefaultSubscriptions() + return nil } +// WsReadData funnels both auth and public ws data into one manageable place +func (k *Kraken) WsReadData(ws *wshandler.WebsocketConnection) { + k.Websocket.Wg.Add(1) + defer k.Websocket.Wg.Done() + for { + select { + case <-k.Websocket.ShutdownC: + return + default: + resp, err := ws.ReadMessage() + if err != nil { + k.Websocket.DataHandler <- err + return + } + k.Websocket.TrafficAlert <- struct{}{} + comms <- resp + } + } +} + +// WsHandleData handles the read data from the websocket connection +func (k *Kraken) WsHandleData() { + k.Websocket.Wg.Add(1) + defer func() { + k.Websocket.Wg.Done() + }() + + for { + select { + case <-k.Websocket.ShutdownC: + return + default: + resp := <-comms + // event response handling + var eventResponse WebsocketEventResponse + err := json.Unmarshal(resp.Raw, &eventResponse) + if err == nil && eventResponse.Event != "" { + k.WsHandleEventResponse(&eventResponse, resp.Raw) + continue + } + // Data response handling + var dataResponse WebsocketDataResponse + err = json.Unmarshal(resp.Raw, &dataResponse) + if err != nil { + log.Error(log.WebsocketMgr, fmt.Errorf("%s - unhandled websocket data: %v", k.Name, err)) + continue + } + if _, ok := dataResponse[0].(float64); ok { + k.WsHandleDataResponse(dataResponse) + } + if _, ok := dataResponse[1].(string); ok { + k.wsHandleAuthDataResponse(dataResponse) + } + } + } +} + // wsPingHandler sends a message "ping" every 27 to maintain the connection to the websocket func (k *Kraken) wsPingHandler() { k.Websocket.Wg.Add(1) @@ -96,82 +171,47 @@ func (k *Kraken) wsPingHandler() { } } -// WsHandleData handles the read data from the websocket connection -func (k *Kraken) WsHandleData() { - k.Websocket.Wg.Add(1) - defer func() { - k.Websocket.Wg.Done() - }() - - for { - select { - case <-k.Websocket.ShutdownC: - return - default: - resp, err := k.WebsocketConn.ReadMessage() - if err != nil { - k.Websocket.ReadMessageErrors <- err - return - } - k.Websocket.TrafficAlert <- struct{}{} - // event response handling - var eventResponse WebsocketEventResponse - err = json.Unmarshal(resp.Raw, &eventResponse) - if err == nil && eventResponse.Event != "" { - k.WsHandleEventResponse(&eventResponse, resp.Raw) - continue - } - // Data response handling - var dataResponse WebsocketDataResponse - err = json.Unmarshal(resp.Raw, &dataResponse) - if err == nil && dataResponse[0].(float64) >= 0 { - k.WsHandleDataResponse(dataResponse) - continue - } - continue - } - } -} - // WsHandleDataResponse classifies the WS response and sends to appropriate handler func (k *Kraken) WsHandleDataResponse(response WebsocketDataResponse) { - channelID := int64(response[0].(float64)) - channelData := getSubscriptionChannelData(channelID) - switch channelData.Subscription { - case krakenWsTicker: - if k.Verbose { - log.Debugf(log.ExchangeSys, "%v Websocket ticker data received", - k.Name) + if cID, ok := response[0].(float64); ok { + channelID := int64(cID) + channelData := getSubscriptionChannelData(channelID) + switch channelData.Subscription { + case krakenWsTicker: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket ticker data received", + k.Name) + } + k.wsProcessTickers(&channelData, response[1].(map[string]interface{})) + case krakenWsOHLC: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket OHLC data received", + k.Name) + } + k.wsProcessCandles(&channelData, response[1].([]interface{})) + case krakenWsOrderbook: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket Orderbook data received", + k.Name) + } + k.wsProcessOrderBook(&channelData, response[1].(map[string]interface{})) + case krakenWsSpread: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket Spread data received", + k.Name) + } + k.wsProcessSpread(&channelData, response[1].([]interface{})) + case krakenWsTrade: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket Trade data received", + k.Name) + } + k.wsProcessTrades(&channelData, response[1].([]interface{})) + default: + log.Errorf(log.ExchangeSys, "%v Unidentified websocket data received: %v", + k.Name, + response) } - k.wsProcessTickers(&channelData, response[1].(map[string]interface{})) - case krakenWsOHLC: - if k.Verbose { - log.Debugf(log.ExchangeSys, "%v Websocket OHLC data received", - k.Name) - } - k.wsProcessCandles(&channelData, response[1].([]interface{})) - case krakenWsOrderbook: - if k.Verbose { - log.Debugf(log.ExchangeSys, "%v Websocket Orderbook data received", - k.Name) - } - k.wsProcessOrderBook(&channelData, response[1].(map[string]interface{})) - case krakenWsSpread: - if k.Verbose { - log.Debugf(log.ExchangeSys, "%v Websocket Spread data received", - k.Name) - } - k.wsProcessSpread(&channelData, response[1].([]interface{})) - case krakenWsTrade: - if k.Verbose { - log.Debugf(log.ExchangeSys, "%v Websocket Trade data received", - k.Name) - } - k.wsProcessTrades(&channelData, response[1].([]interface{})) - default: - log.Errorf(log.ExchangeSys, "%v Unidentified websocket data received: %v", - k.Name, - response) } } @@ -197,7 +237,7 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse, rawResp k.Websocket.DataHandler <- fmt.Errorf("%v Websocket status '%v'", k.Name, response.Status) } - if response.WebsocketStatusResponse.Version != krakenWSSupportedVersion { + if response.WebsocketStatusResponse.Version > krakenWSSupportedVersion { log.Warnf(log.ExchangeSys, "%v New version of Websocket API released. Was %v Now %v", k.Name, krakenWSSupportedVersion, response.WebsocketStatusResponse.Version) } @@ -214,6 +254,192 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse, rawResp } } +func (k *Kraken) wsHandleAuthDataResponse(response WebsocketDataResponse) { + if chName, ok := response[1].(string); ok { + switch chName { + case krakenWsOwnTrades: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket auth own trade data received", + k.Name) + } + k.wsProcessOwnTrades(&response[0]) + case krakenWsOpenOrders: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket auth open order data received", + k.Name) + } + k.wsProcessOpenOrders(&response[0]) + } + } +} + +func (k *Kraken) wsProcessOwnTrades(ownOrders interface{}) { + if data, ok := ownOrders.([]interface{}); ok { + for i := range data { + ownTrade := data[i].(map[string]interface{}) + for _, val := range ownTrade { + tradeData := val.(map[string]interface{}) + cost, err := strconv.ParseFloat(tradeData["cost"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + fee, err := strconv.ParseFloat(tradeData["fee"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + margin, err := strconv.ParseFloat(tradeData["margin"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + vol, err := strconv.ParseFloat(tradeData["vol"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + price, err := strconv.ParseFloat(tradeData["price"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + timeTogether, err := strconv.ParseFloat(tradeData["time"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + first, second, err := convert.SplitFloatDecimals(timeTogether) + if err != nil { + k.Websocket.DataHandler <- err + } + k.Websocket.DataHandler <- WsOwnTrade{ + Cost: cost, + Fee: fee, + Margin: margin, + OrderTransactionID: tradeData["ordertxid"].(string), + OrderType: tradeData["ordertype"].(string), + Pair: tradeData["pair"].(string), + PostTransactionID: tradeData["postxid"].(string), + Price: price, + Time: time.Unix(first, second), + Type: tradeData["type"].(string), + Vol: vol, + } + } + } + } else { + k.Websocket.DataHandler <- errors.New(k.Name + " - Invalid own trades data") + } +} + +func (k *Kraken) wsProcessOpenOrders(ownOrders interface{}) { + if data, ok := ownOrders.([]interface{}); ok { + for i := range data { + ownTrade := data[i].(map[string]interface{}) + for key, val := range ownTrade { + tradeData := val.(map[string]interface{}) + if len(tradeData) == 1 { + // just a status update + if status, ok := tradeData["status"].(string); ok { + k.Websocket.DataHandler <- k.Name + " - Order " + key + " " + status + } + } + startTimeConv, err := strconv.ParseFloat(tradeData["starttm"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + startTime, startTimeNano, err := convert.SplitFloatDecimals(startTimeConv) + if err != nil { + k.Websocket.DataHandler <- err + } + openTimeConv, err := strconv.ParseFloat(tradeData["opentm"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + openTime, openTimeNano, err := convert.SplitFloatDecimals(openTimeConv) + if err != nil { + k.Websocket.DataHandler <- err + } + expireTimeConv, err := strconv.ParseFloat(tradeData["expiretm"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + expireTime, expireTimeNano, err := convert.SplitFloatDecimals(expireTimeConv) + if err != nil { + k.Websocket.DataHandler <- err + } + cost, err := strconv.ParseFloat(tradeData["cost"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + executedVolume, err := strconv.ParseFloat(tradeData["vol_exec"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + volume, err := strconv.ParseFloat(tradeData["vol"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + userReference, err := strconv.ParseFloat(tradeData["userref"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + stopPrice, err := strconv.ParseFloat(tradeData["stopprice"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + price, err := strconv.ParseFloat(tradeData["price"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + limitPrice, err := strconv.ParseFloat(tradeData["limitprice"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + fee, err := strconv.ParseFloat(tradeData["fee"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + descriptionSubData := tradeData["description"].(map[string]interface{}) + descriptionPrice, err := strconv.ParseFloat(descriptionSubData["price"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + descriptionPrice2, err := strconv.ParseFloat(descriptionSubData["price2"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + description := WsOpenOrderDescription{ + Close: descriptionSubData["close"].(string), + Leverage: descriptionSubData["leverage"].(string), + Order: descriptionSubData["order"].(string), + OrderType: descriptionSubData["ordertype"].(string), + Pair: descriptionSubData["pair"].(string), + Price: descriptionPrice, + Price2: descriptionPrice2, + Type: descriptionSubData["type"].(string), + } + + k.Websocket.DataHandler <- WsOpenOrders{ + Cost: cost, + ExpireTime: time.Unix(expireTime, expireTimeNano), + Description: description, + Fee: fee, + LimitPrice: limitPrice, + Misc: tradeData["misc"].(string), + OFlags: tradeData["oflags"].(string), + OpenTime: time.Unix(openTime, openTimeNano), + Price: price, + RefID: tradeData["refid"].(string), + StartTime: time.Unix(startTime, startTimeNano), + Status: tradeData["status"].(string), + StopPrice: stopPrice, + UserReference: userReference, + Volume: volume, + ExecutedVolume: executedVolume, + } + } + } + } else { + k.Websocket.DataHandler <- errors.New(k.Name + " - Invalid own trades data") + } +} + // addNewSubscriptionChannelData stores channel ids, pairs and subscription types to an array // allowing correlation between subscriptions and returned data func addNewSubscriptionChannelData(response *WebsocketEventResponse) { @@ -623,22 +849,39 @@ func (k *Kraken) GenerateDefaultSubscriptions() { k.Websocket.SubscribeToChannels(subscriptions) } +// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() +func (k *Kraken) GenerateAuthenticatedSubscriptions() { + var subscriptions []wshandler.WebsocketChannelSubscription + for i := range authenticatedChannels { + params := make(map[string]interface{}) + subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + Channel: authenticatedChannels[i], + Params: params, + }) + } + k.Websocket.SubscribeToChannels(subscriptions) +} + // Subscribe sends a websocket message to receive data from the channel func (k *Kraken) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - var depth int64 - if channelToSubscribe.Channel == "book" { - depth = 1000 - } - resp := WebsocketSubscriptionEventRequest{ Event: krakenWsSubscribe, - Pairs: []string{channelToSubscribe.Currency.String()}, Subscription: WebsocketSubscriptionData{ - Name: channelToSubscribe.Channel, - Depth: depth, + Name: channelToSubscribe.Channel, }, RequestID: k.WebsocketConn.GenerateMessageID(false), } + if channelToSubscribe.Channel == "book" { + // TODO: Add ability to make depth customisable + resp.Subscription.Depth = 1000 + } + if !channelToSubscribe.Currency.IsEmpty() { + resp.Pairs = []string{channelToSubscribe.Currency.String()} + } + if channelToSubscribe.Params != nil { + resp.Subscription.Token = authToken + } + _, err := k.WebsocketConn.SendMessageReturnResponse(resp.RequestID, resp) return err } @@ -656,3 +899,32 @@ func (k *Kraken) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscr _, err := k.WebsocketConn.SendMessageReturnResponse(resp.RequestID, resp) return err } + +func (k *Kraken) wsAddOrder(request *WsAddOrderRequest) (string, error) { + id := k.AuthenticatedWebsocketConn.GenerateMessageID(false) + request.UserReferenceID = strconv.FormatInt(id, 10) + request.Event = krakenWsAddOrder + request.Token = authToken + jsonResp, err := k.AuthenticatedWebsocketConn.SendMessageReturnResponse(id, request) + if err != nil { + return "", err + } + var resp WsAddOrderResponse + err = json.Unmarshal(jsonResp, &resp) + if err != nil { + return "", err + } + if resp.ErrorMessage != "" { + return "", fmt.Errorf(k.Name + " - " + resp.ErrorMessage) + } + return resp.TransactionID, nil +} + +func (k *Kraken) wsCancelOrders(orderIDs []string) error { + request := WsCancelOrderRequest{ + Event: krakenWsCancelOrder, + Token: authToken, + TransactionIDs: orderIDs, + } + return k.AuthenticatedWebsocketConn.SendMessage(request) +} diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 2831e760..fa820996 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -9,6 +9,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -106,6 +107,9 @@ func (k *Kraken) SetDefaults() { Subscribe: true, Unsubscribe: true, MessageCorrelation: true, + SubmitOrder: true, + CancelOrder: true, + CancelOrders: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | exchange.WithdrawCryptoWith2FA | @@ -171,6 +175,16 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) error { ResponseMaxLimit: exch.WebsocketResponseMaxLimit, } + k.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ + ExchangeName: k.Name, + URL: krakenAuthWSURL, + ProxyURL: k.Websocket.GetProxyAddress(), + Verbose: k.Verbose, + RateLimit: krakenWsRateLimit, + ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, + ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + } + k.Websocket.Orderbook.Setup( exch.WebsocketOrderbookBufferLimit, true, @@ -397,25 +411,47 @@ func (k *Kraken) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex // SubmitOrder submits a new order func (k *Kraken) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - if err := s.Validate(); err != nil { + err := s.Validate() + if err != nil { return submitOrderResponse, err } - response, err := k.AddOrder(s.Pair.String(), - s.OrderSide.String(), - s.OrderType.String(), - s.Amount, - s.Price, - 0, - 0, - &AddOrderOptions{}) - if len(response.TransactionIds) > 0 { - submitOrderResponse.OrderID = strings.Join(response.TransactionIds, ", ") - } - if err == nil { + if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var resp string + resp, err = k.wsAddOrder(&WsAddOrderRequest{ + OrderType: s.OrderType.String(), + OrderSide: s.OrderSide.String(), + Pair: s.Pair.String(), + Price: s.Price, + Volume: s.Amount, + }) + if err != nil { + return submitOrderResponse, err + } + submitOrderResponse.OrderID = resp submitOrderResponse.IsOrderPlaced = true + } else { + var response AddOrderResponse + response, err = k.AddOrder(s.Pair.String(), + s.OrderSide.String(), + s.OrderType.String(), + s.Amount, + s.Price, + 0, + 0, + &AddOrderOptions{}) + if err != nil { + return submitOrderResponse, err + } + if len(response.TransactionIds) > 0 { + submitOrderResponse.OrderID = strings.Join(response.TransactionIds, ", ") + } } - return submitOrderResponse, err + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true + } + submitOrderResponse.IsOrderPlaced = true + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -426,6 +462,9 @@ func (k *Kraken) ModifyOrder(action *order.Modify) (string, error) { // CancelOrder cancels an order by its corresponding ID number func (k *Kraken) CancelOrder(order *order.Cancel) error { + if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + return k.wsCancelOrders([]string{order.OrderID}) + } _, err := k.CancelExistingOrder(order.OrderID) return err @@ -436,26 +475,78 @@ func (k *Kraken) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, erro cancelAllOrdersResponse := order.CancelAllResponse{ Status: make(map[string]string), } + var emptyOrderOptions OrderInfoOptions openOrders, err := k.GetOpenOrders(emptyOrderOptions) if err != nil { return cancelAllOrdersResponse, err } - for orderID := range openOrders.Open { - _, err = k.CancelExistingOrder(orderID) + var err error + if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + err = k.wsCancelOrders([]string{orderID}) + } else { + _, err = k.CancelExistingOrder(orderID) + } if err != nil { cancelAllOrdersResponse.Status[orderID] = err.Error() } } - return cancelAllOrdersResponse, nil } // GetOrderInfo returns information on a current open order func (k *Kraken) GetOrderInfo(orderID string) (order.Detail, error) { var orderDetail order.Detail - return orderDetail, common.ErrNotYetImplemented + var emptyOrderOptions OrderInfoOptions + openOrders, err := k.GetOpenOrders(emptyOrderOptions) + if err != nil { + return orderDetail, err + } + if orderInfo, ok := openOrders.Open[orderID]; ok { + var trades []order.TradeHistory + for i := range orderInfo.Trades { + trades = append(trades, order.TradeHistory{ + TID: orderInfo.Trades[i], + }) + } + firstNum, decNum, err := convert.SplitFloatDecimals(orderInfo.StartTime) + if err != nil { + return orderDetail, err + } + side, err := order.StringToOrderSide(orderInfo.Description.Type) + if err != nil { + return orderDetail, err + } + status, err := order.StringToOrderStatus(orderInfo.Status) + if err != nil { + return orderDetail, err + } + oType, err := order.StringToOrderType(orderInfo.Description.OrderType) + if err != nil { + return orderDetail, err + } + + orderDetail = order.Detail{ + Exchange: k.Name, + ID: orderID, + CurrencyPair: currency.NewPairFromString(orderInfo.Description.Pair), + OrderSide: side, + OrderType: oType, + OrderDate: time.Unix(firstNum, decNum), + Status: status, + Price: orderInfo.Price, + Amount: orderInfo.Volume, + ExecutedAmount: orderInfo.VolumeExecuted, + RemainingAmount: orderInfo.Volume - orderInfo.VolumeExecuted, + Fee: orderInfo.Fee, + Trades: trades, + } + } else { + return orderDetail, errors.New(k.Name + " - Order ID not found: " + orderID) + } + + return orderDetail, nil } // GetDepositAddress returns a deposit address for a specified currency @@ -606,5 +697,9 @@ func (k *Kraken) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, e // AuthenticateWebsocket sends an authentication message to the websocket func (k *Kraken) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported + resp, err := k.GetWebsocketToken() + if resp != "" { + authToken = resp + } + return err } diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index 48b8e33c..fd2c87f1 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -3,6 +3,7 @@ package lakebtc import ( "fmt" "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -16,7 +17,6 @@ import ( ) var l LakeBTC -var setupRan bool // Please add your own APIkeys to do correct due diligence testing. const ( @@ -25,34 +25,28 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { - if !setupRan { - l.SetDefaults() +func TestMain(m *testing.M) { + l.SetDefaults() + cfg := config.GetConfig() + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("LakeBTC load config error", err) } -} + lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC") + if err != nil { + log.Fatal("LakeBTC Setup() init error") + } + lakebtcConfig.API.AuthenticatedSupport = true + lakebtcConfig.API.Credentials.Key = apiKey + lakebtcConfig.API.Credentials.Secret = apiSecret + lakebtcConfig.Features.Enabled.Websocket = true + err = l.Setup(lakebtcConfig) + if err != nil { + log.Fatal("LakeBTC setup error", err) + } + l.API.Endpoints.WebsocketURL = lakeBTCWSURL -func TestSetup(t *testing.T) { - if !setupRan { - cfg := config.GetConfig() - err := cfg.LoadConfig("../../testdata/configtest.json", true) - if err != nil { - log.Fatal("LakeBTC load config error", err) - } - lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC") - if err != nil { - t.Error("LakeBTC Setup() init error") - } - lakebtcConfig.API.AuthenticatedSupport = true - lakebtcConfig.API.Credentials.Key = apiKey - lakebtcConfig.API.Credentials.Secret = apiSecret - lakebtcConfig.Features.Enabled.Websocket = true - err = l.Setup(lakebtcConfig) - if err != nil { - t.Fatal("LakeBTC setup error", err) - } - l.API.Endpoints.WebsocketURL = lakeBTCWSURL - setupRan = true - } + os.Exit(m.Run()) } func TestFetchTradablePairs(t *testing.T) { @@ -171,7 +165,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() l.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -259,20 +253,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - l.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := l.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - l.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -286,9 +274,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - l.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -308,9 +293,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - l.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -335,15 +317,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - l.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -361,15 +339,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - l.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -392,6 +366,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := l.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -399,8 +376,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - l.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -424,9 +399,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - l.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -439,9 +411,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - l.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -469,8 +438,6 @@ func TestGetDepositAddress(t *testing.T) { // TestWsConn websocket connection test func TestWsConn(t *testing.T) { - TestSetDefaults(t) - TestSetup(t) if !l.Websocket.IsEnabled() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -484,8 +451,6 @@ func TestWsConn(t *testing.T) { // TestWsTradeProcessing logic test func TestWsTradeProcessing(t *testing.T) { - TestSetDefaults(t) - TestSetup(t) l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() json := `{"trades":[{"type":"sell","date":1564985787,"price":"11913.02","amount":"0.49"}]}` @@ -497,8 +462,6 @@ func TestWsTradeProcessing(t *testing.T) { // TestWsTickerProcessing logic test func TestWsTickerProcessing(t *testing.T) { - TestSetDefaults(t) - TestSetup(t) const testChanSize = 26 l.Websocket.DataHandler = make(chan interface{}, testChanSize) l.Websocket.TrafficAlert = make(chan struct{}, testChanSize) @@ -519,8 +482,6 @@ func TestGetCurrencyFromChannel(t *testing.T) { // TestWsOrderbookProcessing logic test func TestWsOrderbookProcessing(t *testing.T) { - TestSetDefaults(t) - TestSetup(t) l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() json := `{"asks":[["11905.66","0.0019"],["11905.73","0.0015"],["11906.43","0.0013"],["11906.62","0.0019"],["11907.25","11.087"],["11907.66","0.0006"],["11907.73","0.3113"],["11907.84","0.0006"],["11908.37","0.0016"],["11908.86","10.3786"],["11909.54","4.2955"],["11910.15","0.0012"],["11910.56","13.5505"],["11911.06","0.0011"],["11911.37","0.0023"]],"bids":[["11905.55","0.0171"],["11904.43","0.0225"],["11903.31","0.0223"],["11902.2","0.0027"],["11901.92","1.002"],["11901.6","0.0015"],["11901.49","0.0012"],["11901.08","0.0227"],["11900.93","0.0009"],["11900.53","1.662"],["11900.08","0.001"],["11900.01","3.6745"],["11899.96","0.003"],["11899.91","0.0006"],["11899.44","0.0013"]]}` diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 3be7eb82..0e15e043 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -334,13 +334,18 @@ func (l *LakeBTC) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { isBuyOrder := s.OrderSide == order.Buy response, err := l.Trade(isBuyOrder, s.Amount, s.Price, s.Pair.Lower().String()) + if err != nil { + return submitOrderResponse, err + } if response.ID > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.ID, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true + + submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -369,8 +374,8 @@ func (l *LakeBTC) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, err } var ordersToCancel []string - for _, order := range openOrders { - ordersToCancel = append(ordersToCancel, strconv.FormatInt(order.ID, 10)) + for i := range openOrders { + ordersToCancel = append(ordersToCancel, strconv.FormatInt(openOrders[i].ID, 10)) } return cancelAllOrdersResponse, l.CancelExistingOrders(ordersToCancel) diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go index 05309537..4aa2c4a7 100644 --- a/exchanges/lbank/lbank_test.go +++ b/exchanges/lbank/lbank_test.go @@ -1,9 +1,9 @@ package lbank import ( - "fmt" "log" "os" + "strconv" "testing" "time" @@ -91,7 +91,7 @@ func TestGetMarketDepths(t *testing.T) { func TestGetTrades(t *testing.T) { t.Parallel() _, err := l.GetTrades(testCurrencyPair, "600", - fmt.Sprintf("%v", time.Now().Unix())) + strconv.FormatInt(time.Now().Unix(), 10)) if err != nil { t.Error(err) } @@ -104,7 +104,7 @@ func TestGetTrades(t *testing.T) { func TestGetKlines(t *testing.T) { t.Parallel() _, err := l.GetKlines(testCurrencyPair, "600", "minute1", - fmt.Sprintf("%v", time.Now().Unix())) + strconv.FormatInt(time.Now().Unix(), 10)) if err != nil { t.Error(err) } diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index e9fbd8b3..d5eab666 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -315,6 +315,9 @@ func (l *Lbank) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } resp.IsOrderPlaced = true resp.OrderID = tempResp.OrderID + if s.OrderType == order.Market { + resp.FullyMatched = true + } return resp, nil } diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index dd37c207..4e97dc89 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -579,7 +579,7 @@ func (l *LocalBitcoins) WalletSend(address string, amount float64, pin int64) er path := localbitcoinsAPIWalletSend if pin > 0 { - values.Set("pincode", fmt.Sprintf("%v", pin)) + values.Set("pincode", strconv.FormatInt(pin, 10)) path = localbitcoinsAPIWalletSendPin } diff --git a/exchanges/localbitcoins/localbitcoins_test.go b/exchanges/localbitcoins/localbitcoins_test.go index 8962afe1..d1c6bc0e 100644 --- a/exchanges/localbitcoins/localbitcoins_test.go +++ b/exchanges/localbitcoins/localbitcoins_test.go @@ -96,7 +96,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { t.Parallel() var feeBuilder = setFeeBuilder() l.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -189,7 +189,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := l.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", @@ -275,7 +274,6 @@ func TestCancelExchangeOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -300,7 +298,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 8b7fa490..2d212f0e 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -2,7 +2,9 @@ package okcoin import ( "encoding/json" + "log" "net/http" + "os" "strings" "sync" "testing" @@ -34,43 +36,25 @@ var testSetupRan bool var spotCurrency = currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-").Lower().String() var websocketEnabled bool -// TestSetDefaults Sets standard default settings for running a test -func TestSetDefaults(t *testing.T) { - if o.Name != OKGroupExchange { - o.SetDefaults() - } - if o.Name != OKGroupExchange { - t.Errorf("%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.API.Credentials.Key == apiKey && o.API.Credentials.Secret == apiSecret && - o.API.Credentials.ClientID == passphrase && apiKey != "" && apiSecret != "" && passphrase != "" { - return - } +func TestMain(m *testing.M) { + o.SetDefaults() o.ExchangeName = OKGroupExchange cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Okcoin load config error", err) + log.Fatal("Okcoin load config error", err) } okcoinConfig, err := cfg.GetExchangeConfig(OKGroupExchange) if err != nil { - t.Fatalf("%v Setup() init error", OKGroupExchange) + log.Fatalf("%v Setup() init error", OKGroupExchange) } if okcoinConfig.Features.Enabled.Websocket { websocketEnabled = true @@ -84,11 +68,13 @@ func TestSetup(t *testing.T) { okcoinConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL err = o.Setup(okcoinConfig) if err != nil { - t.Fatal("OKCoin setup error", err) + log.Fatal("OKCoin setup error", err) } testSetupRan = true o.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() o.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() + + os.Exit(m.Run()) } func areTestAPIKeysSet() bool { @@ -106,14 +92,12 @@ func testStandardErrorHandling(t *testing.T, err error) { // 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 { @@ -129,7 +113,6 @@ func TestGetAccountWalletInformation(t *testing.T) { // TestGetAccountWalletInformationForCurrency API endpoint test func TestGetAccountWalletInformationForCurrency(t *testing.T) { - TestSetDefaults(t) resp, err := o.GetAccountWalletInformation(currency.BTC.String()) if areTestAPIKeysSet() { if err != nil { @@ -173,7 +156,6 @@ func TestAccountWithdrawRequest(t *testing.T) { // TestGetAccountWithdrawalFee API endpoint test func TestGetAccountWithdrawalFee(t *testing.T) { - TestSetDefaults(t) resp, err := o.GetAccountWithdrawalFee("") if areTestAPIKeysSet() { if err != nil { @@ -189,7 +171,6 @@ func TestGetAccountWithdrawalFee(t *testing.T) { // TestGetWithdrawalFeeForCurrency API endpoint test func TestGetAccountWithdrawalFeeForCurrency(t *testing.T) { - TestSetDefaults(t) resp, err := o.GetAccountWithdrawalFee(currency.BTC.String()) if areTestAPIKeysSet() { if err != nil { @@ -205,63 +186,54 @@ func TestGetAccountWithdrawalFeeForCurrency(t *testing.T) { // 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(currency.BTC.String()) 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(currency.BTC.String()) 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(currency.BTC.String()) 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(currency.BTC.String()) testStandardErrorHandling(t, err) } // TestGetSpotBillDetailsForCurrency API endpoint test func TestGetSpotBillDetailsForCurrency(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: currency.BTC.String(), Limit: 100, @@ -272,7 +244,6 @@ func TestGetSpotBillDetailsForCurrency(t *testing.T) { // TestGetSpotBillDetailsForCurrencyBadLimit API logic test func TestGetSpotBillDetailsForCurrencyBadLimit(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: currency.BTC.String(), Limit: -1, @@ -316,7 +287,7 @@ func TestPlaceSpotOrderMarket(t *testing.T) { // TestPlaceMultipleSpotOrders API endpoint test func TestPlaceMultipleSpotOrders(t *testing.T) { TestSetRealOrderDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -325,7 +296,7 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } _, errs := o.PlaceMultipleSpotOrders(request) @@ -336,8 +307,7 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { // TestPlaceMultipleSpotOrdersOverCurrencyLimits API logic test func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { - TestSetDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -346,11 +316,11 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, - order, - order, - order, - order, + ord, + ord, + ord, + ord, + ord, } _, errs := o.PlaceMultipleSpotOrders(request) @@ -361,8 +331,7 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { // TestPlaceMultipleSpotOrdersOverPairLimits API logic test func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { - TestSetDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -371,7 +340,7 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } pairs := currency.Pairs{ @@ -382,8 +351,8 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { } for x := range pairs { - order.InstrumentID = pairs[x].Format("-", false).String() - request = append(request, order) + ord.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, ord) } _, errs := o.PlaceMultipleSpotOrders(request) @@ -439,7 +408,6 @@ func TestCancelMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { // TestGetSpotOrders API endpoint test func TestGetSpotOrders(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOrdersRequest{ InstrumentID: spotCurrency, Status: "all", @@ -450,7 +418,6 @@ func TestGetSpotOrders(t *testing.T) { // TestGetSpotOpenOrders API endpoint test func TestGetSpotOpenOrders(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOpenOrdersRequest{} _, err := o.GetSpotOpenOrders(request) testStandardErrorHandling(t, err) @@ -458,7 +425,6 @@ func TestGetSpotOpenOrders(t *testing.T) { // TestGetSpotOrder API endpoint test func TestGetSpotOrder(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOrderRequest{ OrderID: "-1234", InstrumentID: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-").Upper().String(), @@ -469,7 +435,6 @@ func TestGetSpotOrder(t *testing.T) { // TestGetSpotTransactionDetails API endpoint test func TestGetSpotTransactionDetails(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotTransactionDetailsRequest{ OrderID: 1234, InstrumentID: spotCurrency, @@ -480,7 +445,6 @@ func TestGetSpotTransactionDetails(t *testing.T) { // TestGetSpotTokenPairDetails API endpoint test func TestGetSpotTokenPairDetails(t *testing.T) { - TestSetDefaults(t) _, err := o.GetSpotTokenPairDetails() if err != nil { t.Error(err) @@ -489,7 +453,6 @@ func TestGetSpotTokenPairDetails(t *testing.T) { // TestGetSpotAllTokenPairsInformation API endpoint test func TestGetSpotAllTokenPairsInformation(t *testing.T) { - TestSetDefaults(t) _, err := o.GetSpotAllTokenPairsInformation() if err != nil { t.Error(err) @@ -498,7 +461,6 @@ func TestGetSpotAllTokenPairsInformation(t *testing.T) { // TestGetSpotAllTokenPairsInformationForCurrency API endpoint test func TestGetSpotAllTokenPairsInformationForCurrency(t *testing.T) { - TestSetDefaults(t) _, err := o.GetSpotAllTokenPairsInformationForCurrency(spotCurrency) if err != nil { t.Error(err) @@ -507,7 +469,6 @@ func TestGetSpotAllTokenPairsInformationForCurrency(t *testing.T) { // TestGetSpotFilledOrdersInformation API endpoint test func TestGetSpotFilledOrdersInformation(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotFilledOrdersInformationRequest{ InstrumentID: spotCurrency, } @@ -519,7 +480,6 @@ func TestGetSpotFilledOrdersInformation(t *testing.T) { // TestGetSpotMarketData API endpoint test func TestGetSpotMarketData(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotMarketDataRequest{ InstrumentID: spotCurrency, Granularity: 604800, @@ -532,21 +492,18 @@ func TestGetSpotMarketData(t *testing.T) { // 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, @@ -557,14 +514,12 @@ func TestGetMarginBillDetails(t *testing.T) { // 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) } @@ -631,7 +586,7 @@ func TestPlaceMarginOrderMarket(t *testing.T) { // TestPlaceMultipleMarginOrders API endpoint test func TestPlaceMultipleMarginOrders(t *testing.T) { TestSetRealOrderDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -641,7 +596,7 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } _, errs := o.PlaceMultipleMarginOrders(request) @@ -652,8 +607,7 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { // TestPlaceMultipleMarginOrdersOverCurrencyLimits API logic test func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { - TestSetDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -663,11 +617,11 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, - order, - order, - order, - order, + ord, + ord, + ord, + ord, + ord, } _, errs := o.PlaceMultipleMarginOrders(request) @@ -678,8 +632,7 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { // TestPlaceMultipleMarginOrdersOverPairLimits API logic test func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { - TestSetDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -689,7 +642,7 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } pairs := currency.Pairs{ @@ -700,8 +653,8 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { } for x := range pairs { - order.InstrumentID = pairs[x].Format("-", false).String() - request = append(request, order) + ord.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, ord) } _, errs := o.PlaceMultipleMarginOrders(request) @@ -752,7 +705,6 @@ func TestCancelMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { // TestGetMarginOrders API endpoint test func TestGetMarginOrders(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOrdersRequest{ InstrumentID: spotCurrency, Status: "all", @@ -763,7 +715,6 @@ func TestGetMarginOrders(t *testing.T) { // TestGetMarginOpenOrders API endpoint test func TestGetMarginOpenOrders(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOpenOrdersRequest{} _, err := o.GetMarginOpenOrders(request) testStandardErrorHandling(t, err) @@ -771,7 +722,6 @@ func TestGetMarginOpenOrders(t *testing.T) { // TestGetMarginOrder API endpoint test func TestGetMarginOrder(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOrderRequest{ OrderID: "1234", InstrumentID: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-").Upper().String(), @@ -782,7 +732,6 @@ func TestGetMarginOrder(t *testing.T) { // TestGetMarginTransactionDetails API endpoint test func TestGetMarginTransactionDetails(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotTransactionDetailsRequest{ OrderID: 1234, InstrumentID: spotCurrency, @@ -797,7 +746,6 @@ func TestGetMarginTransactionDetails(t *testing.T) { // Attempts to subscribe to a channel that doesn't exist // Will log in if credentials are present func TestSendWsMessages(t *testing.T) { - TestSetDefaults(t) if !o.Websocket.IsEnabled() && !o.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -850,7 +798,6 @@ func TestGetAssetTypeFromTableName(t *testing.T) { // TestGetWsChannelWithoutOrderType logic test func TestGetWsChannelWithoutOrderType(t *testing.T) { - TestSetDefaults(t) str := "spot/depth5:BTC-USDT" expected := "depth5" resp := o.GetWsChannelWithoutOrderType(str) @@ -872,7 +819,6 @@ func TestGetWsChannelWithoutOrderType(t *testing.T) { // TestOrderBookUpdateChecksumCalculator logic test func TestOrderBookUpdateChecksumCalculator(t *testing.T) { - TestSetDefaults(t) if !websocketEnabled { t.Skip("Websocket not enabled, skipping") } @@ -902,7 +848,6 @@ func TestOrderBookUpdateChecksumCalculator(t *testing.T) { // TestOrderBookUpdateChecksumCalculatorWithDash logic test func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { - TestSetDefaults(t) if !websocketEnabled { t.Skip("Websocket not enabled, skipping") } @@ -964,7 +909,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() o.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -976,7 +921,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - TestSetDefaults(t) var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic if resp, err := o.GetFee(feeBuilder); resp != float64(0.0015) || err != nil { @@ -1031,7 +975,6 @@ func TestGetFee(t *testing.T) { // TestFormatWithdrawPermissions helper test func TestFormatWithdrawPermissions(t *testing.T) { - TestSetDefaults(t) expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText withdrawPermissions := o.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { @@ -1149,3 +1092,25 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) } } + +// TestGetOrderbook logic test +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: "BTC-USDT"}, + asset.Spot) + if err != nil { + t.Error(err) + } + + _, err = o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: "Payload"}, + asset.Futures) + if err == nil { + t.Error("error cannot be nil") + } + + _, err = o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, + asset.PerpetualSwap) + if err == nil { + t.Error("error cannot be nil") + } +} diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index fa9d6e2a..70ae6246 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -3,7 +3,9 @@ package okex import ( "encoding/json" "fmt" + "log" "net/http" + "os" "strconv" "strings" "sync" @@ -31,49 +33,30 @@ const ( canManipulateRealOrders = false ) -var testSetupRan bool -var o = OKEX{} +var o OKEX var spotCurrency = currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "-").Lower().String() var websocketEnabled bool -// TestSetDefaults Sets standard default settings for running a test -func TestSetDefaults(t *testing.T) { - if o.Name != OKGroupExchange { - o.SetDefaults() - } - if o.Name != OKGroupExchange { - t.Errorf("%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.API.Credentials.Key == apiKey && o.API.Credentials.Secret == apiSecret && - o.API.Credentials.ClientID == passphrase && apiKey != "" && apiSecret != "" && passphrase != "" { - return - } +func TestMain(m *testing.M) { + o.SetDefaults() o.ExchangeName = OKGroupExchange cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Okex load config error", err) + log.Fatal("Okex load config error", err) } okexConfig, err := cfg.GetExchangeConfig(OKGroupExchange) if err != nil { - t.Fatalf("%v Setup() init error", OKGroupExchange) + log.Fatalf("%v Setup() init error", OKGroupExchange) } if okexConfig.Features.Enabled.Websocket { websocketEnabled = true @@ -86,11 +69,12 @@ func TestSetup(t *testing.T) { okexConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL err = o.Setup(okexConfig) if err != nil { - t.Fatal("Okex setup error", err) + log.Fatal("Okex setup error", err) } - testSetupRan = true o.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() o.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() + + os.Exit(m.Run()) } func areTestAPIKeysSet() bool { @@ -108,7 +92,6 @@ func testStandardErrorHandling(t *testing.T, err error) { // TestGetAccountCurrencies API endpoint test func TestGetAccountCurrencies(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountCurrencies() testStandardErrorHandling(t, err) @@ -116,7 +99,6 @@ func TestGetAccountCurrencies(t *testing.T) { // TestGetAccountWalletInformation API endpoint test func TestGetAccountWalletInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() resp, err := o.GetAccountWalletInformation("") if areTestAPIKeysSet() { @@ -133,7 +115,6 @@ func TestGetAccountWalletInformation(t *testing.T) { // TestGetAccountWalletInformationForCurrency API endpoint test func TestGetAccountWalletInformationForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() resp, err := o.GetAccountWalletInformation(currency.BTC.String()) if areTestAPIKeysSet() { @@ -182,7 +163,6 @@ func TestAccountWithdrawRequest(t *testing.T) { // TestGetAccountWithdrawalFee API endpoint test func TestGetAccountWithdrawalFee(t *testing.T) { - TestSetDefaults(t) t.Parallel() resp, err := o.GetAccountWithdrawalFee("") if areTestAPIKeysSet() { @@ -199,7 +179,6 @@ func TestGetAccountWithdrawalFee(t *testing.T) { // TestGetWithdrawalFeeForCurrency API endpoint test func TestGetAccountWithdrawalFeeForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() resp, err := o.GetAccountWithdrawalFee(currency.BTC.String()) if areTestAPIKeysSet() { @@ -216,7 +195,6 @@ func TestGetAccountWithdrawalFeeForCurrency(t *testing.T) { // TestGetAccountWithdrawalHistory API endpoint test func TestGetAccountWithdrawalHistory(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountWithdrawalHistory("") testStandardErrorHandling(t, err) @@ -224,7 +202,6 @@ func TestGetAccountWithdrawalHistory(t *testing.T) { // TestGetAccountWithdrawalHistoryForCurrency API endpoint test func TestGetAccountWithdrawalHistoryForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountWithdrawalHistory(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -232,7 +209,6 @@ func TestGetAccountWithdrawalHistoryForCurrency(t *testing.T) { // TestGetAccountBillDetails API endpoint test func TestGetAccountBillDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountBillDetails(okgroup.GetAccountBillDetailsRequest{}) testStandardErrorHandling(t, err) @@ -240,7 +216,6 @@ func TestGetAccountBillDetails(t *testing.T) { // TestGetAccountDepositAddressForCurrency API endpoint test func TestGetAccountDepositAddressForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountDepositAddressForCurrency(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -248,7 +223,6 @@ func TestGetAccountDepositAddressForCurrency(t *testing.T) { // TestGetAccountDepositHistory API endpoint test func TestGetAccountDepositHistory(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountDepositHistory("") testStandardErrorHandling(t, err) @@ -256,7 +230,6 @@ func TestGetAccountDepositHistory(t *testing.T) { // TestGetAccountDepositHistoryForCurrency API endpoint test func TestGetAccountDepositHistoryForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountDepositHistory(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -264,7 +237,6 @@ func TestGetAccountDepositHistoryForCurrency(t *testing.T) { // TestGetSpotTradingAccounts API endpoint test func TestGetSpotTradingAccounts(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSpotTradingAccounts() testStandardErrorHandling(t, err) @@ -272,7 +244,6 @@ func TestGetSpotTradingAccounts(t *testing.T) { // TestGetSpotTradingAccountsForCurrency API endpoint test func TestGetSpotTradingAccountsForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSpotTradingAccountForCurrency(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -280,7 +251,6 @@ func TestGetSpotTradingAccountsForCurrency(t *testing.T) { // TestGetSpotBillDetailsForCurrency API endpoint test func TestGetSpotBillDetailsForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: currency.BTC.String(), @@ -292,7 +262,6 @@ func TestGetSpotBillDetailsForCurrency(t *testing.T) { // TestGetSpotBillDetailsForCurrencyBadLimit API logic test func TestGetSpotBillDetailsForCurrencyBadLimit(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: currency.BTC.String(), @@ -340,7 +309,7 @@ func TestPlaceSpotOrderMarket(t *testing.T) { func TestPlaceMultipleSpotOrders(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -349,7 +318,7 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } _, errs := o.PlaceMultipleSpotOrders(request) @@ -360,9 +329,8 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { // TestPlaceMultipleSpotOrdersOverCurrencyLimits API logic test func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { - TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -371,11 +339,11 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, - order, - order, - order, - order, + ord, + ord, + ord, + ord, + ord, } _, errs := o.PlaceMultipleSpotOrders(request) @@ -386,9 +354,8 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { // TestPlaceMultipleSpotOrdersOverPairLimits API logic test func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { - TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -397,7 +364,7 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } pairs := currency.Pairs{ @@ -408,8 +375,8 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { } for x := range pairs { - order.InstrumentID = pairs[x].Format("-", false).String() - request = append(request, order) + ord.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, ord) } _, errs := o.PlaceMultipleSpotOrders(request) @@ -468,7 +435,6 @@ func TestCancelMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { // TestGetSpotOrders API endpoint test func TestGetSpotOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOrdersRequest{ InstrumentID: spotCurrency, @@ -481,7 +447,6 @@ func TestGetSpotOrders(t *testing.T) { // TestGetSpotOpenOrders API endpoint test func TestGetSpotOpenOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOpenOrdersRequest{} _, err := o.GetSpotOpenOrders(request) @@ -490,7 +455,6 @@ func TestGetSpotOpenOrders(t *testing.T) { // TestGetSpotOrder API endpoint test func TestGetSpotOrder(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOrderRequest{ OrderID: "-1234", @@ -502,7 +466,6 @@ func TestGetSpotOrder(t *testing.T) { // TestGetSpotTransactionDetails API endpoint test func TestGetSpotTransactionDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotTransactionDetailsRequest{ OrderID: 1234, @@ -514,7 +477,6 @@ func TestGetSpotTransactionDetails(t *testing.T) { // TestGetSpotTokenPairDetails API endpoint test func TestGetSpotTokenPairDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSpotTokenPairDetails() if err != nil { @@ -524,7 +486,6 @@ func TestGetSpotTokenPairDetails(t *testing.T) { // TestGetSpotAllTokenPairsInformation API endpoint test func TestGetSpotAllTokenPairsInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSpotAllTokenPairsInformation() if err != nil { @@ -534,7 +495,6 @@ func TestGetSpotAllTokenPairsInformation(t *testing.T) { // TestGetSpotAllTokenPairsInformationForCurrency API endpoint test func TestGetSpotAllTokenPairsInformationForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSpotAllTokenPairsInformationForCurrency(spotCurrency) if err != nil { @@ -544,7 +504,6 @@ func TestGetSpotAllTokenPairsInformationForCurrency(t *testing.T) { // TestGetSpotFilledOrdersInformation API endpoint test func TestGetSpotFilledOrdersInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotFilledOrdersInformationRequest{ InstrumentID: spotCurrency, @@ -557,7 +516,6 @@ func TestGetSpotFilledOrdersInformation(t *testing.T) { // TestGetSpotMarketData API endpoint test func TestGetSpotMarketData(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotMarketDataRequest{ InstrumentID: spotCurrency, @@ -571,7 +529,6 @@ func TestGetSpotMarketData(t *testing.T) { // TestGetMarginTradingAccounts API endpoint test func TestGetMarginTradingAccounts(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetMarginTradingAccounts() testStandardErrorHandling(t, err) @@ -579,7 +536,6 @@ func TestGetMarginTradingAccounts(t *testing.T) { // TestGetMarginTradingAccountsForCurrency API endpoint test func TestGetMarginTradingAccountsForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetMarginTradingAccountsForCurrency(spotCurrency) testStandardErrorHandling(t, err) @@ -587,7 +543,6 @@ func TestGetMarginTradingAccountsForCurrency(t *testing.T) { // TestGetMarginBillDetails API endpoint test func TestGetMarginBillDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetMarginBillDetailsRequest{ InstrumentID: spotCurrency, @@ -600,7 +555,6 @@ func TestGetMarginBillDetails(t *testing.T) { // TestGetMarginAccountSettings API endpoint test func TestGetMarginAccountSettings(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetMarginAccountSettings("") testStandardErrorHandling(t, err) @@ -608,7 +562,6 @@ func TestGetMarginAccountSettings(t *testing.T) { // TestGetMarginAccountSettingsForCurrency API endpoint test func TestGetMarginAccountSettingsForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetMarginAccountSettings(spotCurrency) testStandardErrorHandling(t, err) @@ -681,7 +634,7 @@ func TestPlaceMarginOrderMarket(t *testing.T) { func TestPlaceMultipleMarginOrders(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -691,7 +644,7 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } _, errs := o.PlaceMultipleMarginOrders(request) @@ -702,9 +655,8 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { // TestPlaceMultipleMarginOrdersOverCurrencyLimits API logic test func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { - TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -714,11 +666,11 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, - order, - order, - order, - order, + ord, + ord, + ord, + ord, + ord, } _, errs := o.PlaceMultipleMarginOrders(request) @@ -729,9 +681,8 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { // TestPlaceMultipleMarginOrdersOverPairLimits API logic test func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { - TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -741,7 +692,7 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } pairs := currency.Pairs{ @@ -752,8 +703,8 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { } for x := range pairs { - order.InstrumentID = pairs[x].Format("-", false).String() - request = append(request, order) + ord.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, ord) } _, errs := o.PlaceMultipleMarginOrders(request) @@ -807,7 +758,6 @@ func TestCancelMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { // TestGetMarginOrders API endpoint test func TestGetMarginOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOrdersRequest{ InstrumentID: spotCurrency, @@ -819,7 +769,6 @@ func TestGetMarginOrders(t *testing.T) { // TestGetMarginOpenOrders API endpoint test func TestGetMarginOpenOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOpenOrdersRequest{} _, err := o.GetMarginOpenOrders(request) @@ -828,7 +777,6 @@ func TestGetMarginOpenOrders(t *testing.T) { // TestGetMarginOrder API endpoint test func TestGetMarginOrder(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOrderRequest{ OrderID: "1234", @@ -840,7 +788,6 @@ func TestGetMarginOrder(t *testing.T) { // TestGetMarginTransactionDetails API endpoint test func TestGetMarginTransactionDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotTransactionDetailsRequest{ OrderID: 1234, @@ -869,7 +816,6 @@ func getFutureInstrumentID() string { // TestGetFuturesPostions API endpoint test func TestGetFuturesPostions(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesPostions() testStandardErrorHandling(t, err) @@ -877,7 +823,6 @@ func TestGetFuturesPostions(t *testing.T) { // TestGetFuturesPostionsForCurrency API endpoint test func TestGetFuturesPostionsForCurrency(t *testing.T) { - TestSetDefaults(t) currencyContract := getFutureInstrumentID() _, err := o.GetFuturesPostionsForCurrency(currencyContract) testStandardErrorHandling(t, err) @@ -885,7 +830,6 @@ func TestGetFuturesPostionsForCurrency(t *testing.T) { // TestGetFuturesAccountOfAllCurrencies API endpoint test func TestGetFuturesAccountOfAllCurrencies(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesAccountOfAllCurrencies() testStandardErrorHandling(t, err) @@ -893,7 +837,6 @@ func TestGetFuturesAccountOfAllCurrencies(t *testing.T) { // TestGetFuturesAccountOfACurrency API endpoint test func TestGetFuturesAccountOfACurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesAccountOfACurrency(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -901,7 +844,6 @@ func TestGetFuturesAccountOfACurrency(t *testing.T) { // TestGetFuturesLeverage API endpoint test func TestGetFuturesLeverage(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesLeverage(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -922,7 +864,6 @@ func TestSetFuturesLeverage(t *testing.T) { // TestGetFuturesBillDetails API endpoint test func TestGetFuturesBillDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesBillDetails(okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: currency.BTC.String(), @@ -987,7 +928,6 @@ func TestCancelMultipleFuturesOrders(t *testing.T) { // TestGetFuturesOrderList API endpoint test func TestGetFuturesOrderList(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesOrderList(okgroup.GetFuturesOrdersListRequest{ InstrumentID: getFutureInstrumentID(), Status: 6, @@ -997,7 +937,6 @@ func TestGetFuturesOrderList(t *testing.T) { // TestGetFuturesOrderDetails API endpoint test func TestGetFuturesOrderDetails(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesOrderDetails(okgroup.GetFuturesOrderDetailsRequest{ InstrumentID: getFutureInstrumentID(), OrderID: 1, @@ -1007,7 +946,6 @@ func TestGetFuturesOrderDetails(t *testing.T) { // TestGetFuturesTransactionDetails API endpoint test func TestGetFuturesTransactionDetails(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesTransactionDetails(okgroup.GetFuturesTransactionDetailsRequest{ InstrumentID: getFutureInstrumentID(), OrderID: 1, @@ -1017,7 +955,6 @@ func TestGetFuturesTransactionDetails(t *testing.T) { // TestGetFuturesContractInformation API endpoint test func TestGetFuturesContractInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesContractInformation() if err != nil { @@ -1027,7 +964,6 @@ func TestGetFuturesContractInformation(t *testing.T) { // TestGetAllFuturesTokenInfo API endpoint test func TestGetAllFuturesTokenInfo(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAllFuturesTokenInfo() if err != nil { @@ -1037,7 +973,6 @@ func TestGetAllFuturesTokenInfo(t *testing.T) { // TestGetAllFuturesTokenInfo API endpoint test func TestGetFuturesTokenInfoForCurrency(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesTokenInfoForCurrency(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1046,7 +981,6 @@ func TestGetFuturesTokenInfoForCurrency(t *testing.T) { // TestGetFuturesFilledOrder API endpoint test func TestGetFuturesFilledOrder(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesFilledOrder(okgroup.GetFuturesFilledOrderRequest{ InstrumentID: getFutureInstrumentID(), }) @@ -1055,14 +989,12 @@ func TestGetFuturesFilledOrder(t *testing.T) { // 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(err) @@ -1071,7 +1003,6 @@ func TestGetFuturesIndices(t *testing.T) { // TestGetFuturesHoldAmount API endpoint test func TestGetFuturesExchangeRates(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesExchangeRates() if err != nil { @@ -1081,7 +1012,6 @@ func TestGetFuturesExchangeRates(t *testing.T) { // TestGetFuturesHoldAmount API endpoint test func TestGetFuturesEstimatedDeliveryPrice(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesEstimatedDeliveryPrice(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1090,7 +1020,6 @@ func TestGetFuturesEstimatedDeliveryPrice(t *testing.T) { // TestGetFuturesOpenInterests API endpoint test func TestGetFuturesOpenInterests(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesOpenInterests(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1099,7 +1028,6 @@ func TestGetFuturesOpenInterests(t *testing.T) { // TestGetFuturesOpenInterests API endpoint test func TestGetFuturesCurrentPriceLimit(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesCurrentPriceLimit(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1108,7 +1036,6 @@ func TestGetFuturesCurrentPriceLimit(t *testing.T) { // TestGetFuturesCurrentMarkPrice API endpoint test func TestGetFuturesCurrentMarkPrice(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesCurrentMarkPrice(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1117,7 +1044,6 @@ func TestGetFuturesCurrentMarkPrice(t *testing.T) { // TestGetFuturesForceLiquidatedOrders API endpoint test func TestGetFuturesForceLiquidatedOrders(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesForceLiquidatedOrders(okgroup.GetFuturesForceLiquidatedOrdersRequest{ InstrumentID: getFutureInstrumentID(), Status: "1", @@ -1129,14 +1055,12 @@ func TestGetFuturesForceLiquidatedOrders(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() _, err := o.GetSwapPostions() testStandardErrorHandling(t, err) @@ -1144,7 +1068,6 @@ func TestGetSwapPostions(t *testing.T) { // TestGetSwapPostionsForContract API endpoint test func TestGetSwapPostionsForContract(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapPostionsForContract(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) testStandardErrorHandling(t, err) @@ -1152,7 +1075,6 @@ func TestGetSwapPostionsForContract(t *testing.T) { // TestGetSwapAccountOfAllCurrency API endpoint test func TestGetSwapAccountOfAllCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapAccountOfAllCurrency() testStandardErrorHandling(t, err) @@ -1160,7 +1082,6 @@ func TestGetSwapAccountOfAllCurrency(t *testing.T) { // TestGetSwapAccountSettingsOfAContract API endpoint test func TestGetSwapAccountSettingsOfAContract(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapAccountSettingsOfAContract(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) testStandardErrorHandling(t, err) @@ -1168,7 +1089,6 @@ func TestGetSwapAccountSettingsOfAContract(t *testing.T) { // TestSetSwapLeverageLevelOfAContract API endpoint test func TestSetSwapLeverageLevelOfAContract(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.SetSwapLeverageLevelOfAContract(okgroup.SetSwapLeverageLevelOfAContractRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1181,7 +1101,6 @@ func TestSetSwapLeverageLevelOfAContract(t *testing.T) { // TestGetSwapAccountSettingsOfAContract API endpoint test func TestGetSwapBillDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapBillDetails(okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1252,7 +1171,6 @@ func TestCancelMultipleSwapOrders(t *testing.T) { // TestGetSwapOrderList API endpoint test func TestGetSwapOrderList(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapOrderList(okgroup.GetSwapOrderListRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1263,7 +1181,6 @@ func TestGetSwapOrderList(t *testing.T) { // TestGetSwapOrderDetails API endpoint test func TestGetSwapOrderDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapOrderDetails(okgroup.GetSwapOrderDetailsRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1274,7 +1191,6 @@ func TestGetSwapOrderDetails(t *testing.T) { // TestGetSwapTransactionDetails API endpoint test func TestGetSwapTransactionDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapTransactionDetails(okgroup.GetSwapTransactionDetailsRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1285,7 +1201,6 @@ func TestGetSwapTransactionDetails(t *testing.T) { // TestGetSwapContractInformation API endpoint test func TestGetSwapContractInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapContractInformation() if err != nil { @@ -1295,7 +1210,6 @@ func TestGetSwapContractInformation(t *testing.T) { // TestGetAllSwapTokensInformation API endpoint test func TestGetAllSwapTokensInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAllSwapTokensInformation() if err != nil { @@ -1305,7 +1219,6 @@ func TestGetAllSwapTokensInformation(t *testing.T) { // TestGetSwapTokensInformationForCurrency API endpoint test func TestGetSwapTokensInformationForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapTokensInformationForCurrency(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1315,7 +1228,6 @@ func TestGetSwapTokensInformationForCurrency(t *testing.T) { // TestGetSwapFilledOrdersData API endpoint test func TestGetSwapFilledOrdersData(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapFilledOrdersData(&okgroup.GetSwapFilledOrdersDataRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1328,7 +1240,6 @@ func TestGetSwapFilledOrdersData(t *testing.T) { // TestGetSwapMarketData API endpoint test func TestGetSwapMarketData(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSwapMarketDataRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1342,7 +1253,6 @@ func TestGetSwapMarketData(t *testing.T) { // TestGetSwapIndeces API endpoint test func TestGetSwapIndeces(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapIndices(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1352,7 +1262,6 @@ func TestGetSwapIndeces(t *testing.T) { // TestGetSwapExchangeRates API endpoint test func TestGetSwapExchangeRates(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapExchangeRates() if err != nil { @@ -1362,7 +1271,6 @@ func TestGetSwapExchangeRates(t *testing.T) { // TestGetSwapOpenInterest API endpoint test func TestGetSwapOpenInterest(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapOpenInterest(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1372,7 +1280,6 @@ func TestGetSwapOpenInterest(t *testing.T) { // TestGetSwapCurrentPriceLimits API endpoint test func TestGetSwapCurrentPriceLimits(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapCurrentPriceLimits(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1382,7 +1289,6 @@ func TestGetSwapCurrentPriceLimits(t *testing.T) { // TestGetSwapForceLiquidatedOrders API endpoint test func TestGetSwapForceLiquidatedOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapForceLiquidatedOrders(okgroup.GetSwapForceLiquidatedOrdersRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1395,7 +1301,6 @@ func TestGetSwapForceLiquidatedOrders(t *testing.T) { // TestGetSwapOnHoldAmountForOpenOrders API endpoint test func TestGetSwapOnHoldAmountForOpenOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapOnHoldAmountForOpenOrders(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) testStandardErrorHandling(t, err) @@ -1403,7 +1308,6 @@ func TestGetSwapOnHoldAmountForOpenOrders(t *testing.T) { // TestGetSwapNextSettlementTime API endpoint test func TestGetSwapNextSettlementTime(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapNextSettlementTime(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1413,7 +1317,6 @@ func TestGetSwapNextSettlementTime(t *testing.T) { // TestGetSwapMarkPrice API endpoint test func TestGetSwapMarkPrice(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapMarkPrice(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1423,7 +1326,6 @@ func TestGetSwapMarkPrice(t *testing.T) { // TestGetSwapFundingRateHistory API endpoint test func TestGetSwapFundingRateHistory(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapFundingRateHistory(okgroup.GetSwapFundingRateHistoryRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1436,7 +1338,6 @@ func TestGetSwapFundingRateHistory(t *testing.T) { // TestGetETT API endpoint test func TestGetETT(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetETT() testStandardErrorHandling(t, err) @@ -1444,7 +1345,6 @@ func TestGetETT(t *testing.T) { // TestGetETTAccountInformationForCurrency API endpoint test func TestGetETTAccountInformationForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetETTBillsDetails(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -1452,7 +1352,6 @@ func TestGetETTAccountInformationForCurrency(t *testing.T) { // TestGetETTBillsDetails API endpoint test func TestGetETTBillsDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetETTBillsDetails(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -1487,7 +1386,6 @@ func TestCancelETTOrder(t *testing.T) { // 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, @@ -1501,7 +1399,6 @@ func TestGetETTOrderList(t *testing.T) { // TestGetETTOrderDetails API endpoint test func TestGetETTOrderDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetETTOrderDetails("888845020785408") testStandardErrorHandling(t, err) @@ -1510,7 +1407,6 @@ func TestGetETTOrderDetails(t *testing.T) { // TestGetETTConstituents API endpoint test func TestGetETTConstituents(t *testing.T) { t.Skip("ETT currently unavailable") - TestSetDefaults(t) t.Parallel() _, err := o.GetETTConstituents("OK06ETT") if err != nil { @@ -1521,7 +1417,6 @@ func TestGetETTConstituents(t *testing.T) { // TestGetETTSettlementPriceHistory API endpoint test func TestGetETTSettlementPriceHistory(t *testing.T) { t.Skip("ETT currently unavailable") - TestSetDefaults(t) t.Parallel() _, err := o.GetETTSettlementPriceHistory("OK06ETT") if err != nil { @@ -1535,7 +1430,6 @@ func TestGetETTSettlementPriceHistory(t *testing.T) { // Attempts to subscribe to a channel that doesn't exist // Will log in if credentials are present func TestSendWsMessages(t *testing.T) { - TestSetDefaults(t) if !o.Websocket.IsEnabled() && !o.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -1588,7 +1482,6 @@ func TestGetAssetTypeFromTableName(t *testing.T) { // TestGetWsChannelWithoutOrderType logic test func TestGetWsChannelWithoutOrderType(t *testing.T) { - TestSetDefaults(t) t.Parallel() str := "spot/depth5:BTC-USDT" expected := "depth5" @@ -1611,7 +1504,6 @@ func TestGetWsChannelWithoutOrderType(t *testing.T) { // TestOrderBookUpdateChecksumCalculator logic test func TestOrderBookUpdateChecksumCalculator(t *testing.T) { - TestSetDefaults(t) 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 @@ -1638,7 +1530,6 @@ func TestOrderBookUpdateChecksumCalculator(t *testing.T) { // TestOrderBookUpdateChecksumCalculatorWithDash logic test func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { - TestSetDefaults(t) 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 @@ -1696,7 +1587,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() o.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -1708,7 +1599,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - TestSetDefaults(t) t.Parallel() var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic @@ -1770,7 +1660,6 @@ func TestGetFee(t *testing.T) { // TestFormatWithdrawPermissions helper test func TestFormatWithdrawPermissions(t *testing.T) { - TestSetDefaults(t) t.Parallel() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText withdrawPermissions := o.FormatWithdrawPermissions() @@ -1901,3 +1790,25 @@ func TestWithdrawInternationalBank(t *testing.T) { err) } } + +// TestGetOrderbook logic test +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: "BTC-USDT"}, + asset.Spot) + if err != nil { + t.Error(err) + } + contract := getFutureInstrumentID() + _, err = o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: contract}, + asset.Futures) + if err != nil { + t.Error(err) + } + + _, err = o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, + asset.PerpetualSwap) + if err != nil { + t.Error(err) + } +} diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go index afd95cf5..0b6b7c93 100644 --- a/exchanges/okgroup/okgroup.go +++ b/exchanges/okgroup/okgroup.go @@ -233,8 +233,8 @@ func (o *OKGroup) PlaceMultipleSpotOrders(request []PlaceOrderRequest) (map[stri var orderErrors []error for currency, orderResponse := range resp { - for _, order := range orderResponse { - if !order.Result { + for i := range orderResponse { + if !orderResponse[i].Result { orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency)) } } @@ -262,15 +262,15 @@ func (o *OKGroup) CancelMultipleSpotOrders(request CancelMultipleSpotOrdersReque } for currency, orderResponse := range resp { - for _, order := range orderResponse { + for i := range orderResponse { cancellationResponse := CancelMultipleSpotOrdersResponse{ - OrderID: order.OrderID, - Result: order.Result, - ClientOID: order.ClientOID, + OrderID: orderResponse[i].OrderID, + Result: orderResponse[i].Result, + ClientOID: orderResponse[i].ClientOID, } - if !order.Result { - cancellationResponse.Error = fmt.Errorf("order %v for currency %v failed to be cancelled", order.OrderID, currency) + if !orderResponse[i].Result { + cancellationResponse.Error = fmt.Errorf("order %v for currency %v failed to be cancelled", orderResponse[i].OrderID, currency) } resp[currency] = append(resp[currency], cancellationResponse) @@ -454,8 +454,8 @@ func (o *OKGroup) PlaceMultipleMarginOrders(request []PlaceOrderRequest) (map[st var orderErrors []error for currency, orderResponse := range resp { - for _, order := range orderResponse { - if !order.Result { + for i := range orderResponse { + if !orderResponse[i].Result { orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency)) } } @@ -484,9 +484,9 @@ func (o *OKGroup) CancelMultipleMarginOrders(request CancelMultipleSpotOrdersReq var 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)) + for i := range orderResponse { + if !orderResponse[i].Result { + orderErrors = append(orderErrors, fmt.Errorf("order %v for currency %v failed to be cancelled", orderResponse[i].OrderID, currency)) } } } diff --git a/exchanges/okgroup/okgroup_test.go b/exchanges/okgroup/okgroup_test.go deleted file mode 100644 index edcbf618..00000000 --- a/exchanges/okgroup/okgroup_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package okgroup - -import ( - "log" - "os" - "testing" - "time" - - "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" -) - -const ( - apiKey = "" - apiSecret = "" - - testAPIURL = "https://www.okex.com/api/" - testAPIVersion = "/v3/" -) - -var o OKGroup - -func TestMain(m *testing.M) { - cfg := config.GetConfig() - err := cfg.LoadConfig("../../testdata/configtest.json", true) - if err != nil { - log.Fatal("okgroup load config error", err) - } - okgroup, err := cfg.GetExchangeConfig("Okex") - if err != nil { - log.Fatal("okgroup Setup() init error", err) - } - - okgroup.API.AuthenticatedSupport = true - okgroup.API.Credentials.Key = apiKey - okgroup.API.Credentials.Secret = apiSecret - o.API.Endpoints.URL = testAPIURL - o.APIVersion = testAPIVersion - - o.Requester = request.New("okgroup_test_things", - request.NewRateLimit(time.Second, 10), - request.NewRateLimit(time.Second, 10), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - ) - o.Websocket = wshandler.New() - - err = o.Setup(okgroup) - if err != nil { - log.Fatal("okgroup setup error", err) - } - os.Exit(m.Run()) -} - -func TestGetOrderbook(t *testing.T) { - t.Parallel() - _, err := o.GetOrderBook(GetOrderBookRequest{InstrumentID: "BTC-USDT"}, - asset.Spot) - if err != nil { - t.Error(err) - } - - // futures expire and break test, will need to mock this in the future - _, err = o.GetOrderBook(GetOrderBookRequest{InstrumentID: "Payload"}, - asset.Futures) - if err == nil { - t.Error("error cannot be nil") - } - - _, err = o.GetOrderBook(GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, - asset.PerpetualSwap) - if err != nil { - t.Error(err) - } -} diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index 91658ba0..c3dcca42 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -273,6 +273,9 @@ func (o *OKGroup) SubmitOrder(s *order.Submit) (resp order.SubmitResponse, err e resp.IsOrderPlaced = orderResponse.Result resp.OrderID = orderResponse.OrderID + if s.OrderType == order.Market { + resp.FullyMatched = true + } return } diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 2d7b7cd3..ed019e9d 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -1,6 +1,7 @@ package order import ( + "errors" "strings" "testing" "time" @@ -398,3 +399,138 @@ func TestSortOrdersByOrderType(t *testing.T) { orders[0].OrderType) } } + +var stringsToOrderSide = []struct { + in string + out Side + err error +}{ + {"buy", Buy, nil}, + {"BUY", Buy, nil}, + {"bUy", Buy, nil}, + {"sell", Sell, nil}, + {"SELL", Sell, nil}, + {"sElL", Sell, nil}, + {"bid", Bid, nil}, + {"BID", Bid, nil}, + {"bId", Bid, nil}, + {"ask", Ask, nil}, + {"ASK", Ask, nil}, + {"aSk", Ask, nil}, + {"any", AnySide, nil}, + {"ANY", AnySide, nil}, + {"aNy", AnySide, nil}, + {"woahMan", Buy, errors.New("woahMan not recognised as side type")}, +} + +func TestStringToOrderSide(t *testing.T) { + for i := 0; i < len(stringsToOrderSide); i++ { + testData := &stringsToOrderSide[i] + t.Run(testData.in, func(t *testing.T) { + out, err := StringToOrderSide(testData.in) + if err != nil { + if err.Error() != testData.err.Error() { + t.Error("Unexpected error", err) + } + } else if out != testData.out { + t.Errorf("Unexpected output %v. Expected %v", out, testData.out) + } + }) + } +} + +var stringsToOrderType = []struct { + in string + out Type + err error +}{ + {"limit", Limit, nil}, + {"LIMIT", Limit, nil}, + {"lImIt", Limit, nil}, + {"market", Market, nil}, + {"MARKET", Market, nil}, + {"mArKeT", Market, nil}, + {"immediate_or_cancel", ImmediateOrCancel, nil}, + {"IMMEDIATE_OR_CANCEL", ImmediateOrCancel, nil}, + {"iMmEdIaTe_Or_CaNcEl", ImmediateOrCancel, nil}, + {"stop", Stop, nil}, + {"STOP", Stop, nil}, + {"sToP", Stop, nil}, + {"trailingstop", TrailingStop, nil}, + {"TRAILINGSTOP", TrailingStop, nil}, + {"tRaIlInGsToP", TrailingStop, nil}, + {"any", AnyType, nil}, + {"ANY", AnyType, nil}, + {"aNy", AnyType, nil}, + {"woahMan", Unknown, errors.New("woahMan not recognised as order type")}, +} + +func TestStringToOrderType(t *testing.T) { + for i := 0; i < len(stringsToOrderType); i++ { + testData := &stringsToOrderType[i] + t.Run(testData.in, func(t *testing.T) { + out, err := StringToOrderType(testData.in) + if err != nil { + if err.Error() != testData.err.Error() { + t.Error("Unexpected error", err) + } + } else if out != testData.out { + t.Errorf("Unexpected output %v. Expected %v", out, testData.out) + } + }) + } +} + +var stringsToOrderStatus = []struct { + in string + out Status + err error +}{ + {"any", AnyStatus, nil}, + {"ANY", AnyStatus, nil}, + {"aNy", AnyStatus, nil}, + {"new", New, nil}, + {"NEW", New, nil}, + {"nEw", New, nil}, + {"active", Active, nil}, + {"ACTIVE", Active, nil}, + {"aCtIvE", Active, nil}, + {"partially_filled", PartiallyFilled, nil}, + {"PARTIALLY_FILLED", PartiallyFilled, nil}, + {"pArTiAlLy_FiLlEd", PartiallyFilled, nil}, + {"filled", Filled, nil}, + {"FILLED", Filled, nil}, + {"fIlLeD", Filled, nil}, + {"canceled", Cancelled, nil}, + {"CANCELED", Cancelled, nil}, + {"cAnCelEd", Cancelled, nil}, + {"pending_cancel", PendingCancel, nil}, + {"PENDING_CANCEL", PendingCancel, nil}, + {"pENdInG_cAnCeL", PendingCancel, nil}, + {"rejected", Rejected, nil}, + {"REJECTED", Rejected, nil}, + {"rEjEcTeD", Rejected, nil}, + {"expired", Expired, nil}, + {"EXPIRED", Expired, nil}, + {"eXpIrEd", Expired, nil}, + {"hidden", Hidden, nil}, + {"HIDDEN", Hidden, nil}, + {"hIdDeN", Hidden, nil}, + {"woahMan", UnknownStatus, errors.New("woahMan not recognised as order STATUS")}, +} + +func TestStringToOrderStatus(t *testing.T) { + for i := 0; i < len(stringsToOrderStatus); i++ { + testData := &stringsToOrderStatus[i] + t.Run(testData.in, func(t *testing.T) { + out, err := StringToOrderStatus(testData.in) + if err != nil { + if err.Error() != testData.err.Error() { + t.Error("Unexpected error", err) + } + } else if out != testData.out { + t.Errorf("Unexpected output %v. Expected %v", out, testData.out) + } + }) + } +} diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 0a02e9ed..ef869e02 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -50,6 +50,7 @@ type Submit struct { // SubmitResponse is what is returned after submitting an order to an exchange type SubmitResponse struct { IsOrderPlaced bool + FullyMatched bool OrderID string } @@ -127,7 +128,7 @@ type Detail struct { // TradeHistory holds exchange history data type TradeHistory struct { Timestamp time.Time - TID int64 + TID string Price float64 Amount float64 Exchange string diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 85452186..1fb1d2c7 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -1,6 +1,7 @@ package order import ( + "fmt" "sort" "strings" "time" @@ -10,18 +11,18 @@ import ( // NewOrder creates a new order and returns a an orderID func NewOrder(exchangeName string, amount, price float64) int { - order := &Order{} + ord := &Order{} if len(Orders) == 0 { - order.OrderID = 0 + ord.OrderID = 0 } else { - order.OrderID = len(Orders) + ord.OrderID = len(Orders) } - order.Exchange = exchangeName - order.Amount = amount - order.Price = price - Orders = append(Orders, order) - return order.OrderID + ord.Exchange = exchangeName + ord.Amount = amount + ord.Price = price + Orders = append(Orders, ord) + return ord.OrderID } // DeleteOrder deletes orders by ID and returns state @@ -300,3 +301,72 @@ func SortOrdersBySide(orders *[]Detail, reverse bool) { sort.Sort(ByOrderSide(*orders)) } } + +// StringToOrderSide for converting case insensitive order side +// and returning a real Side +func StringToOrderSide(side string) (Side, error) { + switch { + case strings.EqualFold(side, Buy.String()): + return Buy, nil + case strings.EqualFold(side, Sell.String()): + return Sell, nil + case strings.EqualFold(side, Bid.String()): + return Bid, nil + case strings.EqualFold(side, Ask.String()): + return Ask, nil + case strings.EqualFold(side, AnySide.String()): + return AnySide, nil + default: + return Side(""), fmt.Errorf("%s not recognised as side type", side) + } +} + +// StringToOrderType for converting case insensitive order type +// and returning a real Type +func StringToOrderType(oType string) (Type, error) { + switch { + case strings.EqualFold(oType, Limit.String()): + return Limit, nil + case strings.EqualFold(oType, Market.String()): + return Market, nil + case strings.EqualFold(oType, ImmediateOrCancel.String()): + return ImmediateOrCancel, nil + case strings.EqualFold(oType, Stop.String()): + return Stop, nil + case strings.EqualFold(oType, TrailingStop.String()): + return TrailingStop, nil + case strings.EqualFold(oType, AnyType.String()): + return AnyType, nil + default: + return Unknown, fmt.Errorf("%s not recognised as order type", oType) + } +} + +// StringToOrderStatus for converting case insensitive order status +// and returning a real Status +func StringToOrderStatus(status string) (Status, error) { + switch { + case strings.EqualFold(status, AnyStatus.String()): + return AnyStatus, nil + case strings.EqualFold(status, New.String()): + return New, nil + case strings.EqualFold(status, Active.String()): + return Active, nil + case strings.EqualFold(status, PartiallyFilled.String()): + return PartiallyFilled, nil + case strings.EqualFold(status, Filled.String()): + return Filled, nil + case strings.EqualFold(status, Cancelled.String()): + return Cancelled, nil + case strings.EqualFold(status, PendingCancel.String()): + return PendingCancel, nil + case strings.EqualFold(status, Rejected.String()): + return Rejected, nil + case strings.EqualFold(status, Expired.String()): + return Expired, nil + case strings.EqualFold(status, Hidden.String()): + return Hidden, nil + default: + return UnknownStatus, fmt.Errorf("%s not recognised as order STATUS", status) + } +} diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 9c750478..8c767abf 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -158,9 +158,9 @@ func TestVerify(t *testing.T) { func TestCalculateTotalBids(t *testing.T) { t.Parallel() - currency := currency.NewPairFromStrings("BTC", "USD") + curr := currency.NewPairFromStrings("BTC", "USD") base := Base{ - Pair: currency, + Pair: curr, Bids: []Item{{Price: 100, Amount: 10}}, LastUpdated: time.Now(), } @@ -173,9 +173,9 @@ func TestCalculateTotalBids(t *testing.T) { func TestCalculateTotaAsks(t *testing.T) { t.Parallel() - currency := currency.NewPairFromStrings("BTC", "USD") + curr := currency.NewPairFromStrings("BTC", "USD") base := Base{ - Pair: currency, + Pair: curr, Asks: []Item{{Price: 100, Amount: 10}}, } @@ -187,10 +187,10 @@ func TestCalculateTotaAsks(t *testing.T) { func TestUpdate(t *testing.T) { t.Parallel() - currency := currency.NewPairFromStrings("BTC", "USD") + curr := currency.NewPairFromStrings("BTC", "USD") timeNow := time.Now() base := Base{ - Pair: currency, + Pair: curr, Asks: []Item{{Price: 100, Amount: 10}}, Bids: []Item{{Price: 200, Amount: 10}}, LastUpdated: timeNow, diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index a89aca41..cc939aca 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -103,7 +103,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() p.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, @@ -202,9 +202,7 @@ func TestFormatWithdrawPermissions(t *testing.T) { expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := p.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, @@ -284,7 +282,6 @@ func TestCancelExchangeOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -310,7 +307,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index b5078c45..18b91525 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -469,12 +469,12 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []inter func (p *Poloniex) GenerateDefaultSubscriptions() { var subscriptions []wshandler.WebsocketChannelSubscription subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: fmt.Sprintf("%v", wsTickerDataID), + Channel: strconv.FormatInt(wsTickerDataID, 10), }) if p.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: fmt.Sprintf("%v", wsAccountNotificationID), + Channel: strconv.FormatInt(wsAccountNotificationID, 10), }) } @@ -495,9 +495,9 @@ func (p *Poloniex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscr Command: "subscribe", } switch { - case strings.EqualFold(fmt.Sprintf("%v", wsAccountNotificationID), channelToSubscribe.Channel): + case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), channelToSubscribe.Channel): return p.wsSendAuthorisedCommand("subscribe") - case strings.EqualFold(fmt.Sprintf("%v", wsTickerDataID), channelToSubscribe.Channel): + case strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), channelToSubscribe.Channel): subscriptionRequest.Channel = wsTickerDataID default: subscriptionRequest.Channel = channelToSubscribe.Currency.String() @@ -511,9 +511,9 @@ func (p *Poloniex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubs Command: "unsubscribe", } switch { - case strings.EqualFold(fmt.Sprintf("%v", wsAccountNotificationID), channelToSubscribe.Channel): + case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), channelToSubscribe.Channel): return p.wsSendAuthorisedCommand("unsubscribe") - case strings.EqualFold(fmt.Sprintf("%v", wsTickerDataID), channelToSubscribe.Channel): + case strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), channelToSubscribe.Channel): unsubscriptionRequest.Channel = wsTickerDataID default: unsubscriptionRequest.Channel = channelToSubscribe.Currency.String() diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 02ed3818..496e1e63 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -238,13 +238,14 @@ func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType asset.Item return tickerPrice, err } - for _, x := range p.GetEnabledPairs(assetType) { + enabledPairs := p.GetEnabledPairs(assetType) + for i := range enabledPairs { var tp ticker.Price - curr := p.FormatExchangeCurrency(x, assetType).String() + curr := p.FormatExchangeCurrency(enabledPairs[i], assetType).String() if _, ok := tick[curr]; !ok { continue } - tp.Pair = x + tp.Pair = enabledPairs[i] tp.Ask = tick[curr].LowestAsk tp.Bid = tick[curr].HighestBid tp.High = tick[curr].High24Hr @@ -287,9 +288,9 @@ func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.I return orderBook, err } - for _, x := range p.GetEnabledPairs(assetType) { - currency := p.FormatExchangeCurrency(x, assetType).String() - data, ok := orderbookNew.Data[currency] + enabledPairs := p.GetEnabledPairs(assetType) + for i := range enabledPairs { + data, ok := orderbookNew.Data[p.FormatExchangeCurrency(enabledPairs[i], assetType).String()] if !ok { continue } @@ -307,7 +308,7 @@ func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.I Amount: data.Asks[y].Amount, Price: data.Asks[y].Price}) } orderBook.Asks = obItems - orderBook.Pair = x + orderBook.Pair = enabledPairs[i] orderBook.ExchangeName = p.Name orderBook.AssetType = assetType @@ -370,13 +371,18 @@ func (p *Poloniex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { false, fillOrKill, isBuyOrder) + if err != nil { + return submitOrderResponse, err + } if response.OrderNumber > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true + + submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/websocket/wshandler/wshandler.go b/exchanges/websocket/wshandler/wshandler.go index 27895cbd..be0ee3c2 100644 --- a/exchanges/websocket/wshandler/wshandler.go +++ b/exchanges/websocket/wshandler/wshandler.go @@ -327,6 +327,17 @@ func (w *Websocket) IsConnectionMonitorRunning() bool { return w.connectionMonitorRunning } +// CanUseAuthenticatedWebsocketForWrapper Handles a common check to +// verify whether a wrapper can use an authenticated websocket endpoint +func (w *Websocket) CanUseAuthenticatedWebsocketForWrapper() bool { + if w.IsConnected() && w.CanUseAuthenticatedEndpoints() { + return true + } else if w.IsConnected() && !w.CanUseAuthenticatedEndpoints() { + log.Infof(log.WebsocketMgr, WebsocketNotAuthenticatedUsingRest, w.exchangeName) + } + return false +} + // SetWebsocketURL sets websocket URL func (w *Websocket) SetWebsocketURL(websocketURL string) { if websocketURL == "" || websocketURL == config.WebsocketURLNonDefaultMessage { @@ -629,7 +640,6 @@ func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header } dialer.Proxy = http.ProxyURL(proxy) } - var err error var conStatus *http.Response w.Connection, conStatus, err = dialer.Dial(w.URL, headers) @@ -640,7 +650,7 @@ func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header return fmt.Errorf("%v Error: %v", w.URL, err) } if w.Verbose { - log.Infof(log.WebsocketMgr, "%v Websocket connected", w.ExchangeName) + log.Infof(log.WebsocketMgr, "%v Websocket connected to %s", w.ExchangeName, w.URL) } w.setConnectedStatus(true) return nil @@ -703,6 +713,12 @@ func (w *WebsocketConnection) WaitForResult(id int64, wg *sync.WaitGroup) { for k := range w.IDResponses { if k == id { w.Unlock() + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } return } } diff --git a/exchanges/websocket/wshandler/wshandler_test.go b/exchanges/websocket/wshandler/wshandler_test.go index 25ac23f6..bfb20816 100644 --- a/exchanges/websocket/wshandler/wshandler_test.go +++ b/exchanges/websocket/wshandler/wshandler_test.go @@ -678,3 +678,22 @@ func readMessages(wc *WebsocketConnection, t *testing.T) { } } } + +// TestCanUseAuthenticatedWebsocketForWrapper logic test +func TestCanUseAuthenticatedWebsocketForWrapper(t *testing.T) { + ws := &Websocket{} + resp := ws.CanUseAuthenticatedWebsocketForWrapper() + if resp { + t.Error("Expected false, `connected` is false") + } + ws.setConnectedStatus(true) + resp = ws.CanUseAuthenticatedWebsocketForWrapper() + if resp { + t.Error("Expected false, `connected` is true and `CanUseAuthenticatedEndpoints` is false") + } + ws.canUseAuthenticatedEndpoints = true + resp = ws.CanUseAuthenticatedWebsocketForWrapper() + if !resp { + t.Error("Expected true, `connected` and `CanUseAuthenticatedEndpoints` is true") + } +} diff --git a/exchanges/websocket/wshandler/wshandler_types.go b/exchanges/websocket/wshandler/wshandler_types.go index c4e9b341..22e47266 100644 --- a/exchanges/websocket/wshandler/wshandler_types.go +++ b/exchanges/websocket/wshandler/wshandler_types.go @@ -17,7 +17,8 @@ const ( WebsocketNotEnabled = "exchange_websocket_not_enabled" manageSubscriptionsDelay = 5 * time.Second // connection monitor time delays and limits - connectionMonitorDelay = 2 * time.Second + connectionMonitorDelay = 2 * time.Second + WebsocketNotAuthenticatedUsingRest = "%v - Websocket not authenticated, using REST" ) // Websocket defines a return type for websocket connections via the interface diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index 3307cb71..4a2dafab 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -1,7 +1,9 @@ package yobit import ( + "log" "math" + "os" "testing" "time" @@ -22,19 +24,16 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { y.SetDefaults() -} - -func TestSetup(t *testing.T) { yobitConfig := config.GetConfig() err := yobitConfig.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Yobit load config error", err) + log.Fatal("Yobit load config error", err) } conf, err := yobitConfig.GetExchangeConfig("Yobit") if err != nil { - t.Fatal("Yobit init error", err) + log.Fatal("Yobit init error", err) } conf.API.Credentials.Key = apiKey conf.API.Credentials.Secret = apiSecret @@ -42,8 +41,9 @@ func TestSetup(t *testing.T) { err = y.Setup(conf) if err != nil { - t.Fatal("Yobit setup error", err) + log.Fatal("Yobit setup error", err) } + os.Exit(m.Run()) } func TestFetchTradablePairs(t *testing.T) { @@ -167,7 +167,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() y.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -179,8 +179,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - y.SetDefaults() - TestSetup(t) var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic @@ -317,20 +315,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - y.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := y.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - y.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, @@ -346,9 +338,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - y.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, @@ -372,9 +361,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - y.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -400,15 +386,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - y.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -426,15 +408,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - y.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -457,6 +435,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := y.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -464,8 +445,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - y.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -489,9 +468,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - y.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -506,9 +482,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - y.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -523,7 +496,7 @@ func TestWithdrawInternationalBank(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { _, err := y.GetDepositAddress(currency.BTC, "") if err != nil { t.Error("GetDepositAddress() Expected error") diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 400383a4..363f8208 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -187,20 +187,22 @@ func (y *Yobit) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric return tickerPrice, err } - for _, x := range y.GetEnabledPairs(assetType) { - curr := y.FormatExchangeCurrency(x, assetType).Lower().String() + enabledPairs := y.GetEnabledPairs(assetType) + for i := range enabledPairs { + curr := y.FormatExchangeCurrency(enabledPairs[i], assetType).Lower().String() if _, ok := result[curr]; !ok { continue } + resultCurr := result[curr] var tickerPrice ticker.Price - tickerPrice.Pair = x - tickerPrice.Last = result[curr].Last - tickerPrice.Ask = result[curr].Sell - tickerPrice.Bid = result[curr].Buy - tickerPrice.Last = result[curr].Last - tickerPrice.Low = result[curr].Low - tickerPrice.QuoteVolume = result[curr].VolumeCurrent - tickerPrice.Volume = result[curr].Vol + tickerPrice.Pair = enabledPairs[i] + tickerPrice.Last = resultCurr.Last + tickerPrice.Ask = resultCurr.Sell + tickerPrice.Bid = resultCurr.Buy + tickerPrice.Last = resultCurr.Last + tickerPrice.Low = resultCurr.Low + tickerPrice.QuoteVolume = resultCurr.VolumeCurrent + tickerPrice.Volume = resultCurr.Vol err = ticker.ProcessTicker(y.Name, &tickerPrice, assetType) if err != nil { @@ -323,13 +325,15 @@ func (y *Yobit) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.OrderSide.String(), s.Amount, s.Price) + if err != nil { + return submitOrderResponse, err + } if response > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + + submitOrderResponse.IsOrderPlaced = true + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index 998d4016..dfb5d457 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -423,10 +423,10 @@ func (z *ZB) Withdraw(currency, address, safepassword string, amount, fees float vals := url.Values{} vals.Set("accesskey", z.API.Credentials.Key) - vals.Set("amount", fmt.Sprintf("%v", amount)) + vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) vals.Set("currency", currency) - vals.Set("fees", fmt.Sprintf("%v", fees)) - vals.Set("itransfer", fmt.Sprintf("%v", itransfer)) + vals.Set("fees", strconv.FormatFloat(fees, 'f', -1, 64)) + vals.Set("itransfer", strconv.FormatBool(itransfer)) vals.Set("method", "withdraw") vals.Set("recieveAddr", address) vals.Set("safePwd", safepassword) diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index c0bf48d4..3182e16e 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -3,7 +3,10 @@ package zb import ( "encoding/json" "fmt" + "log" "net/http" + "os" + "strconv" "testing" "github.com/gorilla/websocket" @@ -25,19 +28,16 @@ const ( var z ZB var wsSetupRan bool -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { z.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("ZB load config error", err) + log.Fatal("ZB load config error", err) } zbConfig, err := cfg.GetExchangeConfig("ZB") if err != nil { - t.Fatal("ZB Setup() init error", err) + log.Fatal("ZB Setup() init error", err) } zbConfig.API.AuthenticatedSupport = true zbConfig.API.AuthenticatedWebsocketSupport = true @@ -46,16 +46,16 @@ func TestSetup(t *testing.T) { err = z.Setup(zbConfig) if err != nil { - t.Fatal("ZB setup error", err) + log.Fatal("ZB setup error", err) } + + os.Exit(m.Run()) } func setupWsAuth(t *testing.T) { if wsSetupRan { return } - z.SetDefaults() - TestSetup(t) if !z.Websocket.IsEnabled() && !z.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip(wshandler.WebsocketNotEnabled) } @@ -90,11 +90,9 @@ func TestSpotNewOrder(t *testing.T) { Amount: 0.01, Price: 10246.1, } - orderid, err := z.SpotNewOrder(arg) + _, err := z.SpotNewOrder(arg) if err != nil { t.Errorf("ZB SpotNewOrder: %s", err) - } else { - t.Log(orderid) } } @@ -182,7 +180,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() z.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -194,8 +192,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - z.SetDefaults() - TestSetup(t) var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic @@ -272,20 +268,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - z.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := z.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - z.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.XRP, @@ -301,9 +291,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - z.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, OrderSide: order.Buy, @@ -326,9 +313,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - z.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip(fmt.Sprintf("ApiKey: %s. Can place orders: %v", z.API.Credentials.Key, @@ -356,15 +340,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - z.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.XRP, currency.USDT) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -382,15 +362,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - z.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.XRP, currency.USDT) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -427,6 +403,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := z.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -434,8 +413,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - z.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -460,9 +437,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - z.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -475,9 +449,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - z.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -490,7 +461,7 @@ func TestWithdrawInternationalBank(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { _, err := z.GetDepositAddress(currency.BTC, "") if err != nil { t.Error("GetDepositAddress() error PLEASE MAKE SURE YOU CREATE DEPOSIT ADDRESSES VIA ZB.COM", @@ -548,7 +519,7 @@ func TestWsCreateSuUserKey(t *testing.T) { t.Fatal(err) } userID := subUsers.Message[0].UserID - _, err = z.wsCreateSubUserKey(true, true, true, true, "subu", fmt.Sprintf("%v", userID)) + _, err = z.wsCreateSubUserKey(true, true, true, true, "subu", strconv.FormatInt(userID, 10)) if err != nil { t.Fatal(err) } diff --git a/exchanges/zb/zb_types.go b/exchanges/zb/zb_types.go index 6953e841..2dc9308a 100644 --- a/exchanges/zb/zb_types.go +++ b/exchanges/zb/zb_types.go @@ -16,7 +16,7 @@ type OrderbookResponse struct { // AccountsResponseCoin holds the accounts coin details type AccountsResponseCoin struct { - Freez string `json:"freez"` // 冻结资产 + Freeze string `json:"freez"` // 冻结资产 EnName string `json:"enName"` // 币种英文名 UnitDecimal int `json:"unitDecimal"` // 保留小数位 UnName string `json:"cnName"` // 币种中文名 @@ -44,6 +44,9 @@ type Order struct { TradeDate int `json:"trade_date"` TradeMoney float64 `json:"trade_money"` Type int64 `json:"type"` + Fees float64 `json:"fees,omitempty"` + TradePrice float64 `json:"trade_price,omitempty"` + No int64 `json:"no,string,omitempty"` } // AccountsResponse 用户基本信息 diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 4560f582..b4d31804 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -187,40 +187,6 @@ func (z *ZB) WsHandleData() { } } -var wsErrCodes = map[int64]string{ - 1000: "Successful call", - 1001: "General error message", - 1002: "internal error", - 1003: "Verification failed", - 1004: "Financial security password lock", - 1005: "The fund security password is incorrect. Please confirm and re-enter.", - 1006: "Real-name certification is awaiting review or review", - 1007: "Channel is empty", - 1008: "Event is empty", - 1009: "This interface is being maintained", - 1011: "Not open yet", - 1012: "Insufficient permissions", - 1013: "Can not trade, if you have any questions, please contact online customer service", - 1014: "Cannot be sold during the pre-sale period", - 2002: "Insufficient balance in Bitcoin account", - 2003: "Insufficient balance of Litecoin account", - 2005: "Insufficient balance in Ethereum account", - 2006: "Insufficient balance in ETC currency account", - 2007: "Insufficient balance of BTS currency account", - 2008: "Insufficient balance in EOS currency account", - 2009: "Insufficient account balance", - 3001: "Pending order not found", - 3002: "Invalid amount", - 3003: "Invalid quantity", - 3004: "User does not exist", - 3005: "Invalid parameter", - 3006: "Invalid IP or inconsistent with the bound IP", - 3007: "Request time has expired", - 3008: "Transaction history not found", - 4001: "API interface is locked", - 4002: "Request too frequently", -} - // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (z *ZB) GenerateDefaultSubscriptions() { var subscriptions []wshandler.WebsocketChannelSubscription diff --git a/exchanges/zb/zb_websocket_types.go b/exchanges/zb/zb_websocket_types.go index 983c92aa..29275f48 100644 --- a/exchanges/zb/zb_websocket_types.go +++ b/exchanges/zb/zb_websocket_types.go @@ -191,28 +191,12 @@ type WsGetOrderRequest struct { // WsGetOrderResponse contains order data type WsGetOrderResponse struct { - Message string `json:"message"` - No int64 `json:"no,string"` - Code int64 `json:"code"` - Channel string `json:"channel"` - Success bool `json:"success"` - Data WsGetOrderResponseData `json:"data"` -} - -// WsGetOrderResponseData Detailed order data -type WsGetOrderResponseData struct { - Currency string `json:"currency"` - Fees float64 `json:"fees"` - ID string `json:"id"` - Price float64 `json:"price"` - Status int64 `json:"status"` - TotalAmount float64 `json:"total_amount"` - TradeAmount float64 `json:"trade_amount"` - TradePrice float64 `json:"trade_price"` - TradeDate int64 `json:"trade_date"` - TradeMoney float64 `json:"trade_money"` - Type int64 `json:"type"` - No int64 `json:"no,string"` + Message string `json:"message"` + No int64 `json:"no,string"` + Code int64 `json:"code"` + Channel string `json:"channel"` + Success bool `json:"success"` + Data []Order `json:"data"` } // WsGetOrdersRequest get more orders, with no orderID filtering @@ -228,12 +212,12 @@ type WsGetOrdersRequest struct { // WsGetOrdersResponse contains orders data type WsGetOrdersResponse struct { - Message string `json:"message"` - No int64 `json:"no,string"` - Code int64 `json:"code"` - Channel string `json:"channel"` - Success bool `json:"success"` - Data []WsGetOrderResponseData `json:"data"` + Message string `json:"message"` + No int64 `json:"no,string"` + Code int64 `json:"code"` + Channel string `json:"channel"` + Success bool `json:"success"` + Data []Order `json:"data"` } // WsGetOrdersIgnoreTradeTypeRequest ws request @@ -249,12 +233,12 @@ type WsGetOrdersIgnoreTradeTypeRequest struct { // WsGetOrdersIgnoreTradeTypeResponse contains orders data type WsGetOrdersIgnoreTradeTypeResponse struct { - Message string `json:"message"` - No int64 `json:"no,string"` - Code int64 `json:"code"` - Channel string `json:"channel"` - Success bool `json:"success"` - Data []WsGetOrderResponseData `json:"data"` + Message string `json:"message"` + No int64 `json:"no,string"` + Code int64 `json:"code"` + Channel string `json:"channel"` + Success bool `json:"success"` + Data []Order `json:"data"` } // WsGetAccountInfoResponse contains account data @@ -262,23 +246,44 @@ type WsGetAccountInfoResponse struct { Message string `json:"message"` No int64 `json:"no,string"` Data struct { - Coins []struct { - Freez float64 `json:"freez,string"` - EnName string `json:"enName"` - UnitDecimal int64 `json:"unitDecimal"` - CnName string `json:"cnName"` - UnitTag string `json:"unitTag"` - Available float64 `json:"available,string"` - Key string `json:"key"` - } `json:"coins"` - Base struct { - Username string `json:"username"` - TradePasswordEnabled bool `json:"trade_password_enabled"` - AuthGoogleEnabled bool `json:"auth_google_enabled"` - AuthMobileEnabled bool `json:"auth_mobile_enabled"` - } `json:"base"` + Coins []AccountsResponseCoin `json:"coins"` + Base AccountsBaseResponse `json:"base"` } `json:"data"` Code int64 `json:"code"` Channel string `json:"channel"` Success bool `json:"success"` } + +var wsErrCodes = map[int64]string{ + 1000: "Successful call", + 1001: "General error message", + 1002: "internal error", + 1003: "Verification failed", + 1004: "Financial security password lock", + 1005: "The fund security password is incorrect. Please confirm and re-enter.", + 1006: "Real-name certification is awaiting review or review", + 1007: "Channel is empty", + 1008: "Event is empty", + 1009: "This interface is being maintained", + 1011: "Not open yet", + 1012: "Insufficient permissions", + 1013: "Can not trade, if you have any questions, please contact online customer service", + 1014: "Cannot be sold during the pre-sale period", + 2002: "Insufficient balance in Bitcoin account", + 2003: "Insufficient balance of Litecoin account", + 2005: "Insufficient balance in Ethereum account", + 2006: "Insufficient balance in ETC currency account", + 2007: "Insufficient balance of BTS currency account", + 2008: "Insufficient balance in EOS currency account", + 2009: "Insufficient account balance", + 3001: "Pending order not found", + 3002: "Invalid amount", + 3003: "Invalid quantity", + 3004: "User does not exist", + 3005: "Invalid parameter", + 3006: "Invalid IP or inconsistent with the bound IP", + 3007: "Request time has expired", + 3008: "Transaction history not found", + 4001: "API interface is locked", + 4002: "Request too frequently", +} diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 2f9608e9..eac00ae5 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -2,6 +2,7 @@ package zb import ( "errors" + "fmt" "strconv" "strings" "sync" @@ -268,8 +269,9 @@ func (z *ZB) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Ba // UpdateOrderbook updates and returns the orderbook for a currency pair func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := z.GetOrderbook(z.FormatExchangeCurrency(p, - assetType).String()) + curr := z.FormatExchangeCurrency(p, assetType).String() + + orderbookNew, err := z.GetOrderbook(curr) if err != nil { return orderBook, err } @@ -304,25 +306,35 @@ func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.B // ZB exchange func (z *ZB) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - bal, err := z.GetAccountInformation() - if err != nil { - return info, err + var balances []exchange.AccountCurrencyInfo + var coins []AccountsResponseCoin + if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + resp, err := z.wsGetAccountInfoRequest() + if err != nil { + return info, err + } + coins = resp.Data.Coins + } else { + bal, err := z.GetAccountInformation() + if err != nil { + return info, err + } + coins = bal.Result.Coins } - var balances []exchange.AccountCurrencyInfo - for _, data := range bal.Result.Coins { - hold, err := strconv.ParseFloat(data.Freez, 64) + for i := range coins { + hold, err := strconv.ParseFloat(coins[i].Freeze, 64) if err != nil { return info, err } - avail, err := strconv.ParseFloat(data.Available, 64) + avail, err := strconv.ParseFloat(coins[i].Available, 64) if err != nil { return info, err } balances = append(balances, exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(data.EnName), + CurrencyName: currency.NewCode(coins[i].EnName), TotalValue: hold + avail, Hold: hold, }) @@ -348,33 +360,53 @@ func (z *ZB) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchan } // SubmitOrder submits a new order -func (z *ZB) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { +func (z *ZB) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - if err := s.Validate(); err != nil { + err := o.Validate() + if err != nil { return submitOrderResponse, err } - - var oT SpotNewOrderRequestParamsType - if s.OrderSide == order.Buy { - oT = SpotNewOrderRequestParamsTypeBuy + if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var isBuyOrder int64 + if o.OrderSide == order.Buy { + isBuyOrder = 1 + } else { + isBuyOrder = 0 + } + var response *WsSubmitOrderResponse + response, err = z.wsSubmitOrder(o.Pair, o.Amount, o.Price, isBuyOrder) + if err != nil { + return submitOrderResponse, err + } + submitOrderResponse.OrderID = strconv.FormatInt(response.Data.EntrustID, 10) } else { - oT = SpotNewOrderRequestParamsTypeSell - } + var oT SpotNewOrderRequestParamsType + if o.OrderSide == order.Buy { + oT = SpotNewOrderRequestParamsTypeBuy + } else { + oT = SpotNewOrderRequestParamsTypeSell + } - var params = SpotNewOrderRequestParams{ - Amount: s.Amount, - Price: s.Price, - Symbol: s.Pair.Lower().String(), - Type: oT, + var params = SpotNewOrderRequestParams{ + Amount: o.Amount, + Price: o.Price, + Symbol: o.Pair.Lower().String(), + Type: oT, + } + var response int64 + response, err = z.SpotNewOrder(params) + if err != nil { + return submitOrderResponse, err + } + if response > 0 { + submitOrderResponse.OrderID = strconv.FormatInt(response, 10) + } } - response, err := z.SpotNewOrder(params) - if response > 0 { - submitOrderResponse.OrderID = strconv.FormatInt(response, 10) + submitOrderResponse.IsOrderPlaced = true + if o.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -389,8 +421,20 @@ func (z *ZB) CancelOrder(o *order.Cancel) error { if err != nil { return err } - curr := z.FormatExchangeCurrency(o.CurrencyPair, o.AssetType).String() - return z.CancelExistingOrder(orderIDInt, curr) + + if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var response *WsCancelOrderResponse + response, err = z.wsCancelOrder(o.CurrencyPair, orderIDInt) + if err != nil { + return err + } + if !response.Success { + return fmt.Errorf("%v - Could not cancel order %v", z.Name, o.OrderID) + } + return nil + } + return z.CancelExistingOrder(orderIDInt, z.FormatExchangeCurrency(o.CurrencyPair, + o.AssetType).String()) } // CancelAllOrders cancels all orders associated with a currency pair @@ -424,11 +468,12 @@ func (z *ZB) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { } for i := range allOpenOrders { - err := z.CancelExistingOrder(allOpenOrders[i].ID, - allOpenOrders[i].Currency) + err := z.CancelOrder(&order.Cancel{ + OrderID: strconv.FormatInt(allOpenOrders[i].ID, 10), + CurrencyPair: currency.NewPairFromString(allOpenOrders[i].Currency), + }) if err != nil { - ID := strconv.FormatInt(allOpenOrders[i].ID, 10) - cancelAllOrdersResponse.Status[ID] = err.Error() + cancelAllOrdersResponse.Status[strconv.FormatInt(allOpenOrders[i].ID, 10)] = err.Error() } } @@ -539,35 +584,45 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error if req.OrderSide == order.AnySide || req.OrderSide == "" { return nil, errors.New("specific order side is required") } - var allOrders []Order - + var orders []order.Detail var side int64 - if req.OrderSide == order.Buy { - side = 1 - } - for x := range req.Currencies { - for y := int64(1); ; y++ { - fPair := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() - resp, err := z.GetOrders(fPair, y, side) - if err != nil { - return nil, err + if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + for x := range req.Currencies { + for y := int64(1); ; y++ { + resp, err := z.wsGetOrdersIgnoreTradeType(req.Currencies[x], y, 10) + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp.Data...) + if len(resp.Data) != 10 { + break + } } - - if len(resp) == 0 { - break - } - - allOrders = append(allOrders, resp...) - - if len(resp) != 10 { - break + } + } else { + if req.OrderSide == order.Buy { + side = 1 + } + for x := range req.Currencies { + for y := int64(1); ; y++ { + fPair := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() + resp, err := z.GetOrders(fPair, y, side) + if err != nil { + return nil, err + } + if len(resp) == 0 { + break + } + allOrders = append(allOrders, resp...) + if len(resp) != 10 { + break + } } } } - var orders []order.Detail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Currency, z.GetPairFormat(asset.Spot, false).Delimiter)