From 978b91a6927f3f1dfe2af66eace00e7fc1175a5b Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 5 Feb 2019 10:44:05 +1100 Subject: [PATCH] GetActiveOrders/GetOrderHistory wrapper implementation (#239) * Adds signature to all exchange wrappers * Adds funky new OrderHistoryRequest type. Updates signature for GetOrderHistory to use funky new type. Adds tests for GetOrderHistory on all exchanges. Implements GetOrderHistory for ANX * Fixes alphapoint, bitstamp, itbit, zb tests. Adds exchange functions FilterOrdersByStatusAndType, FilterOrdersByTickRange, FilterOrdersByCurrencies to easily filter returned orders. Adds tests for filters. Implements GetOrderHistory wrapper for Binance, bitfinex, bithumb, bitmex. Adds new filter funcs to implementations. * Adds bitstamp wrapper support * Splits up GetOrderHistory into GetOpenOrders and GetOrderHistory wraapper functions to distinguish between active and past. Renames exchange.GetOrderHistoryRequest to exchange.GetOrdersRequest. Renames any API exchange method named GetOpenOrders to GetActiveOrders. Adds test function TestGetOpenOrders for each exchange * Reimplements the split GetOrders and GetOrderHistory for alphapoint, anx, binance, bitfinex, bithumb, bitmex, bitstamp and bittrex. Renames orderType, orderStatus constants. Adds new exchange.FilterOrdersBySide and exchange.FilterOrdersByType and removes old exchange.FilterOrdersByStatusAndType. * Changes orderHistoryRequest to use currencypair array instead of strings, also adds fees and trade breakdown. Removes if statement preventing ANX/BTCMarkets testing. Implements Active order + Order history retrieval for Bittrex and BTCMarkets. * Adds support for coinut and coinbasepro * Adds Exmo support * Adds GateIO support * Adds Gemini support * Adds hitbtc, huobi, hadax, itbit, kraken support for open orders & order history. Fixes switch case break and fallthroughs. Adds filtering to gateio and gemini results * Adds support for LakeBTC, Liqui, Localbitcoin, OKCoin, OKEX * Adds poloniex support * Adds Wex support * Adds Yobit support. Updates Wex support * Adds ZB support. Removes ArrangeActAssert from tests * Changes baseCurrency + quoteCurrency exchange.OrderDetail properties to a pair.CurrencyPair. Adds exchange name to all implementations. Fixes EXMO TestSetup * Removes verbose setting from tests as verbosity increases the amount of noise return when testing. Noise is only helpful when debugging tests to get more helpful information to resolve the issue and so it is unnecessary to have such lengthy output when testing in bulk or via Travis CI. This commit therefore improves readability when there are no issues * Fixes issue where gemini test sandbox api url was overridden. Handles blank response from Gemini * Fixes verbose typo * Removes spacing for old act assert test comments. Limits previous infinite loop to 10 * Fixes issue with filtering where orderside is never specified * Uses proper capitalisation for ServerOrderID and OpenOrders. Reverts commenting out orde_id param for bithumb.GetOrderDetails. Removes unnecessary int logic * Removes JSON ID fields. Uses map where appropriate for exchange order side/type. Updates OrderDetail/GetOrdersRequest type to use time fields. Remvoes comments. Removes inappropriate variable name. Adds AccountID field for alphapoint. Fixes log message formatting. Lowers errorfs to warnfs for time conversion * Adds missed files * Removes blank line * Adds sorting options for orders. Adds concurrency warnings in comments. Adds test for NewCurrencyPairWithDelimiter. Removes (e *Base) from filter funcs. Updates references to filter funcs * Fixes rebase issues. Condenses append loops. * Fixes more receive typos. Removes some inline strings. Adds AskOrderSide and BidOrderSide. Removes hypothetical infinite loop * Fixes issue where allTrades wasn't used in loop. Fixes assignment/typing issues * Fixes formatting --- communications/slack/slack_test.go | 2 - currency/pair/pair.go | 9 + currency/pair/pair_test.go | 23 ++ exchanges/alphapoint/alphapoint.go | 2 +- exchanges/alphapoint/alphapoint_test.go | 56 +++- exchanges/alphapoint/alphapoint_types.go | 34 ++- exchanges/alphapoint/alphapoint_wrapper.go | 92 +++++- exchanges/anx/anx_test.go | 60 ++-- exchanges/anx/anx_types.go | 28 +- exchanges/anx/anx_wrapper.go | 73 ++++- exchanges/binance/binance.go | 5 +- exchanges/binance/binance_test.go | 65 +++- exchanges/binance/binance_wrapper.go | 91 +++++- exchanges/bitfinex/bitfinex.go | 17 +- exchanges/bitfinex/bitfinex_test.go | 62 ++-- exchanges/bitfinex/bitfinex_types.go | 2 +- exchanges/bitfinex/bitfinex_wrapper.go | 121 +++++++- exchanges/bitflyer/bitflyer_test.go | 45 ++- exchanges/bitflyer/bitflyer_wrapper.go | 11 + exchanges/bithumb/bithumb.go | 26 +- exchanges/bithumb/bithumb_test.go | 57 +++- exchanges/bithumb/bithumb_wrapper.go | 87 +++++- exchanges/bitmex/bitmex.go | 4 +- exchanges/bitmex/bitmex_parameters.go | 28 ++ exchanges/bitmex/bitmex_test.go | 61 ++-- exchanges/bitmex/bitmex_types.go | 20 +- exchanges/bitmex/bitmex_websocket.go | 2 +- exchanges/bitmex/bitmex_wrapper.go | 87 +++++- exchanges/bitstamp/bitstamp.go | 2 +- exchanges/bitstamp/bitstamp_test.go | 57 ++-- exchanges/bitstamp/bitstamp_types.go | 13 +- exchanges/bitstamp/bitstamp_wrapper.go | 97 +++++- exchanges/bittrex/bittrex.go | 4 +- exchanges/bittrex/bittrex_test.go | 61 ++-- exchanges/bittrex/bittrex_types.go | 3 + exchanges/bittrex/bittrex_wrapper.go | 92 +++++- exchanges/btcc/btcc_test.go | 46 ++- exchanges/btcc/btcc_wrapper.go | 11 + exchanges/btcmarkets/btcmarkets.go | 29 +- exchanges/btcmarkets/btcmarkets_test.go | 65 ++-- exchanges/btcmarkets/btcmarkets_wrapper.go | 139 ++++++++- exchanges/coinbasepro/coinbasepro_test.go | 61 +++- exchanges/coinbasepro/coinbasepro_wrapper.go | 89 +++++- exchanges/coinut/coinut_test.go | 53 +++- exchanges/coinut/coinut_wrapper.go | 115 ++++++- exchanges/exchange.go | 281 ++++++++++++++++-- exchanges/exchange_test.go | 247 +++++++++++++++ exchanges/exmo/exmo_test.go | 71 ++++- exchanges/exmo/exmo_types.go | 1 + exchanges/exmo/exmo_wrapper.go | 76 ++++- exchanges/gateio/gateio.go | 22 +- exchanges/gateio/gateio_test.go | 57 +++- exchanges/gateio/gateio_types.go | 33 +- exchanges/gateio/gateio_wrapper.go | 77 ++++- exchanges/gemini/gemini.go | 20 +- exchanges/gemini/gemini_test.go | 69 +++-- exchanges/gemini/gemini_types.go | 3 + exchanges/gemini/gemini_wrapper.go | 88 ++++++ exchanges/hitbtc/hitbtc.go | 43 ++- exchanges/hitbtc/hitbtc_test.go | 55 +++- exchanges/hitbtc/hitbtc_types.go | 17 ++ exchanges/hitbtc/hitbtc_wrapper.go | 88 ++++++ exchanges/huobi/huobi.go | 6 +- exchanges/huobi/huobi_test.go | 55 +++- exchanges/huobi/huobi_types.go | 37 +-- exchanges/huobi/huobi_wrapper.go | 91 +++++- exchanges/huobihadax/huobihadax.go | 6 +- exchanges/huobihadax/huobihadax_test.go | 56 +++- exchanges/huobihadax/huobihadax_types.go | 37 +-- exchanges/huobihadax/huobihadax_wrapper.go | 91 +++++- exchanges/itbit/itbit_test.go | 50 +++- exchanges/itbit/itbit_wrapper.go | 97 ++++++ exchanges/kraken/kraken.go | 46 ++- exchanges/kraken/kraken_test.go | 57 ++-- exchanges/kraken/kraken_wrapper.go | 76 +++++ exchanges/lakebtc/lakebtc_test.go | 53 +++- exchanges/lakebtc/lakebtc_wrapper.go | 70 ++++- exchanges/liqui/liqui.go | 4 +- exchanges/liqui/liqui_test.go | 54 +++- exchanges/liqui/liqui_wrapper.go | 47 ++- exchanges/localbitcoins/localbitcoins.go | 2 + exchanges/localbitcoins/localbitcoins_test.go | 53 +++- .../localbitcoins/localbitcoins_wrapper.go | 106 +++++++ exchanges/okcoin/okcoin.go | 6 +- exchanges/okcoin/okcoin_test.go | 54 +++- exchanges/okcoin/okcoin_wrapper.go | 82 ++++- exchanges/okex/okex.go | 44 ++- exchanges/okex/okex_test.go | 55 +++- exchanges/okex/okex_types.go | 23 ++ exchanges/okex/okex_wrapper.go | 84 +++++- exchanges/poloniex/poloniex.go | 64 ++-- exchanges/poloniex/poloniex_test.go | 52 +++- exchanges/poloniex/poloniex_wrapper.go | 82 ++++- exchanges/wex/wex.go | 14 +- exchanges/wex/wex_test.go | 77 +++-- exchanges/wex/wex_types.go | 6 + exchanges/wex/wex_wrapper.go | 72 ++++- exchanges/yobit/yobit.go | 22 +- exchanges/yobit/yobit_test.go | 72 +++-- exchanges/yobit/yobit_types.go | 7 + exchanges/yobit/yobit_wrapper.go | 72 ++++- exchanges/zb/zb.go | 27 +- exchanges/zb/zb_test.go | 56 +++- exchanges/zb/zb_types.go | 20 +- exchanges/zb/zb_wrapper.go | 122 +++++++- 105 files changed, 4797 insertions(+), 765 deletions(-) diff --git a/communications/slack/slack_test.go b/communications/slack/slack_test.go index aa2381b7..6a92da4f 100644 --- a/communications/slack/slack_test.go +++ b/communications/slack/slack_test.go @@ -71,7 +71,6 @@ func TestBuildURL(t *testing.T) { } func TestGetChannelsString(t *testing.T) { - s.Details.Channels = append(s.Details.Channels, struct { Created int `json:"created"` Creator string `json:"creator"` @@ -315,7 +314,6 @@ func TestHandleHelloResponse(t *testing.T) { } func TestHandleReconnectResponse(t *testing.T) { - err := s.handleReconnectResponse([]byte(`{"malformedjson}`)) if err == nil { t.Error("test failed - slack handleReconnectResponse(), unmarshalled malformed json") diff --git a/currency/pair/pair.go b/currency/pair/pair.go index dd989c95..36f55363 100644 --- a/currency/pair/pair.go +++ b/currency/pair/pair.go @@ -108,6 +108,15 @@ func NewCurrencyPair(firstCurrency, secondCurrency string) CurrencyPair { } } +// NewCurrencyPairWithDelimiter returns a CurrencyPair with a delimiter +func NewCurrencyPairWithDelimiter(firstCurrency, secondCurrency, delimiter string) CurrencyPair { + return CurrencyPair{ + FirstCurrency: CurrencyItem(firstCurrency), + SecondCurrency: CurrencyItem(secondCurrency), + Delimiter: delimiter, + } +} + // NewCurrencyPairFromIndex returns a CurrencyPair via a currency string and // specific index func NewCurrencyPairFromIndex(currency, index string) CurrencyPair { diff --git a/currency/pair/pair_test.go b/currency/pair/pair_test.go index b74b65e5..3d738e38 100644 --- a/currency/pair/pair_test.go +++ b/currency/pair/pair_test.go @@ -180,6 +180,29 @@ func TestNewCurrencyPair(t *testing.T) { } } +func TestNewCurrencyPairWithDelimiter(t *testing.T) { + t.Parallel() + pair := NewCurrencyPairWithDelimiter("BTC", "USD", "-test-") + actual := pair.Pair() + expected := CurrencyItem("BTC-test-USD") + if actual != expected { + t.Errorf( + "Test failed. Pair(): %s was not equal to expected value: %s", + actual, expected, + ) + } + + pair = NewCurrencyPairWithDelimiter("BTC", "USD", "") + actual = pair.Pair() + expected = CurrencyItem("BTCUSD") + if actual != expected { + t.Errorf( + "Test failed. Pair(): %s was not equal to expected value: %s", + actual, expected, + ) + } +} + func TestNewCurrencyPairDelimiter(t *testing.T) { t.Parallel() pair := NewCurrencyPairDelimiter("BTC-USD", "-") diff --git a/exchanges/alphapoint/alphapoint.go b/exchanges/alphapoint/alphapoint.go index 8a949d71..e8c909e8 100644 --- a/exchanges/alphapoint/alphapoint.go +++ b/exchanges/alphapoint/alphapoint.go @@ -361,7 +361,7 @@ func (a *Alphapoint) WithdrawCoins(symbol, product, address string, amount float } func (a *Alphapoint) convertOrderTypeToOrderTypeNumber(orderType string) (orderTypeNumber int64) { - if orderType == exchange.Market.ToString() { + if orderType == exchange.MarketOrderType.ToString() { orderTypeNumber = 1 } diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index 9a40ef08..c3abce41 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -413,7 +413,7 @@ func TestCreateOrder(t *testing.T) { return } - _, err := a.CreateOrder("", "", exchange.Market.ToString(), 0.01, 0) + _, err := a.CreateOrder("", "", exchange.MarketOrderType.ToString(), 0.01, 0) if err == nil { t.Error("Test Failed - GetUserInfo() error") } @@ -480,18 +480,49 @@ func TestGetOrderFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange a := &Alphapoint{} a.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := a.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + a := &Alphapoint{} + a.SetDefaults() + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := a.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet(a) && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet(a) && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + a := &Alphapoint{} + a.SetDefaults() + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := a.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet(a) && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet(a) && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- @@ -515,9 +546,9 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.USD, } - response, err := a.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 1, "clientId") + response, err := a.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") if !areTestAPIKeysSet(a) && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet(a) && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -529,7 +560,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange a := &Alphapoint{} a.SetDefaults() @@ -545,12 +575,10 @@ func TestCancelExchangeOrder(t *testing.T) { AccountID: "1", CurrencyPair: currencyPair, } - // Act - err := a.CancelOrder(orderCancellation) - // Assert + err := a.CancelOrder(orderCancellation) if !areTestAPIKeysSet(a) && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet(a) && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -558,7 +586,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange a := &Alphapoint{} a.SetDefaults() @@ -574,12 +601,11 @@ func TestCancelAllExchangeOrders(t *testing.T) { AccountID: "1", CurrencyPair: currencyPair, } - // Act + resp, err := a.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet(a) && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet(a) && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/alphapoint/alphapoint_types.go b/exchanges/alphapoint/alphapoint_types.go index f00cedf2..37a2db17 100644 --- a/exchanges/alphapoint/alphapoint_types.go +++ b/exchanges/alphapoint/alphapoint_types.go @@ -1,5 +1,7 @@ package alphapoint +import exchange "github.com/thrasher-/gocryptotrader/exchanges" + // Response contains general responses from the exchange type Response struct { IsAccepted bool `json:"isAccepted"` @@ -144,19 +146,21 @@ type AccountInfo struct { // Order is a generalised order type type Order struct { - Serverorderid int `json:"ServerOrderId"` - AccountID int `json:"AccountId"` - Price int `json:"Price"` - QtyTotal int `json:"QtyTotal"` - QtyRemaining int `json:"QtyRemaining"` - ReceiveTime int64 `json:"ReceiveTime"` - Side int `json:"Side"` + ServerOrderID int `json:"ServerOrderId"` + AccountID int `json:"AccountId"` + Price float64 `json:"Price"` + QtyTotal float64 `json:"QtyTotal"` + QtyRemaining float64 `json:"QtyRemaining"` + ReceiveTime int64 `json:"ReceiveTime"` + Side int64 `json:"Side"` + State int `json:"orderState"` + OrderType int `json:"orderType"` } // OpenOrders holds the full range of orders by instrument type OpenOrders struct { Instrument string `json:"ins"` - Openorders []Order `json:"openOrders"` + OpenOrders []Order `json:"openOrders"` } // OrderInfo holds all open orders across the entire range of all instruments @@ -192,3 +196,17 @@ type WebsocketTicker struct { BuyOrderCount int `json:"buyOrderCount"` SellOrderCount int `json:"sellOrderCount"` } + +// orderSideMap holds order type info based on Alphapoint data +var orderSideMap = map[int64]exchange.OrderSide{ + 1: exchange.BuyOrderSide, + 2: exchange.SellOrderSide, +} + +// orderTypeMap holds order type info based on Alphapoint data +var orderTypeMap = map[int]exchange.OrderType{ + 1: exchange.MarketOrderType, + 2: exchange.LimitOrderType, + 3: exchange.StopOrderType, + 6: exchange.TrailingStopOrderType, +} diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 7bb38bab..0d9c9492 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -161,9 +162,9 @@ func (a *Alphapoint) GetOrderInfo(orderID int64) (float64, error) { } for x := range orders { - for y := range orders[x].Openorders { - if int64(orders[x].Openorders[y].Serverorderid) == orderID { - return float64(orders[x].Openorders[y].QtyRemaining), nil + for y := range orders[x].OpenOrders { + if int64(orders[x].OpenOrders[y].ServerOrderID) == orderID { + return float64(orders[x].OpenOrders[y].QtyRemaining), nil } } } @@ -216,3 +217,88 @@ func (a *Alphapoint) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, erro func (a *Alphapoint) GetWithdrawCapabilities() uint32 { return a.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +// This function is not concurrency safe due to orderSide/orderType maps +func (a *Alphapoint) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := a.GetOrders() + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for x := range resp { + for _, order := range resp[x].OpenOrders { + if order.State != 1 { + continue + } + + orderDetail := exchange.OrderDetail{ + Amount: float64(order.QtyTotal), + Exchange: a.Name, + AccountID: fmt.Sprintf("%v", order.AccountID), + ID: fmt.Sprintf("%v", order.ServerOrderID), + Price: float64(order.Price), + RemainingAmount: float64(order.QtyRemaining), + } + + orderDetail.OrderSide = orderSideMap[order.Side] + orderDetail.OrderDate = time.Unix(order.ReceiveTime, 0) + orderDetail.OrderType = orderTypeMap[order.OrderType] + if orderDetail.OrderType == "" { + orderDetail.OrderType = exchange.UnknownOrderType + } + + orders = append(orders, orderDetail) + } + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +// This function is not concurrency safe due to orderSide/orderType maps +func (a *Alphapoint) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := a.GetOrders() + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for x := range resp { + for _, order := range resp[x].OpenOrders { + if order.State == 1 { + continue + } + + orderDetail := exchange.OrderDetail{ + Amount: float64(order.QtyTotal), + AccountID: fmt.Sprintf("%v", order.AccountID), + Exchange: a.Name, + ID: fmt.Sprintf("%v", order.ServerOrderID), + Price: float64(order.Price), + RemainingAmount: float64(order.QtyRemaining), + } + + orderDetail.OrderSide = orderSideMap[order.Side] + orderDetail.OrderDate = time.Unix(order.ReceiveTime, 0) + orderDetail.OrderType = orderTypeMap[order.OrderType] + if orderDetail.OrderType == "" { + orderDetail.OrderType = exchange.UnknownOrderType + } + + orders = append(orders, orderDetail) + } + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + + return orders, nil +} diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index 886c28b3..637c1205 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -55,11 +55,9 @@ func TestSetup(t *testing.T) { t.Error("Test Failed - ANX Setup() init error") } a.Setup(anxConfig) - if testAPIKey != "" && testAPISecret != "" { - a.APIKey = testAPIKey - a.APISecret = testAPISecret - a.AuthenticatedAPISupport = true - } + a.APIKey = testAPIKey + a.APISecret = testAPISecret + a.AuthenticatedAPISupport = true if a.Enabled != true { t.Error("Test Failed - ANX Setup() incorrect values set") @@ -216,18 +214,49 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange a.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.WithdrawCryptoWithEmailText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - // Act + withdrawPermissions := a.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + a.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := a.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + a.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := a.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- @@ -251,7 +280,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.USD, } - response, err := a.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 1, "clientId") + response, err := a.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -260,7 +289,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange a.SetDefaults() TestSetup(t) @@ -276,19 +304,16 @@ func TestCancelExchangeOrder(t *testing.T) { AccountID: "1", CurrencyPair: currencyPair, } - // Act - err := a.CancelOrder(orderCancellation) - // Assert + err := a.CancelOrder(orderCancellation) if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel order: %s", err) } else if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange a.SetDefaults() TestSetup(t) @@ -304,14 +329,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { AccountID: "1", CurrencyPair: currencyPair, } - // Act + resp, err := a.CancelAllOrders(orderCancellation) - // Assert if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel order: %s", err) } else if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if len(resp.OrderStatus) > 0 { diff --git a/exchanges/anx/anx_types.go b/exchanges/anx/anx_types.go index 49b1d498..5deca029 100644 --- a/exchanges/anx/anx_types.go +++ b/exchanges/anx/anx_types.go @@ -119,20 +119,20 @@ type Order struct { // OrderResponse holds order response data type OrderResponse struct { - BuyTradedCurrency bool `json:"buyTradedCurrency"` - ExecutedAverageRate string `json:"executedAverageRate"` - LimitPriceInSettlementCurrency string `json:"limitPriceInSettlementCurrency"` - OrderID string `json:"orderId"` - OrderStatus string `json:"orderStatus"` - OrderType string `json:"orderType"` - ReplaceExistingOrderUUID string `json:"replaceExistingOrderId"` - SettlementCurrency string `json:"settlementCurrency"` - SettlementCurrencyAmount string `json:"settlementCurrencyAmount"` - SettlementCurrencyOutstanding string `json:"settlementCurrencyOutstanding"` - Timestamp int64 `json:"timestamp"` - TradedCurrency string `json:"tradedCurrency"` - TradedCurrencyAmount string `json:"tradedCurrencyAmount"` - TradedCurrencyOutstanding string `json:"tradedCurrencyOutstanding"` + BuyTradedCurrency bool `json:"buyTradedCurrency"` + ExecutedAverageRate string `json:"executedAverageRate"` + LimitPriceInSettlementCurrency string `json:"limitPriceInSettlementCurrency"` + OrderID string `json:"orderId"` + OrderStatus string `json:"orderStatus"` + OrderType string `json:"orderType"` + ReplaceExistingOrderUUID string `json:"replaceExistingOrderId"` + SettlementCurrency string `json:"settlementCurrency"` + SettlementCurrencyAmount float64 `json:"settlementCurrencyAmount,string"` + SettlementCurrencyOutstanding string `json:"settlementCurrencyOutstanding"` + Timestamp int64 `json:"timestamp"` + TradedCurrency string `json:"tradedCurrency"` + TradedCurrencyAmount float64 `json:"tradedCurrencyAmount,string"` + TradedCurrencyOutstanding string `json:"tradedCurrencyOutstanding"` } // OrderCancelResponse returned when cancelling multiple orders diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 089eabb9..5a93f92b 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -3,7 +3,9 @@ package anx import ( "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -228,11 +230,11 @@ func (a *ANX) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderTyp var isBuying bool var limitPriceInSettlementCurrency float64 - if side == exchange.Buy { + if side == exchange.BuyOrderSide { isBuying = true } - if orderType == exchange.Limit { + if orderType == exchange.LimitOrderType { limitPriceInSettlementCurrency = price } @@ -345,3 +347,70 @@ func (a *ANX) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (a *ANX) GetWithdrawCapabilities() uint32 { return a.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (a *ANX) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := a.GetOrderList(true) + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, order := range resp { + orderDate := time.Unix(order.Timestamp, 0) + orderType := exchange.OrderType(strings.ToUpper(order.OrderType)) + + orderDetail := exchange.OrderDetail{ + Amount: order.TradedCurrencyAmount, + CurrencyPair: pair.NewCurrencyPairWithDelimiter(order.TradedCurrency, order.SettlementCurrency, a.ConfigCurrencyPairFormat.Delimiter), + OrderDate: orderDate, + Exchange: a.Name, + ID: order.OrderID, + OrderType: orderType, + Price: order.SettlementCurrencyAmount, + Status: order.OrderStatus, + } + + orders = append(orders, orderDetail) + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (a *ANX) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := a.GetOrderList(false) + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, order := range resp { + orderDate := time.Unix(order.Timestamp, 0) + orderType := exchange.OrderType(strings.ToUpper(order.OrderType)) + + orderDetail := exchange.OrderDetail{ + Amount: order.TradedCurrencyAmount, + OrderDate: orderDate, + Exchange: a.Name, + ID: order.OrderID, + OrderType: orderType, + Price: order.SettlementCurrencyAmount, + Status: order.OrderStatus, + CurrencyPair: pair.NewCurrencyPairWithDelimiter(order.TradedCurrency, order.SettlementCurrency, a.ConfigCurrencyPairFormat.Delimiter), + } + + orders = append(orders, orderDetail) + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index a3d4c582..278bb4b5 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -498,8 +498,9 @@ func (b *Binance) CancelExistingOrder(symbol string, orderID int64, origClientOr return resp, b.SendAuthHTTPRequest("DELETE", path, params, &resp) } -// OpenOrders Current open orders -// Get all open orders on a symbol. Careful when accessing this with no symbol. +// OpenOrders Current open orders. Get all open orders on a symbol. +// Careful when accessing this with no symbol: The number of requests counted against the rate limiter +// is equal to the number of symbols currently trading on the exchange. func (b *Binance) OpenOrders(symbol string) ([]QueryOrderData, error) { var resp []QueryOrderData path := fmt.Sprintf("%s%s", b.APIUrl, openOrders) diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index b3d23866..c563c317 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -320,17 +320,61 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := b.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + _, err := b.GetActiveOrders(getOrdersRequest) + if err == nil { + t.Error("Expected: 'At least one currency is required to fetch order history'. received nil") + } + + getOrdersRequest.Currencies = []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)} + + _, err = b.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetOrderHistory(getOrdersRequest) + if err == nil { + t.Error("Expected: 'At least one currency is required to fetch order history'. received nil") + } + + getOrdersRequest.Currencies = []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)} + + _, err = b.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ----------------------------------------------------------------------------------------------------------------------------- @@ -351,7 +395,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.LTC, SecondCurrency: symbol.BTC, } - response, err := b.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -360,7 +404,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -373,12 +416,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := b.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -386,7 +426,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -399,12 +438,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := b.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel order: %v", err) @@ -452,7 +489,7 @@ func TestWithdraw(t *testing.T) { _, err := b.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index fdf28e02..fc9d8075 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -183,16 +185,16 @@ func (b *Binance) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orde var submitOrderResponse exchange.SubmitOrderResponse var sideType RequestParamsSideType - if side == exchange.Buy { + if side == exchange.BuyOrderSide { sideType = BinanceRequestParamsSideBuy } else { sideType = BinanceRequestParamsSideSell } var requestParamsOrderType RequestParamsOrderType - if orderType == exchange.Market { + if orderType == exchange.MarketOrderType { requestParamsOrderType = BinanceRequestParamsOrderMarket - } else if orderType == exchange.Limit { + } else if orderType == exchange.LimitOrderType { requestParamsOrderType = BinanceRequestParamsOrderLimit } else { submitOrderResponse.IsOrderPlaced = false @@ -306,3 +308,86 @@ func (b *Binance) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) func (b *Binance) GetWithdrawCapabilities() uint32 { return b.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (b *Binance) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("At least one currency is required to fetch order history") + } + + var orders []exchange.OrderDetail + for _, currency := range getOrdersRequest.Currencies { + resp, err := b.OpenOrders(exchange.FormatExchangeCurrency(b.Name, currency).String()) + if err != nil { + return nil, err + } + + for _, order := range resp { + orderSide := exchange.OrderSide(strings.ToUpper(order.Side)) + orderType := exchange.OrderType(strings.ToUpper(order.Type)) + orderDate := time.Unix(int64(order.Time), 0) + + orders = append(orders, exchange.OrderDetail{ + Amount: order.OrigQty, + OrderDate: orderDate, + Exchange: b.Name, + ID: fmt.Sprintf("%v", order.OrderID), + OrderSide: orderSide, + OrderType: orderType, + Price: order.Price, + Status: order.Status, + CurrencyPair: pair.NewCurrencyPairFromString(order.Symbol), + }) + } + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (b *Binance) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("At least one currency is required to fetch order history") + } + + var orders []exchange.OrderDetail + for _, currency := range getOrdersRequest.Currencies { + resp, err := b.AllOrders(exchange.FormatExchangeCurrency(b.Name, currency).String(), "", "1000") + if err != nil { + return nil, err + } + + for _, order := range resp { + orderSide := exchange.OrderSide(strings.ToUpper(order.Side)) + orderType := exchange.OrderType(strings.ToUpper(order.Type)) + orderDate := time.Unix(int64(order.Time), 0) + // New orders are covered in GetOpenOrders + if order.Status == "NEW" { + continue + } + + orders = append(orders, exchange.OrderDetail{ + Amount: order.OrigQty, + OrderDate: orderDate, + Exchange: b.Name, + ID: fmt.Sprintf("%v", order.OrderID), + OrderSide: orderSide, + OrderType: orderType, + Price: order.Price, + CurrencyPair: pair.NewCurrencyPairFromString(order.Symbol), + Status: order.Status, + }) + } + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + + return orders, nil +} diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index eb48bc98..ba3d4dda 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -46,6 +46,7 @@ const ( bitfinexOrderCancelReplace = "order/cancel/replace" bitfinexOrderStatus = "order/status" bitfinexOrders = "orders" + bitfinexInactiveOrders = "orders/hist" bitfinexPositions = "positions" bitfinexClaimPosition = "position/claim" bitfinexHistory = "history" @@ -732,9 +733,19 @@ func (b *Bitfinex) GetOrderStatus(OrderID int64) (Order, error) { b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderStatus, request, &orderStatus) } -// GetActiveOrders returns all active orders and statuses -func (b *Bitfinex) GetActiveOrders() ([]Order, error) { - response := []Order{} +// GetInactiveOrders returns order status information +func (b *Bitfinex) GetInactiveOrders() ([]Order, error) { + var response []Order + request := make(map[string]interface{}) + request["limit"] = "100" + + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexInactiveOrders, request, &response) +} + +// GetOpenOrders returns all active orders and statuses +func (b *Bitfinex) GetOpenOrders() ([]Order, error) { + var response []Order return response, b.SendAuthenticatedHTTPRequest("POST", bitfinexOrders, nil, &response) diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 454eb07a..18ea00e6 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -431,15 +431,15 @@ func TestGetOrderStatus(t *testing.T) { } } -func TestGetActiveOrders(t *testing.T) { +func TestGetOpenOrders(t *testing.T) { if b.APIKey == "" || b.APISecret == "" { t.SkipNow() } t.Parallel() - _, err := b.GetActiveOrders() + _, err := b.GetOpenOrders() if err == nil { - t.Error("Test Failed - GetActiveOrders() error") + t.Error("Test Failed - GetOpenOrders() error") } } @@ -697,17 +697,48 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText - // Act + withdrawPermissions := b.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -730,7 +761,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.LTC, SecondCurrency: symbol.BTC, } - response, err := b.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -739,7 +770,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -756,12 +786,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := b.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -769,7 +796,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrdera(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -786,12 +812,10 @@ func TestCancelAllExchangeOrdera(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := b.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -825,7 +849,7 @@ func TestWithdraw(t *testing.T) { _, err := b.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -858,7 +882,7 @@ func TestWithdrawFiat(t *testing.T) { _, err := b.WithdrawFiatFunds(withdrawFiatRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -897,7 +921,7 @@ func TestWithdrawInternationalBank(t *testing.T) { _, err := b.WithdrawFiatFundsToInternationalBank(withdrawFiatRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/bitfinex/bitfinex_types.go b/exchanges/bitfinex/bitfinex_types.go index 4ab0e68e..3eea281c 100644 --- a/exchanges/bitfinex/bitfinex_types.go +++ b/exchanges/bitfinex/bitfinex_types.go @@ -257,7 +257,7 @@ type Order struct { OriginalAmount float64 `json:"original_amount,string"` RemainingAmount float64 `json:"remaining_amount,string"` ExecutedAmount float64 `json:"executed_amount,string"` - OrderID int64 `json:"order_id"` + OrderID int64 `json:"order_id,omitempty"` } // OrderMultiResponse holds order information on the executed orders diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 127afb46..89d97700 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -5,7 +5,9 @@ import ( "fmt" "net/url" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -166,7 +168,7 @@ func (b *Bitfinex) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, ord var submitOrderResponse exchange.SubmitOrderResponse var isBuying bool - if side == exchange.Buy { + if side == exchange.BuyOrderSide { isBuying = true } @@ -306,3 +308,120 @@ func (b *Bitfinex) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) func (b *Bitfinex) GetWithdrawCapabilities() uint32 { return b.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (b *Bitfinex) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var orders []exchange.OrderDetail + resp, err := b.GetOpenOrders() + if err != nil { + return nil, err + } + + for _, order := range resp { + orderSide := exchange.OrderSide(strings.ToUpper(order.Side)) + timestamp, err := strconv.ParseInt(order.Timestamp, 10, 64) + if err != nil { + log.Warnf("Unable to convert timestamp '%v', leaving blank", order.Timestamp) + } + orderDate := time.Unix(timestamp, 0) + + orderDetail := exchange.OrderDetail{ + Amount: order.OriginalAmount, + OrderDate: orderDate, + Exchange: b.Name, + ID: fmt.Sprintf("%v", order.OrderID), + OrderSide: orderSide, + Price: order.Price, + RemainingAmount: order.RemainingAmount, + CurrencyPair: pair.NewCurrencyPairFromString(order.Symbol), + ExecutedAmount: order.ExecutedAmount, + } + + if order.IsLive { + orderDetail.Status = string(exchange.ActiveOrderStatus) + } else if order.IsCancelled { + orderDetail.Status = string(exchange.CancelledOrderStatus) + } else if order.IsHidden { + orderDetail.Status = string(exchange.HiddenOrderStatus) + } else { + orderDetail.Status = string(exchange.UnknownOrderStatus) + } + + // API docs discrepency. Example contains prefixed "exchange " + // Return type suggests “market” / “limit” / “stop” / “trailing-stop” + orderType := strings.Replace(order.Type, "exchange ", "", 1) + if orderType == "trailing-stop" { + orderDetail.OrderType = exchange.TrailingStopOrderType + } else { + orderDetail.OrderType = exchange.OrderType(strings.ToUpper(orderType)) + } + + orders = append(orders, orderDetail) + } + + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (b *Bitfinex) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var orders []exchange.OrderDetail + resp, err := b.GetInactiveOrders() + if err != nil { + return nil, err + } + + for _, order := range resp { + orderSide := exchange.OrderSide(strings.ToUpper(order.Side)) + timestamp, err := strconv.ParseInt(order.Timestamp, 10, 64) + if err != nil { + log.Warnf("Unable to convert timestamp '%v', leaving blank", order.Timestamp) + } + orderDate := time.Unix(timestamp, 0) + + orderDetail := exchange.OrderDetail{ + Amount: order.OriginalAmount, + OrderDate: orderDate, + Exchange: b.Name, + ID: fmt.Sprintf("%v", order.OrderID), + OrderSide: orderSide, + Price: order.Price, + RemainingAmount: order.RemainingAmount, + ExecutedAmount: order.ExecutedAmount, + CurrencyPair: pair.NewCurrencyPairFromString(order.Symbol), + } + + if order.IsLive { + orderDetail.Status = string(exchange.ActiveOrderStatus) + } else if order.IsCancelled { + orderDetail.Status = string(exchange.CancelledOrderStatus) + } else if order.IsHidden { + orderDetail.Status = string(exchange.HiddenOrderStatus) + } else { + orderDetail.Status = string(exchange.UnknownOrderStatus) + } + + // API docs discrepency. Example contains prefixed "exchange " + // Return type suggests “market” / “limit” / “stop” / “trailing-stop” + orderType := strings.Replace(order.Type, "exchange ", "", 1) + if orderType == "trailing-stop" { + orderDetail.OrderType = exchange.TrailingStopOrderType + } else { + orderDetail.OrderType = exchange.OrderType(strings.ToUpper(orderType)) + } + + orders = append(orders, orderDetail) + } + + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index de5cd7c4..4d3dd9d4 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -238,17 +238,46 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange b.SetDefaults() expectedResult := exchange.AutoWithdrawFiatText + " & " + exchange.WithdrawCryptoViaWebsiteOnlyText - // Act + withdrawPermissions := b.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetOrderHistory(getOrdersRequest) + if err != common.ErrNotYetImplemented { + t.Errorf("Expected '%v', received '%v'", common.ErrNotYetImplemented, err) + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -272,14 +301,13 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.LTC, SecondCurrency: symbol.BTC, } - _, err := b.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 1, "clientId") + _, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not Yet Implemented', received %v", err) } } func TestCancelExchangeOrder(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -295,16 +323,14 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := b.CancelOrder(orderCancellation) - // Assert + if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not Yet Implemented', received %v", err) } } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -320,9 +346,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act _, err := b.CancelAllOrders(orderCancellation) - // Assert + if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not Yet Implemented', received %v", err) } diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index bfab32b7..24ab5476 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -215,3 +215,14 @@ func (b *Bitflyer) GetWebsocket() (*exchange.Websocket, error) { func (b *Bitflyer) GetWithdrawCapabilities() uint32 { return b.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (b *Bitflyer) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + return nil, common.ErrNotYetImplemented +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (b *Bitflyer) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + return nil, common.ErrNotYetImplemented +} diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go index 5d25e138..0f4a72c5 100644 --- a/exchanges/bithumb/bithumb.go +++ b/exchanges/bithumb/bithumb.go @@ -355,11 +355,25 @@ func (b *Bithumb) GetOrders(orderID, transactionType, count, after, currency str response := Orders{} params := url.Values{} - params.Set("order_id", orderID) - params.Set("type", transactionType) - params.Set("count", count) - params.Set("after", after) - params.Set("currency", common.StringToUpper(currency)) + if len(orderID) > 0 { + params.Set("order_id", orderID) + } + + if len(transactionType) > 0 { + params.Set("type", transactionType) + } + + if len(count) > 0 { + params.Set("count", count) + } + + if len(after) > 0 { + params.Set("after", after) + } + + if len(currency) > 0 { + params.Set("currency", common.StringToUpper(currency)) + } return response, b.SendAuthenticatedHTTPRequest(privateOrders, params, &response) @@ -421,7 +435,7 @@ func (b *Bithumb) GetOrderDetails(orderID, transactionType, currency string) (Or params := url.Values{} params.Set("order_id", common.StringToUpper(orderID)) - params.Set("type", common.StringToUpper(transactionType)) + params.Set("type", transactionType) params.Set("currency", common.StringToUpper(currency)) return response, diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index 2c52eeea..70afa0d5 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -277,17 +277,49 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.AutoWithdrawFiatText - // Act + withdrawPermissions := b.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + OrderSide: exchange.SellOrderSide, + } + + _, err := b.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -311,7 +343,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.LTC, } - response, err := b.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -320,7 +352,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -337,12 +368,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := b.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel order: %v", err) @@ -350,7 +378,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -367,12 +394,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := b.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel order: %v", err) @@ -403,7 +428,7 @@ func TestModifyOrder(t *testing.T) { _, err := b.ModifyOrder(exchange.ModifyOrder{OrderID: "1337", Price: 100, Amount: 1000, - OrderSide: exchange.Sell, + OrderSide: exchange.SellOrderSide, Currency: curr}) if err == nil { t.Error("Test Failed - ModifyOrder() error") @@ -426,7 +451,7 @@ func TestWithdraw(t *testing.T) { _, err := b.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -460,7 +485,7 @@ func TestWithdrawFiat(t *testing.T) { _, err := b.WithdrawFiatFunds(withdrawFiatRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 7f310ac1..18ee971c 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -6,6 +6,7 @@ import ( "math" "strconv" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -173,11 +174,11 @@ func (b *Bithumb) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orde var submitOrderResponse exchange.SubmitOrderResponse var err error var orderID string - if side == exchange.Buy { + if side == exchange.BuyOrderSide { var result MarketBuy result, err = b.MarketBuyOrder(p.FirstCurrency.String(), amount) orderID = result.OrderID - } else if side == exchange.Sell { + } else if side == exchange.SellOrderSide { var result MarketSell result, err = b.MarketSellOrder(p.FirstCurrency.String(), amount) orderID = result.OrderID @@ -306,3 +307,85 @@ func (b *Bithumb) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) func (b *Bithumb) GetWithdrawCapabilities() uint32 { return b.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (b *Bithumb) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var orders []exchange.OrderDetail + resp, err := b.GetOrders("", "", "1000", "", "") + if err != nil { + return nil, err + } + + for _, order := range resp.Data { + if order.Status != "placed" { + continue + } + + orderDate := time.Unix(order.OrderDate, 0) + orderDetail := exchange.OrderDetail{ + Amount: order.Units, + Exchange: b.Name, + ID: order.OrderID, + OrderDate: orderDate, + Price: order.Price, + RemainingAmount: order.UnitsRemaining, + Status: string(exchange.ActiveOrderStatus), + CurrencyPair: pair.NewCurrencyPairWithDelimiter(order.OrderCurrency, order.PaymentCurrency, b.ConfigCurrencyPairFormat.Delimiter), + } + + if order.Type == "bid" { + orderDetail.OrderSide = exchange.BuyOrderSide + } else if order.Type == "ask" { + orderDetail.OrderSide = exchange.SellOrderSide + } + + orders = append(orders, orderDetail) + } + + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (b *Bithumb) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var orders []exchange.OrderDetail + resp, err := b.GetOrders("", "", "1000", "", "") + if err != nil { + return nil, err + } + + for _, order := range resp.Data { + if order.Status == "placed" { + continue + } + + orderDate := time.Unix(order.OrderDate, 0) + orderDetail := exchange.OrderDetail{ + Amount: order.Units, + Exchange: b.Name, + ID: order.OrderID, + OrderDate: orderDate, + Price: order.Price, + RemainingAmount: order.UnitsRemaining, + CurrencyPair: pair.NewCurrencyPairWithDelimiter(order.OrderCurrency, order.PaymentCurrency, b.ConfigCurrencyPairFormat.Delimiter), + } + + if order.Type == "bid" { + orderDetail.OrderSide = exchange.BuyOrderSide + } else if order.Type == "ask" { + orderDetail.OrderSide = exchange.SellOrderSide + } + + orders = append(orders, orderDetail) + } + + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index 70cb7042..aa42216f 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -400,11 +400,11 @@ func (b *Bitmex) GetCurrentNotifications() ([]Notification, error) { } // GetOrders returns all the orders, open and closed -func (b *Bitmex) GetOrders(params GenericRequestParams) ([]Order, error) { +func (b *Bitmex) GetOrders(params OrdersRequest) ([]Order, error) { var orders []Order return orders, b.SendAuthenticatedHTTPRequest("GET", - bitmexEndpointOrder, + fmt.Sprintf("%v%v", bitmexEndpointOrder, ""), params, &orders) } diff --git a/exchanges/bitmex/bitmex_parameters.go b/exchanges/bitmex/bitmex_parameters.go index ea40bb34..60f736c9 100644 --- a/exchanges/bitmex/bitmex_parameters.go +++ b/exchanges/bitmex/bitmex_parameters.go @@ -1037,3 +1037,31 @@ func (p UserRequestWithdrawalParams) ToURLVals(path string) (string, error) { func (p UserRequestWithdrawalParams) IsNil() bool { return p == (UserRequestWithdrawalParams{}) } + +// OrdersRequest used for GetOrderHistory +type OrdersRequest struct { + Symbol string `json:"symbol,omitempty"` + Filter string `json:"filter,omitempty"` + Columns string `json:"columns,omitempty"` + Count float64 `json:"count,omitempty"` + Start float64 `json:"start,omitempty"` + Reverse bool `json:"reverse,omitempty"` + StartTime string `json:"startTime,omitempty"` + EndTime string `json:"endTime,omitempty"` +} + +// VerifyData verifies parameter data during SendAuthenticatedHTTPRequest +func (p OrdersRequest) VerifyData() error { + return nil +} + +// ToURLVals converts struct values to url.values and encodes it on the supplied +// path +func (p OrdersRequest) ToURLVals(path string) (string, error) { + return "", nil +} + +// IsNil checks to see if any values has been set for the paramater +func (p OrdersRequest) IsNil() bool { + return p == (OrdersRequest{}) +} diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index 0efc06a6..ecfc3189 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -32,7 +32,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Bitmex Setup() init error") } - bitmexConfig.AuthenticatedAPISupport = true bitmexConfig.APIKey = testAPIKey bitmexConfig.APISecret = testAPISecret @@ -209,13 +208,6 @@ func TestGetCurrentNotifications(t *testing.T) { } } -func TestGetOrders(t *testing.T) { - _, err := b.GetOrders(GenericRequestParams{}) - if err == nil { - t.Error("test failed - GetOrders() error", err) - } -} - func TestAmendOrder(t *testing.T) { _, err := b.AmendOrder(OrderAmendParams{}) if err == nil { @@ -454,18 +446,50 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.WithdrawCryptoWithEmailText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := b.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := b.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -489,7 +513,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.XBT, SecondCurrency: symbol.USD, } - response, err := b.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -498,7 +522,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -515,12 +538,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := b.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -528,7 +548,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -545,12 +564,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := b.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -599,7 +616,7 @@ func TestWithdraw(t *testing.T) { _, err := b.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/bitmex/bitmex_types.go b/exchanges/bitmex/bitmex_types.go index d6d4030b..2535e80d 100644 --- a/exchanges/bitmex/bitmex_types.go +++ b/exchanges/bitmex/bitmex_types.go @@ -1,5 +1,7 @@ package bitmex +import exchange "github.com/thrasher-/gocryptotrader/exchanges" + // RequestError allows for a general error capture from requests type RequestError struct { Error struct { @@ -293,14 +295,14 @@ type Order struct { MultiLegReportingType string `json:"multiLegReportingType"` OrdRejReason string `json:"ordRejReason"` OrdStatus string `json:"ordStatus"` - OrdType string `json:"ordType"` + OrdType int64 `json:"ordType,string"` OrderID string `json:"orderID"` OrderQty int64 `json:"orderQty"` PegOffsetValue float64 `json:"pegOffsetValue"` PegPriceType string `json:"pegPriceType"` Price float64 `json:"price"` SettlCurrency string `json:"settlCurrency"` - Side string `json:"side"` + Side int64 `json:"side,string"` SimpleCumQty float64 `json:"simpleCumQty"` SimpleLeavesQty float64 `json:"simpleLeavesQty"` SimpleOrderQty float64 `json:"simpleOrderQty"` @@ -666,3 +668,17 @@ type WalletInfo struct { WithdrawalLock []string `json:"withdrawalLock"` Withdrawn int64 `json:"withdrawn"` } + +// orderTypeMap holds order type info based on Bitmex data +var orderTypeMap = map[int64]exchange.OrderType{ + 1: exchange.MarketOrderType, + 2: exchange.LimitOrderType, + 3: exchange.StopOrderType, + 7: exchange.TrailingStopOrderType, +} + +// orderSideMap holds order type info based on Bitmex data +var orderSideMap = map[int64]exchange.OrderSide{ + 1: exchange.BuyOrderSide, + 2: exchange.SellOrderSide, +} diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index c3e63032..d1503403 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -322,7 +322,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai var bids, asks []orderbook.Item for _, orderbookItem := range data { - if orderbookItem.Side == exchange.Sell.ToString() { + if orderbookItem.Side == exchange.SellOrderSide.ToString() { asks = append(asks, orderbook.Item{ Price: orderbookItem.Price, Amount: float64(orderbookItem.Size), diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 6888b980..3fb46520 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -107,12 +107,12 @@ func (b *Bitmex) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbo } for _, ob := range orderbookNew { - if ob.Side == exchange.Sell.ToString() { + if ob.Side == exchange.SellOrderSide.ToString() { orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: float64(ob.Size), Price: ob.Price}) continue } - if ob.Side == exchange.Buy.ToString() { + if ob.Side == exchange.BuyOrderSide.ToString() { orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: float64(ob.Size), Price: ob.Price}) continue @@ -181,7 +181,7 @@ func (b *Bitmex) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, order Side: side.ToString(), } - if orderType == exchange.Limit { + if orderType == exchange.LimitOrderType { orderNewParams.Price = price } @@ -304,3 +304,84 @@ func (b *Bitmex) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (b *Bitmex) GetWithdrawCapabilities() uint32 { return b.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +// This function is not concurrency safe due to orderSide/orderType maps +func (b *Bitmex) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var orders []exchange.OrderDetail + params := OrdersRequest{} + params.Filter = "{\"open\":true}" + + resp, err := b.GetOrders(params) + if err != nil { + return nil, err + } + + for _, order := range resp { + orderSide := orderSideMap[order.Side] + orderType := orderTypeMap[order.OrdType] + if orderType == "" { + orderType = exchange.UnknownOrderType + } + + orderDetail := exchange.OrderDetail{ + Price: order.Price, + Amount: float64(order.OrderQty), + Exchange: b.Name, + ID: order.OrderID, + OrderSide: orderSide, + OrderType: orderType, + Status: order.OrdStatus, + CurrencyPair: pair.NewCurrencyPairWithDelimiter(order.Symbol, order.SettlCurrency, b.ConfigCurrencyPairFormat.Delimiter), + } + + orders = append(orders, orderDetail) + } + + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +// This function is not concurrency safe due to orderSide/orderType maps +func (b *Bitmex) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var orders []exchange.OrderDetail + params := OrdersRequest{} + resp, err := b.GetOrders(params) + if err != nil { + return nil, err + } + + for _, order := range resp { + orderSide := orderSideMap[order.Side] + orderType := orderTypeMap[order.OrdType] + if orderType == "" { + orderType = exchange.UnknownOrderType + } + + orderDetail := exchange.OrderDetail{ + Price: order.Price, + Amount: float64(order.OrderQty), + Exchange: b.Name, + ID: order.OrderID, + OrderSide: orderSide, + OrderType: orderType, + Status: order.OrdStatus, + CurrencyPair: pair.NewCurrencyPairWithDelimiter(order.Symbol, order.SettlCurrency, b.ConfigCurrencyPairFormat.Delimiter), + } + + orders = append(orders, orderDetail) + } + + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 33822160..9d64ec3b 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -339,7 +339,7 @@ func (b *Bitstamp) GetBalance() (Balances, error) { // GetUserTransactions returns an array of transactions func (b *Bitstamp) GetUserTransactions(currencyPair string) ([]UserTransactions, error) { type Response struct { - Date string `json:"datetime"` + Date int64 `json:"datetime"` TransID int64 `json:"id"` Type int `json:"type,string"` USD interface{} `json:"usd"` diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 3cdabfbb..0528869f 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -339,7 +339,6 @@ func TestGetUnconfirmedBitcoinDeposits(t *testing.T) { } func TestTransferAccountBalance(t *testing.T) { - t.Parallel() if b.APIKey == "" || b.APISecret == "" || b.APIKey == "Key" || b.APISecret == "Secret" { @@ -356,17 +355,48 @@ func TestTransferAccountBalance(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.AutoWithdrawFiatText - // Act + withdrawPermissions := b.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -389,7 +419,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.USD, } - response, err := b.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -398,7 +428,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -415,12 +444,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := b.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -428,7 +454,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -445,12 +470,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := b.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -484,7 +507,7 @@ func TestWithdraw(t *testing.T) { _, err := b.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -519,7 +542,7 @@ func TestWithdrawFiat(t *testing.T) { _, err := b.WithdrawFiatFunds(withdrawFiatRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -560,7 +583,7 @@ func TestWithdrawInternationalBank(t *testing.T) { _, err := b.WithdrawFiatFundsToInternationalBank(withdrawFiatRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/bitstamp/bitstamp_types.go b/exchanges/bitstamp/bitstamp_types.go index 0b5b2c15..0a51dbbd 100644 --- a/exchanges/bitstamp/bitstamp_types.go +++ b/exchanges/bitstamp/bitstamp_types.go @@ -77,7 +77,7 @@ type Balances struct { // UserTransactions holds user transaction information type UserTransactions struct { - Date string `json:"datetime"` + Date int64 `json:"datetime"` TransID int64 `json:"id"` Type int `json:"type,string"` USD float64 `json:"usd"` @@ -91,11 +91,12 @@ type UserTransactions struct { // Order holds current open order data type Order struct { - ID int64 `json:"id"` - Date string `json:"datetime"` - Type int `json:"type"` - Price float64 `json:"price"` - Amount float64 `json:"amount"` + ID int64 `json:"id"` + Date int64 `json:"datetime"` + Type int `json:"type"` + Price float64 `json:"price"` + Amount float64 `json:"amount"` + Currency string `json:"currency_pair"` } // OrderStatus holds order status information diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 2e4e43f8..21cc5af4 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -6,9 +6,11 @@ import ( "strconv" "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" + "github.com/thrasher-/gocryptotrader/currency/symbol" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -176,8 +178,8 @@ func (b *Bitstamp) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([] // SubmitOrder submits a new order func (b *Bitstamp) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - buy := side == exchange.Buy - market := orderType == exchange.Market + buy := side == exchange.BuyOrderSide + market := orderType == exchange.MarketOrderType response, err := b.PlaceOrder(p.Pair().String(), price, amount, buy, market) if response.ID > 0 { @@ -289,3 +291,94 @@ func (b *Bitstamp) GetWebsocket() (*exchange.Websocket, error) { func (b *Bitstamp) GetWithdrawCapabilities() uint32 { return b.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (b *Bitstamp) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var orders []exchange.OrderDetail + var currPair string + if len(getOrdersRequest.Currencies) != 1 { + currPair = "all" + } else { + currPair = getOrdersRequest.Currencies[0].Pair().String() + } + + resp, err := b.GetOpenOrders(currPair) + if err != nil { + return nil, err + } + + for _, order := range resp { + symbolOne := order.Currency[0:3] + symbolTwo := order.Currency[len(order.Currency)-3:] + orderDate := time.Unix(order.Date, 0) + + orders = append(orders, exchange.OrderDetail{ + Amount: order.Amount, + ID: fmt.Sprintf("%v", order.ID), + Price: order.Price, + OrderDate: orderDate, + CurrencyPair: pair.NewCurrencyPair(symbolOne, symbolTwo), + Exchange: b.Name, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (b *Bitstamp) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var currPair string + if len(getOrdersRequest.Currencies) == 1 { + currPair = getOrdersRequest.Currencies[0].Pair().String() + } + resp, err := b.GetUserTransactions(currPair) + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, order := range resp { + if order.Type == 2 { + quoteCurrency := "" + baseCurrency := "" + if order.BTC > 0 { + baseCurrency = symbol.BTC + } else if order.XRP > 0 { + baseCurrency = symbol.XRP + } else { + log.Warnf("No quote currency found for OrderID '%v'", order.OrderID) + } + + if order.USD > 0 { + quoteCurrency = symbol.USD + } else if order.EUR > 0 { + quoteCurrency = symbol.EUR + } else { + log.Warnf("No quote currency found for OrderID '%v'", order.OrderID) + } + + var currPair pair.CurrencyPair + if quoteCurrency != "" && baseCurrency != "" { + currPair = pair.NewCurrencyPairWithDelimiter(baseCurrency, quoteCurrency, b.ConfigCurrencyPairFormat.Delimiter) + } + orderDate := time.Unix(order.Date, 0) + + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.OrderID), + OrderDate: orderDate, + Exchange: b.Name, + CurrencyPair: currPair, + }) + + } + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index eebd3af9..2069733d 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -422,9 +422,9 @@ func (b *Bittrex) GetOrder(uuid string) (Order, error) { return order, nil } -// GetOrderHistory is used to retrieve your order history. If currencyPair +// GetOrderHistoryForCurrency is used to retrieve your order history. If currencyPair // omitted it will return the entire order History. -func (b *Bittrex) GetOrderHistory(currencyPair string) (Order, error) { +func (b *Bittrex) GetOrderHistoryForCurrency(currencyPair string) (Order, error) { var orders Order values := url.Values{} diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index c17ddee6..6378bac0 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -181,14 +181,14 @@ func TestGetOrder(t *testing.T) { } } -func TestGetOrderHistory(t *testing.T) { +func TestGetOrderHistoryForCurrency(t *testing.T) { t.Parallel() - _, err := b.GetOrderHistory("") + _, err := b.GetOrderHistoryForCurrency("") if err == nil { t.Error("Test Failed - Bittrex - GetOrderHistory() error") } - _, err = b.GetOrderHistory("btc-ltc") + _, err = b.GetOrderHistoryForCurrency("btc-ltc") if err == nil { t.Error("Test Failed - Bittrex - GetOrderHistory() error") } @@ -305,17 +305,51 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := b.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.LTC)}, + } + + getOrdersRequest.Currencies[0].Delimiter = "-" + + _, err := b.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -339,7 +373,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.LTC, } - response, err := b.SubmitOrder(p, exchange.Buy, exchange.Limit, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -348,7 +382,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -365,12 +398,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := b.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -378,7 +408,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -395,12 +424,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := b.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -434,7 +461,7 @@ func TestWithdraw(t *testing.T) { _, err := b.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/bittrex/bittrex_types.go b/exchanges/bittrex/bittrex_types.go index c1ea773f..854281a5 100644 --- a/exchanges/bittrex/bittrex_types.go +++ b/exchanges/bittrex/bittrex_types.go @@ -183,6 +183,9 @@ type Order struct { IsConditional bool `json:"IsConditional"` Condition string `json:"Condition"` ConditionTarget string `json:"ConditionTarget"` + // Below Used in OrderHistory + TimeStamp string `json:"TimeStamp"` + Commission float64 `json:"Commission"` } `json:"result"` } diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 18774ffe..399971d2 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -3,7 +3,9 @@ package bittrex import ( "errors" "fmt" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -178,11 +180,11 @@ func (b *Bittrex) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]e // SubmitOrder submits a new order func (b *Bittrex) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - buy := side == exchange.Buy + buy := side == exchange.BuyOrderSide var response UUID var err error - if orderType != exchange.Limit { + if orderType != exchange.LimitOrderType { return submitOrderResponse, errors.New("not supported on exchange") } @@ -286,3 +288,89 @@ func (b *Bittrex) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) func (b *Bittrex) GetWithdrawCapabilities() uint32 { return b.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (b *Bittrex) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var currPair string + if len(getOrdersRequest.Currencies) == 1 { + currPair = getOrdersRequest.Currencies[0].Pair().String() + } + + resp, err := b.GetOpenOrders(currPair) + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, order := range resp.Result { + orderDate, err := time.Parse(time.RFC3339, order.Opened) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + b.Name, "GetActiveOrders", order.OrderUUID, order.Opened) + } + + currency := pair.NewCurrencyPairDelimiter(order.Exchange, b.ConfigCurrencyPairFormat.Delimiter) + orderType := exchange.OrderType(strings.ToUpper(order.Type)) + + orders = append(orders, exchange.OrderDetail{ + Amount: order.Quantity, + RemainingAmount: order.QuantityRemaining, + Price: order.Price, + OrderDate: orderDate, + ID: order.OrderUUID, + Exchange: b.Name, + OrderType: orderType, + CurrencyPair: currency, + }) + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (b *Bittrex) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var currPair string + if len(getOrdersRequest.Currencies) == 1 { + currPair = getOrdersRequest.Currencies[0].Pair().String() + } + + resp, err := b.GetOrderHistoryForCurrency(currPair) + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, order := range resp.Result { + orderDate, err := time.Parse(time.RFC3339, order.TimeStamp) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + b.Name, "GetActiveOrders", order.OrderUUID, order.Opened) + } + + currency := pair.NewCurrencyPairDelimiter(order.Exchange, b.ConfigCurrencyPairFormat.Delimiter) + orderType := exchange.OrderType(strings.ToUpper(order.Type)) + + orders = append(orders, exchange.OrderDetail{ + Amount: order.Quantity, + RemainingAmount: order.QuantityRemaining, + Price: order.Price, + OrderDate: orderDate, + ID: order.OrderUUID, + Exchange: b.Name, + OrderType: orderType, + Fee: order.Commission, + CurrencyPair: currency, + }) + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} diff --git a/exchanges/btcc/btcc_test.go b/exchanges/btcc/btcc_test.go index fdb1c670..ae8b3928 100644 --- a/exchanges/btcc/btcc_test.go +++ b/exchanges/btcc/btcc_test.go @@ -161,17 +161,48 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange b.SetDefaults() expectedResult := exchange.NoAPIWithdrawalMethodsText - // Act + withdrawPermissions := b.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -195,14 +226,13 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.LTC, } - _, err := b.SubmitOrder(p, exchange.Buy, exchange.Limit, 1, 1, "clientId") + _, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not Yet Implemented', received %v", err) } } func TestCancelExchangeOrder(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -219,17 +249,13 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := b.CancelOrder(orderCancellation) - - // Assert if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not Yet Implemented', received %v", err) } } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -246,10 +272,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act _, err := b.CancelAllOrders(orderCancellation) - // Assert if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not Yet Implemented', received %v", err) } diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index 53feabcf..bcbc2ae1 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -216,3 +216,14 @@ func (b *BTCC) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (b *BTCC) GetWithdrawCapabilities() uint32 { return b.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (b *BTCC) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + return nil, common.ErrNotYetImplemented +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (b *BTCC) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + return nil, common.ErrNotYetImplemented +} diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 0e416586..418f6a16 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -267,17 +267,18 @@ func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, return nil, errors.New(resp.ErrorMessage) } - for i := range resp.Orders { - resp.Orders[i].Price = resp.Orders[i].Price / common.SatoshisPerBTC - resp.Orders[i].OpenVolume = resp.Orders[i].OpenVolume / common.SatoshisPerBTC - resp.Orders[i].Volume = resp.Orders[i].Volume / common.SatoshisPerBTC + for _, order := range resp.Orders { + order.Price = order.Price / common.SatoshisPerBTC + order.OpenVolume = order.OpenVolume / common.SatoshisPerBTC + order.Volume = order.Volume / common.SatoshisPerBTC - for x := range resp.Orders[i].Trades { - resp.Orders[i].Trades[x].Fee = resp.Orders[i].Trades[x].Fee / common.SatoshisPerBTC - resp.Orders[i].Trades[x].Price = resp.Orders[i].Trades[x].Price / common.SatoshisPerBTC - resp.Orders[i].Trades[x].Volume = resp.Orders[i].Trades[x].Volume / common.SatoshisPerBTC + for _, trade := range order.Trades { + trade.Fee = trade.Fee / common.SatoshisPerBTC + trade.Price = trade.Price / common.SatoshisPerBTC + trade.Volume = trade.Volume / common.SatoshisPerBTC } } + return resp.Orders, nil } @@ -300,6 +301,18 @@ func (b *BTCMarkets) GetOpenOrders() ([]Order, error) { return nil, errors.New(resp.ErrorMessage) } + for _, order := range resp.Orders { + order.Price = order.Price / common.SatoshisPerBTC + order.OpenVolume = order.OpenVolume / common.SatoshisPerBTC + order.Volume = order.Volume / common.SatoshisPerBTC + + for _, trade := range order.Trades { + trade.Fee = trade.Fee / common.SatoshisPerBTC + trade.Price = trade.Price / common.SatoshisPerBTC + trade.Volume = trade.Volume / common.SatoshisPerBTC + } + } + return resp.Orders, nil } diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index 6b38c53b..7096b371 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -31,12 +31,9 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - BTC Markets Setup() init error") } - - if areTestAPIKeysSet() { - bConfig.APIKey = apiKey - bConfig.APISecret = apiSecret - bConfig.AuthenticatedAPISupport = true - } + bConfig.APIKey = apiKey + bConfig.APISecret = apiSecret + bConfig.AuthenticatedAPISupport = true b.Setup(bConfig) } @@ -155,7 +152,6 @@ func TestGetFundingHistory(t *testing.T) { } func TestCancelOrder(t *testing.T) { - _, err := b.CancelExistingOrder([]int64{1337}) if err == nil { @@ -266,17 +262,49 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.AutoWithdrawFiatText - // Act + withdrawPermissions := b.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := b.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + b.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.LTC)}, + } + + _, err := b.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -300,7 +328,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.LTC, } - response, err := b.SubmitOrder(p, exchange.Buy, exchange.Limit, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -309,7 +337,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -326,12 +353,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := b.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -339,7 +363,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange b.SetDefaults() TestSetup(t) @@ -356,12 +379,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := b.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -395,7 +416,7 @@ func TestWithdraw(t *testing.T) { _, err := b.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -428,7 +449,7 @@ func TestWithdrawFiat(t *testing.T) { _, err := b.WithdrawFiatFunds(withdrawFiatRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index fb77e0ef..68f0b293 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -245,17 +247,25 @@ func (b *BTCMarkets) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } for _, order := range orders { + var side exchange.OrderSide + if strings.EqualFold(order.OrderSide, exchange.AskOrderSide.ToString()) { + side = exchange.SellOrderSide + } else if strings.EqualFold(order.OrderSide, exchange.BidOrderSide.ToString()) { + side = exchange.BuyOrderSide + } + orderDate := time.Unix(int64(order.CreationTime), 0) + orderType := exchange.OrderType(strings.ToUpper(order.OrderType)) + OrderDetail.Amount = order.Volume - OrderDetail.BaseCurrency = order.Currency - OrderDetail.CreationTime = int64(order.CreationTime) + OrderDetail.OrderDate = orderDate OrderDetail.Exchange = b.GetName() OrderDetail.ID = order.ID - OrderDetail.OpenVolume = order.OpenVolume - OrderDetail.OrderSide = order.OrderSide - OrderDetail.OrderType = order.OrderType + OrderDetail.RemainingAmount = order.OpenVolume + OrderDetail.OrderSide = side + OrderDetail.OrderType = orderType OrderDetail.Price = order.Price - OrderDetail.QuoteCurrency = order.Instrument OrderDetail.Status = order.Status + OrderDetail.CurrencyPair = pair.NewCurrencyPairWithDelimiter(order.Instrument, order.Currency, b.ConfigCurrencyPairFormat.Delimiter) } return OrderDetail, nil @@ -300,3 +310,120 @@ func (b *BTCMarkets) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, erro func (b *BTCMarkets) GetWithdrawCapabilities() uint32 { return b.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (b *BTCMarkets) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := b.GetOpenOrders() + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, order := range resp { + var side exchange.OrderSide + if strings.EqualFold(order.OrderSide, exchange.AskOrderSide.ToString()) { + side = exchange.SellOrderSide + } else if strings.EqualFold(order.OrderSide, exchange.BidOrderSide.ToString()) { + side = exchange.BuyOrderSide + } + orderDate := time.Unix(int64(order.CreationTime), 0) + orderType := exchange.OrderType(strings.ToUpper(order.OrderType)) + + openOrder := exchange.OrderDetail{ + ID: order.ID, + Amount: order.Volume, + Exchange: b.Name, + RemainingAmount: order.OpenVolume, + OrderDate: orderDate, + OrderSide: side, + OrderType: orderType, + Price: order.Price, + Status: order.Status, + CurrencyPair: pair.NewCurrencyPairWithDelimiter(order.Instrument, order.Currency, b.ConfigCurrencyPairFormat.Delimiter), + } + + for _, trade := range order.Trades { + tradeDate := time.Unix(int64(trade.CreationTime), 0) + openOrder.Trades = append(openOrder.Trades, exchange.TradeHistory{ + Amount: trade.Volume, + Exchange: b.Name, + Price: trade.Price, + TID: trade.ID, + Timestamp: tradeDate, + Fee: trade.Fee, + Description: trade.Description, + }) + } + + orders = append(orders, openOrder) + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (b *BTCMarkets) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("Requires at least one currency pair to retrieve history") + } + + var respOrders []Order + for _, currency := range getOrdersRequest.Currencies { + resp, err := b.GetOrders(currency.FirstCurrency.String(), currency.SecondCurrency.String(), 200, 0, true) + if err != nil { + return nil, err + } + respOrders = append(respOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range respOrders { + var side exchange.OrderSide + if strings.EqualFold(order.OrderSide, exchange.AskOrderSide.ToString()) { + side = exchange.SellOrderSide + } else if strings.EqualFold(order.OrderSide, exchange.BidOrderSide.ToString()) { + side = exchange.BuyOrderSide + } + orderDate := time.Unix(int64(order.CreationTime), 0) + orderType := exchange.OrderType(strings.ToUpper(order.OrderType)) + + openOrder := exchange.OrderDetail{ + ID: order.ID, + Amount: order.Volume, + Exchange: b.Name, + RemainingAmount: order.OpenVolume, + OrderDate: orderDate, + OrderSide: side, + OrderType: orderType, + Price: order.Price, + Status: order.Status, + CurrencyPair: pair.NewCurrencyPairWithDelimiter(order.Instrument, order.Currency, b.ConfigCurrencyPairFormat.Delimiter), + } + + for _, trade := range order.Trades { + tradeDate := time.Unix(int64(trade.CreationTime), 0) + openOrder.Trades = append(openOrder.Trades, exchange.TradeHistory{ + Amount: trade.Volume, + Exchange: b.Name, + Price: trade.Price, + TID: trade.ID, + Timestamp: tradeDate, + Fee: trade.Fee, + Description: trade.Description, + }) + } + + orders = append(orders, openOrder) + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 89027f5d..18adeee6 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -396,17 +396,51 @@ func TestCalculateTradingFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange + c.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText - // Act + withdrawPermissions := c.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + c.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.LTC)}, + } + + _, err := c.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + c.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.LTC)}, + } + + _, err := c.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -430,7 +464,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.LTC, } - response, err := c.SubmitOrder(p, exchange.Buy, exchange.Limit, 1, 1, "clientId") + response, err := c.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -439,7 +473,7 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange + c.SetDefaults() TestSetup(t) @@ -456,12 +490,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := c.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -469,7 +500,7 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange + c.SetDefaults() TestSetup(t) @@ -486,12 +517,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := c.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -525,7 +554,7 @@ func TestWithdraw(t *testing.T) { _, err := c.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -548,7 +577,7 @@ func TestWithdrawFiat(t *testing.T) { _, err := c.WithdrawFiatFunds(withdrawFiatRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -571,7 +600,7 @@ func TestWithdrawInternationalBank(t *testing.T) { _, err := c.WithdrawFiatFundsToInternationalBank(withdrawFiatRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 40c2ad8c..94cc2bd2 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -3,7 +3,9 @@ package coinbasepro import ( "errors" "fmt" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -156,10 +158,10 @@ func (c *CoinbasePro) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, var submitOrderResponse exchange.SubmitOrderResponse var response string var err error - if orderType == exchange.Market { + if orderType == exchange.MarketOrderType { response, err = c.PlaceMarginOrder("", amount, amount, side.ToString(), p.Pair().String(), "") - } else if orderType == exchange.Limit { + } else if orderType == exchange.LimitOrderType { response, err = c.PlaceLimitOrder("", price, amount, side.ToString(), "", "", p.Pair().String(), "", false) } else { err = errors.New("not supported") @@ -259,3 +261,86 @@ func (c *CoinbasePro) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, err func (c *CoinbasePro) GetWithdrawCapabilities() uint32 { return c.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (c *CoinbasePro) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var respOrders []GeneralizedOrderResponse + for _, currency := range getOrdersRequest.Currencies { + resp, err := c.GetOrders([]string{"open", "pending", "active"}, exchange.FormatExchangeCurrency(c.Name, currency).String()) + if err != nil { + return nil, err + } + respOrders = append(respOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range respOrders { + currency := pair.NewCurrencyPairDelimiter(order.ProductID, c.ConfigCurrencyPairFormat.Delimiter) + orderSide := exchange.OrderSide(strings.ToUpper(order.Side)) + orderType := exchange.OrderType(strings.ToUpper(order.Type)) + orderDate, err := time.Parse(time.RFC3339, order.CreatedAt) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + c.Name, "GetActiveOrders", order.ID, order.CreatedAt) + } + + orders = append(orders, exchange.OrderDetail{ + ID: order.ID, + Amount: order.Size, + ExecutedAmount: order.FilledSize, + OrderType: orderType, + OrderDate: orderDate, + OrderSide: orderSide, + CurrencyPair: currency, + Exchange: c.Name, + }) + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (c *CoinbasePro) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var respOrders []GeneralizedOrderResponse + for _, currency := range getOrdersRequest.Currencies { + resp, err := c.GetOrders([]string{"done", "settled"}, exchange.FormatExchangeCurrency(c.Name, currency).String()) + if err != nil { + return nil, err + } + respOrders = append(respOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range respOrders { + currency := pair.NewCurrencyPairDelimiter(order.ProductID, c.ConfigCurrencyPairFormat.Delimiter) + orderSide := exchange.OrderSide(strings.ToUpper(order.Side)) + orderType := exchange.OrderType(strings.ToUpper(order.Type)) + orderDate, err := time.Parse(time.RFC3339, order.CreatedAt) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + c.Name, "GetActiveOrders", order.ID, order.CreatedAt) + } + + orders = append(orders, exchange.OrderDetail{ + ID: order.ID, + Amount: order.Size, + ExecutedAmount: order.FilledSize, + OrderType: orderType, + OrderDate: orderDate, + OrderSide: orderSide, + CurrencyPair: currency, + Exchange: c.Name, + }) + } + + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 4a92bed7..a19106cb 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -182,22 +182,50 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange + c.SetDefaults() expectedResult := exchange.WithdrawCryptoViaWebsiteOnlyText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - // Act + withdrawPermissions := c.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + c.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, 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) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.LTC)}, + } + + _, err := c.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if c.APIKey != "" && c.APIKey != "Key" && - c.APISecret != "" && c.APISecret != "Secret" { + if c.APIKey != "" && c.APIKey != "Key" { return true } return false @@ -216,7 +244,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.USD, } - response, err := c.SubmitOrder(p, exchange.Buy, exchange.Limit, 1, 10, "1234234") + response, err := c.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "1234234") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -225,7 +253,7 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange + c.SetDefaults() TestSetup(t) @@ -242,12 +270,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := c.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -255,7 +280,7 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange + c.SetDefaults() TestSetup(t) @@ -272,12 +297,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := c.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 447fd60a..4f8ed508 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -215,7 +217,7 @@ func (c *COINUT) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, order var submitOrderResponse exchange.SubmitOrderResponse var err error var APIresponse interface{} - isBuyOrder := side == exchange.Buy + isBuyOrder := side == exchange.BuyOrderSide clientIDInt, err := strconv.ParseUint(clientID, 0, 32) clientIDUint := uint32(clientIDInt) @@ -231,9 +233,9 @@ func (c *COINUT) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, order currencyArray := instruments.Instruments[p.Pair().String()] currencyID := currencyArray[0].InstID - if orderType == exchange.Limit { + if orderType == exchange.LimitOrderType { APIresponse, err = c.NewOrder(currencyID, amount, price, isBuyOrder, clientIDUint) - } else if orderType == exchange.Market { + } else if orderType == exchange.MarketOrderType { APIresponse, err = c.NewOrder(currencyID, amount, 0, isBuyOrder, clientIDUint) } else { return submitOrderResponse, errors.New("unsupported order type") @@ -382,3 +384,110 @@ func (c *COINUT) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (c *COINUT) GetWithdrawCapabilities() uint32 { return c.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (c *COINUT) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + instruments, err := c.GetInstruments() + if err != nil { + return nil, err + } + + var allTheOrders []OrderResponse + for instrument, allInstrumentData := range instruments.Instruments { + for _, instrumentData := range allInstrumentData { + for _, currency := range getOrdersRequest.Currencies { + currStr := fmt.Sprintf("%v%v%v", currency.FirstCurrency.String(), c.ConfigCurrencyPairFormat.Delimiter, currency.SecondCurrency.String()) + if strings.EqualFold(currStr, instrument) { + openOrders, err := c.GetOpenOrders(instrumentData.InstID) + if err != nil { + return nil, err + } + allTheOrders = append(allTheOrders, openOrders.Orders...) + + continue + } + } + } + } + + var orders []exchange.OrderDetail + for _, order := range allTheOrders { + for instrument, allInstrumentData := range instruments.Instruments { + for _, instrumentData := range allInstrumentData { + if instrumentData.InstID == int(order.InstrumentID) { + currPair := pair.NewCurrencyPairDelimiter(instrument, "") + orderSide := exchange.OrderSide(strings.ToUpper(order.Side)) + orderDate := time.Unix(order.Timestamp, 0) + orders = append(orders, exchange.OrderDetail{ + ID: strconv.FormatInt(order.OrderID, 10), + Amount: order.Quantity, + Price: order.Price, + Exchange: c.Name, + OrderSide: orderSide, + OrderDate: orderDate, + CurrencyPair: currPair, + }) + } + } + } + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (c *COINUT) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + instruments, err := c.GetInstruments() + if err != nil { + return nil, err + } + + var allTheOrders []OrderFilledResponse + for instrument, allInstrumentData := range instruments.Instruments { + for _, instrumentData := range allInstrumentData { + for _, currency := range getOrdersRequest.Currencies { + currStr := fmt.Sprintf("%v%v%v", currency.FirstCurrency.String(), c.ConfigCurrencyPairFormat.Delimiter, currency.SecondCurrency.String()) + if strings.EqualFold(currStr, instrument) { + orders, err := c.GetTradeHistory(instrumentData.InstID, -1, -1) + if err != nil { + return nil, err + } + allTheOrders = append(allTheOrders, orders.Trades...) + + continue + } + } + } + } + + var orders []exchange.OrderDetail + for _, order := range allTheOrders { + for instrument, allInstrumentData := range instruments.Instruments { + for _, instrumentData := range allInstrumentData { + if instrumentData.InstID == int(order.Order.InstrumentID) { + currPair := pair.NewCurrencyPairDelimiter(instrument, "") + orderSide := exchange.OrderSide(strings.ToUpper(order.Order.Side)) + orderDate := time.Unix(order.Order.Timestamp, 0) + orders = append(orders, exchange.OrderDetail{ + ID: strconv.FormatInt(order.Order.OrderID, 10), + Amount: order.Order.Quantity, + Price: order.Order.Price, + Exchange: c.Name, + OrderSide: orderSide, + OrderDate: orderDate, + CurrencyPair: currPair, + }) + } + } + } + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 13d149ad..ec8e4ed6 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/url" + "sort" "strings" "sync" "time" @@ -207,27 +208,32 @@ type AccountCurrencyInfo struct { // TradeHistory holds exchange history data type TradeHistory struct { - Timestamp int64 - TID int64 - Price float64 - Amount float64 - Exchange string - Type string + Timestamp time.Time + TID int64 + Price float64 + Amount float64 + Exchange string + Type string + Fee float64 + Description string } // OrderDetail holds order detail data type OrderDetail struct { - Exchange string - ID string - BaseCurrency string - QuoteCurrency string - OrderSide string - OrderType string - CreationTime int64 - Status string - Price float64 - Amount float64 - OpenVolume float64 + Exchange string + AccountID string + ID string + CurrencyPair pair.CurrencyPair + OrderSide OrderSide + OrderType OrderType + OrderDate time.Time + Status string + Price float64 + Amount float64 + ExecutedAmount float64 + RemainingAmount float64 + Fee float64 + Trades []TradeHistory } // FundHistory holds exchange funding history data @@ -316,6 +322,9 @@ type IBotExchange interface { GetOrderInfo(orderID int64) (OrderDetail, error) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) + GetOrderHistory(GetOrdersRequest GetOrdersRequest) ([]OrderDetail, error) + GetActiveOrders(getOrdersRequest GetOrdersRequest) ([]OrderDetail, error) + WithdrawCryptocurrencyFunds(wtihdrawRequest WithdrawRequest) (string, error) WithdrawFiatFunds(wtihdrawRequest WithdrawRequest) (string, error) WithdrawFiatFundsToInternationalBank(wtihdrawRequest WithdrawRequest) (string, error) @@ -817,9 +826,13 @@ type OrderType string // OrderType ...types const ( - Limit OrderType = "Limit" - Market OrderType = "Market" - ImmediateOrCancel OrderType = "IMMEDIATE_OR_CANCEL" + AnyOrderType OrderType = "ANY" + LimitOrderType OrderType = "LIMIT" + MarketOrderType OrderType = "MARKET" + ImmediateOrCancelOrderType OrderType = "IMMEDIATE_OR_CANCEL" + StopOrderType OrderType = "STOP" + TrailingStopOrderType OrderType = "TRAILINGSTOP" + UnknownOrderType OrderType = "UNKNOWN" ) // ToString changes the ordertype to the exchange standard and returns a string @@ -832,8 +845,11 @@ type OrderSide string // OrderSide types const ( - Buy OrderSide = "Buy" - Sell OrderSide = "Sell" + AnyOrderSide OrderSide = "ANY" + BuyOrderSide OrderSide = "BUY" + SellOrderSide OrderSide = "SELL" + BidOrderSide OrderSide = "BID" + AskOrderSide OrderSide = "ASK" ) // ToString changes the ordertype to the exchange standard and returns a string @@ -942,3 +958,224 @@ func (e *Base) FormatWithdrawPermissions() string { return NoAPIWithdrawalMethodsText } + +// GetOrdersRequest used for GetOrderHistory and GetOpenOrders wrapper functions +type GetOrdersRequest struct { + OrderType OrderType + OrderSide OrderSide + StartTicks time.Time + EndTicks time.Time + // Currencies Empty array = all currencies. Some endpoints only support singular currency enquiries + Currencies []pair.CurrencyPair +} + +// OrderStatus defines order status types +type OrderStatus string + +// All OrderStatus types +const ( + AnyOrderStatus OrderStatus = "ANY" + NewOrderStatus OrderStatus = "NEW" + ActiveOrderStatus OrderStatus = "ACTIVE" + PartiallyFilledOrderStatus OrderStatus = "PARTIALLY_FILLED" + FilledOrderStatus OrderStatus = "FILLED" + CancelledOrderStatus OrderStatus = "CANCELED" + PendingCancelOrderStatus OrderStatus = "PENDING_CANCEL" + RejectedOrderStatus OrderStatus = "REJECTED" + ExpiredOrderStatus OrderStatus = "EXPIRED" + HiddenOrderStatus OrderStatus = "HIDDEN" + UnknownOrderStatus OrderStatus = "UNKNOWN" +) + +// FilterOrdersBySide removes any OrderDetails that don't match the orderStatus provided +func FilterOrdersBySide(orders *[]OrderDetail, orderSide OrderSide) { + if orderSide == "" || orderSide == AnyOrderSide { + return + } + + var filteredOrders []OrderDetail + for _, orderDetail := range *orders { + if strings.EqualFold(string(orderDetail.OrderSide), string(orderSide)) { + filteredOrders = append(filteredOrders, orderDetail) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByType removes any OrderDetails that don't match the orderType provided +func FilterOrdersByType(orders *[]OrderDetail, orderType OrderType) { + if orderType == "" || orderType == AnyOrderType { + return + } + + var filteredOrders []OrderDetail + for _, orderDetail := range *orders { + if strings.EqualFold(string(orderDetail.OrderType), string(orderType)) { + filteredOrders = append(filteredOrders, orderDetail) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByTickRange removes any OrderDetails outside of the tick range +func FilterOrdersByTickRange(orders *[]OrderDetail, startTicks, endTicks time.Time) { + if startTicks.IsZero() || endTicks.IsZero() || + startTicks.Unix() == 0 || endTicks.Unix() == 0 || endTicks.Before(startTicks) { + return + } + + var filteredOrders []OrderDetail + for _, orderDetail := range *orders { + if orderDetail.OrderDate.Unix() >= startTicks.Unix() && orderDetail.OrderDate.Unix() <= endTicks.Unix() { + filteredOrders = append(filteredOrders, orderDetail) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByCurrencies removes any OrderDetails that do not match the provided currency list +// It is forgiving in that the provided currencies can match quote or base currencies +func FilterOrdersByCurrencies(orders *[]OrderDetail, currencies []pair.CurrencyPair) { + if len(currencies) <= 0 { + return + } + + var filteredOrders []OrderDetail + for _, orderDetail := range *orders { + matchFound := false + for _, currency := range currencies { + if !matchFound && orderDetail.CurrencyPair.Equal(currency, false) { + matchFound = true + } + } + + if matchFound { + filteredOrders = append(filteredOrders, orderDetail) + } + } + + *orders = filteredOrders +} + +// ByPrice used for sorting orders by price +type ByPrice []OrderDetail + +func (b ByPrice) Len() int { + return len(b) +} + +func (b ByPrice) Less(i, j int) bool { + return b[i].Price < b[j].Price +} + +func (b ByPrice) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByPrice the caller function to sort orders +func SortOrdersByPrice(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByPrice(*orders))) + } else { + sort.Sort(ByPrice(*orders)) + } +} + +// ByOrderType used for sorting orders by order type +type ByOrderType []OrderDetail + +func (b ByOrderType) Len() int { + return len(b) +} + +func (b ByOrderType) Less(i, j int) bool { + return b[i].OrderType.ToString() < b[j].OrderType.ToString() +} + +func (b ByOrderType) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByType the caller function to sort orders +func SortOrdersByType(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByOrderType(*orders))) + } else { + sort.Sort(ByOrderType(*orders)) + } +} + +// ByCurrency used for sorting orders by order currency +type ByCurrency []OrderDetail + +func (b ByCurrency) Len() int { + return len(b) +} + +func (b ByCurrency) Less(i, j int) bool { + return b[i].CurrencyPair.Pair().String() < b[j].CurrencyPair.Pair().String() +} + +func (b ByCurrency) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByCurrency the caller function to sort orders +func SortOrdersByCurrency(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByCurrency(*orders))) + } else { + sort.Sort(ByCurrency(*orders)) + } +} + +// ByDate used for sorting orders by order date +type ByDate []OrderDetail + +func (b ByDate) Len() int { + return len(b) +} + +func (b ByDate) Less(i, j int) bool { + return b[i].OrderDate.Unix() < b[j].OrderDate.Unix() +} + +func (b ByDate) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByDate the caller function to sort orders +func SortOrdersByDate(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByDate(*orders))) + } else { + sort.Sort(ByDate(*orders)) + } +} + +// ByOrderSide used for sorting orders by order side (buy sell) +type ByOrderSide []OrderDetail + +func (b ByOrderSide) Len() int { + return len(b) +} + +func (b ByOrderSide) Less(i, j int) bool { + return b[i].OrderSide.ToString() < b[j].OrderSide.ToString() +} + +func (b ByOrderSide) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersBySide the caller function to sort orders +func SortOrdersBySide(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByOrderSide(*orders))) + } else { + sort.Sort(ByOrderSide(*orders)) + } +} diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 8309b58a..60d38a48 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -2,12 +2,14 @@ package exchange import ( "net/http" + "strings" "testing" "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" + "github.com/thrasher-/gocryptotrader/currency/symbol" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -929,3 +931,248 @@ func TestOrderTypes(t *testing.T) { t.Errorf("test failed - unexpected string %s", os.ToString()) } } + +func TestFilterOrdersByType(t *testing.T) { + var orders []OrderDetail + + orders = append(orders, OrderDetail{ + OrderType: ImmediateOrCancelOrderType, + }) + orders = append(orders, OrderDetail{ + OrderType: LimitOrderType, + }) + + FilterOrdersByType(&orders, AnyOrderType) + if len(orders) != 2 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) + } + + FilterOrdersByType(&orders, LimitOrderType) + if len(orders) != 1 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + } + + FilterOrdersByType(&orders, StopOrderType) + if len(orders) != 0 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) + } +} + +func TestFilterOrdersBySide(t *testing.T) { + var orders []OrderDetail + + orders = append(orders, OrderDetail{ + OrderSide: BuyOrderSide, + }) + orders = append(orders, OrderDetail{ + OrderSide: SellOrderSide, + }) + orders = append(orders, OrderDetail{}) + + FilterOrdersBySide(&orders, AnyOrderSide) + if len(orders) != 3 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + } + + FilterOrdersBySide(&orders, BuyOrderSide) + if len(orders) != 1 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + } + + FilterOrdersBySide(&orders, SellOrderSide) + if len(orders) != 0 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) + } +} + +func TestFilterOrdersByTickRange(t *testing.T) { + var orders []OrderDetail + + orders = append(orders, OrderDetail{ + OrderDate: time.Unix(100, 0), + }) + orders = append(orders, OrderDetail{ + OrderDate: time.Unix(110, 0), + }) + orders = append(orders, OrderDetail{ + OrderDate: time.Unix(111, 0), + }) + + FilterOrdersByTickRange(&orders, time.Unix(0, 0), time.Unix(0, 0)) + if len(orders) != 3 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + } + + FilterOrdersByTickRange(&orders, time.Unix(100, 0), time.Unix(111, 0)) + if len(orders) != 3 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + } + + FilterOrdersByTickRange(&orders, time.Unix(101, 0), time.Unix(111, 0)) + if len(orders) != 2 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) + } + + FilterOrdersByTickRange(&orders, time.Unix(200, 0), time.Unix(300, 0)) + if len(orders) != 0 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) + } +} + +func TestFilterOrdersByCurrencies(t *testing.T) { + var orders []OrderDetail + + orders = append(orders, OrderDetail{ + CurrencyPair: pair.NewCurrencyPair(symbol.BTC, symbol.USD), + }) + orders = append(orders, OrderDetail{ + CurrencyPair: pair.NewCurrencyPair(symbol.LTC, symbol.EUR), + }) + orders = append(orders, OrderDetail{ + CurrencyPair: pair.NewCurrencyPair(symbol.DOGE, symbol.RUB), + }) + + currencies := []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.USD), pair.NewCurrencyPair(symbol.LTC, symbol.EUR), pair.NewCurrencyPair(symbol.DOGE, symbol.RUB)} + FilterOrdersByCurrencies(&orders, currencies) + if len(orders) != 3 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + } + + currencies = []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.USD), pair.NewCurrencyPair(symbol.LTC, symbol.EUR)} + FilterOrdersByCurrencies(&orders, currencies) + if len(orders) != 2 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) + } + + currencies = []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.USD)} + FilterOrdersByCurrencies(&orders, currencies) + if len(orders) != 1 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + } + + currencies = []pair.CurrencyPair{} + FilterOrdersByCurrencies(&orders, currencies) + if len(orders) != 1 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + } +} + +func TestSortOrdersByPrice(t *testing.T) { + orders := []OrderDetail{ + { + Price: 100, + }, { + Price: 0, + }, { + Price: 50, + }, + } + + SortOrdersByPrice(&orders, false) + if orders[0].Price != 0 { + t.Errorf("Test failed. Expected: '%v', received: '%v'", 0, orders[0].Price) + } + + SortOrdersByPrice(&orders, true) + if orders[0].Price != 100 { + t.Errorf("Test failed. Expected: '%v', received: '%v'", 100, orders[0].Price) + } +} + +func TestSortOrdersByDate(t *testing.T) { + orders := []OrderDetail{ + { + OrderDate: time.Unix(0, 0), + }, { + OrderDate: time.Unix(1, 0), + }, { + OrderDate: time.Unix(2, 0), + }, + } + + SortOrdersByDate(&orders, false) + if orders[0].OrderDate.Unix() != time.Unix(0, 0).Unix() { + t.Errorf("Test failed. Expected: '%v', received: '%v'", time.Unix(0, 0).Unix(), orders[0].OrderDate.Unix()) + } + + SortOrdersByDate(&orders, true) + if orders[0].OrderDate.Unix() != time.Unix(2, 0).Unix() { + t.Errorf("Test failed. Expected: '%v', received: '%v'", time.Unix(2, 0).Unix(), orders[0].OrderDate.Unix()) + } +} + +func TestSortOrdersByCurrency(t *testing.T) { + orders := []OrderDetail{ + { + CurrencyPair: pair.NewCurrencyPairWithDelimiter(symbol.BTC, symbol.USD, "-"), + }, { + CurrencyPair: pair.NewCurrencyPairWithDelimiter(symbol.DOGE, symbol.USD, "-"), + }, { + CurrencyPair: pair.NewCurrencyPairWithDelimiter(symbol.BTC, symbol.RUB, "-"), + }, { + CurrencyPair: pair.NewCurrencyPairWithDelimiter(symbol.LTC, symbol.EUR, "-"), + }, { + CurrencyPair: pair.NewCurrencyPairWithDelimiter(symbol.LTC, symbol.AUD, "-"), + }, + } + + SortOrdersByCurrency(&orders, false) + if orders[0].CurrencyPair.Pair().String() != symbol.BTC+"-"+symbol.RUB { + t.Errorf("Test failed. Expected: '%v', received: '%v'", symbol.BTC+"-"+symbol.RUB, orders[0].CurrencyPair.Pair().String()) + } + + SortOrdersByCurrency(&orders, true) + if orders[0].CurrencyPair.Pair().String() != symbol.LTC+"-"+symbol.EUR { + t.Errorf("Test failed. Expected: '%v', received: '%v'", symbol.LTC+"-"+symbol.EUR, orders[0].CurrencyPair.Pair().String()) + } +} + +func TestSortOrdersByOrderSide(t *testing.T) { + orders := []OrderDetail{ + { + OrderSide: BuyOrderSide, + }, { + OrderSide: SellOrderSide, + }, { + OrderSide: SellOrderSide, + }, { + OrderSide: BuyOrderSide, + }, + } + + SortOrdersBySide(&orders, false) + if !strings.EqualFold(orders[0].OrderSide.ToString(), BuyOrderSide.ToString()) { + t.Errorf("Test failed. Expected: '%v', received: '%v'", BuyOrderSide, orders[0].OrderSide) + } + + t.Log(orders) + + SortOrdersBySide(&orders, true) + if !strings.EqualFold(orders[0].OrderSide.ToString(), SellOrderSide.ToString()) { + t.Errorf("Test failed. Expected: '%v', received: '%v'", SellOrderSide, orders[0].OrderSide) + } +} + +func TestSortOrdersByOrderType(t *testing.T) { + orders := []OrderDetail{ + { + OrderType: MarketOrderType, + }, { + OrderType: LimitOrderType, + }, { + OrderType: ImmediateOrCancelOrderType, + }, { + OrderType: TrailingStopOrderType, + }, + } + + SortOrdersByType(&orders, false) + if !strings.EqualFold(orders[0].OrderType.ToString(), ImmediateOrCancelOrderType.ToString()) { + t.Errorf("Test failed. Expected: '%v', received: '%v'", ImmediateOrCancelOrderType, orders[0].OrderType) + } + + SortOrdersByType(&orders, true) + if !strings.EqualFold(orders[0].OrderType.ToString(), TrailingStopOrderType.ToString()) { + t.Errorf("Test failed. Expected: '%v', received: '%v'", TrailingStopOrderType, orders[0].OrderType) + } +} diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index 76d9732b..b011de0e 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/currency/symbol" exchange "github.com/thrasher-/gocryptotrader/exchanges" @@ -24,6 +25,18 @@ func TestDefault(t *testing.T) { } func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + exmoConf, err := cfg.GetExchangeConfig("EXMO") + if err != nil { + t.Error("Test Failed - OKCoin Setup() init error") + } + exmoConf.AuthenticatedAPISupport = true + exmoConf.APIKey = APIKey + exmoConf.APISecret = APISecret + + e.Setup(exmoConf) + e.AuthenticatedAPISupport = true e.APIKey = APIKey e.APISecret = APISecret @@ -226,17 +239,52 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange + e.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := e.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + e.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := e.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + e.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + currPair := pair.NewCurrencyPair(symbol.BTC, symbol.USD) + currPair.Delimiter = "_" + getOrdersRequest.Currencies = []pair.CurrencyPair{currPair} + + _, err := e.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -259,7 +307,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.USD, } - response, err := e.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 10, "1234234") + response, err := e.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "1234234") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -268,7 +316,7 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange + e.SetDefaults() TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -284,11 +332,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := e.CancelOrder(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -296,7 +342,7 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange + e.SetDefaults() TestSetup(t) @@ -312,11 +358,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := e.CancelAllOrders(orderCancellation) - // Assert + if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -350,7 +395,7 @@ func TestWithdraw(t *testing.T) { _, err := e.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/exmo/exmo_types.go b/exchanges/exmo/exmo_types.go index 4b59c7d2..91b1e38d 100644 --- a/exchanges/exmo/exmo_types.go +++ b/exchanges/exmo/exmo_types.go @@ -10,6 +10,7 @@ type Trades struct { Price float64 `json:"price,string"` Amount float64 `json:"amount,string"` Date int64 `json:"date"` + Pair string `json:"pair"` } // Orderbook holds the orderbook data diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index a0d0bd08..3bfba783 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -186,10 +188,10 @@ func (e *EXMO) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exch func (e *EXMO) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse var oT string - if orderType == exchange.Limit { + if orderType == exchange.LimitOrderType { return submitOrderResponse, errors.New("Unsupported order type") - } else if orderType == exchange.Market { - if side == exchange.Buy { + } else if orderType == exchange.MarketOrderType { + if side == exchange.BuyOrderSide { oT = "market_buy" } else { oT = "market_sell" @@ -303,3 +305,71 @@ func (e *EXMO) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (e *EXMO) GetWithdrawCapabilities() uint32 { return e.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (e *EXMO) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := e.GetOpenOrders() + if err != nil { + return nil, err + } + var orders []exchange.OrderDetail + for _, order := range resp { + symbol := pair.NewCurrencyPairDelimiter(order.Pair, "_") + orderDate := time.Unix(order.Created, 0) + orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.OrderID), + Amount: order.Quantity, + OrderDate: orderDate, + Price: order.Price, + OrderSide: orderSide, + Exchange: e.Name, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (e *EXMO) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("Currency must be supplied") + } + + var allTrades []UserTrades + for _, currency := range getOrdersRequest.Currencies { + resp, err := e.GetUserTrades(exchange.FormatExchangeCurrency(e.Name, currency).String(), "", "10000") + if err != nil { + return nil, err + } + for _, order := range resp { + allTrades = append(allTrades, order...) + } + } + + var orders []exchange.OrderDetail + for _, order := range allTrades { + symbol := pair.NewCurrencyPairDelimiter(order.Pair, "_") + orderDate := time.Unix(order.Date, 0) + orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.TradeID), + Amount: order.Quantity, + OrderDate: orderDate, + Price: order.Price, + OrderSide: orderSide, + Exchange: e.Name, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index 15359180..298084bb 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -31,6 +31,7 @@ const ( gateioCancelAllOrders = "private/cancelAllOrders" gateioWithdraw = "private/withdraw" gateioOpenOrders = "private/openOrders" + gateioTradeHistory = "private/tradeHistory" gateioDepositAddress = "private/depositAddress" gateioTicker = "ticker" gateioTickers = "tickers" @@ -428,8 +429,7 @@ func (g *Gateio) CancelAllExistingOrders(orderType int64, symbol string) error { return nil } -// -// GetOpenOrders retrieves all orders with an optional symbol filter +// GetOpenOrders retrieves all open orders with an optional symbol filter func (g *Gateio) GetOpenOrders(symbol string) (OpenOrdersResponse, error) { var params string var result OpenOrdersResponse @@ -450,6 +450,24 @@ func (g *Gateio) GetOpenOrders(symbol string) (OpenOrdersResponse, error) { return result, nil } +// GetTradeHistory retrieves all orders with an optional symbol filter +func (g *Gateio) GetTradeHistory(symbol string) (TradHistoryResponse, error) { + var params string + var result TradHistoryResponse + params = fmt.Sprintf("currencyPair=%s", symbol) + + err := g.SendAuthenticatedHTTPRequest("POST", gateioTradeHistory, params, &result) + if err != nil { + return result, err + } + + if result.Code > 0 { + return result, fmt.Errorf("code:%d message:%s", result.Code, result.Message) + } + + return result, nil +} + // SendAuthenticatedHTTPRequest sends authenticated requests to the Gateio API // To use this you must setup an APIKey and APISecret from the exchange func (g *Gateio) SendAuthenticatedHTTPRequest(method, endpoint, param string, result interface{}) error { diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index a9a503a9..9de181d4 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -31,7 +31,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - GateIO Setup() init error") } - gateioConfig.AuthenticatedAPISupport = true gateioConfig.APIKey = apiKey gateioConfig.APISecret = apiSecret @@ -240,17 +239,52 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange g.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := g.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + g.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := g.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + g.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + currPair := pair.NewCurrencyPair(symbol.LTC, symbol.BTC) + currPair.Delimiter = "_" + getOrdersRequest.Currencies = []pair.CurrencyPair{currPair} + + _, err := g.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -274,7 +308,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.LTC, SecondCurrency: symbol.BTC, } - response, err := g.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 10, "1234234") + response, err := g.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "1234234") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -283,7 +317,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange g.SetDefaults() TestSetup(t) @@ -300,12 +333,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := g.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -313,7 +343,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange g.SetDefaults() TestSetup(t) @@ -330,12 +359,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := g.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -383,7 +410,7 @@ func TestWithdraw(t *testing.T) { _, err := g.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index 9f7a8207..84bc7375 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -142,20 +142,41 @@ type OpenOrdersResponse struct { // OpenOrder details each open order type OpenOrder struct { - Amount string `json:"amount"` + Amount float64 `json:"amount,string"` CurrencyPair string `json:"currencyPair"` - FilledAmount int `json:"filledAmount"` - FilledRate int `json:"filledRate"` - InitialAmount string `json:"initialAmount"` + FilledAmount float64 `json:"filledAmount,string"` + FilledRate float64 `json:"filledRate"` + InitialAmount float64 `json:"initialAmount"` InitialRate float64 `json:"initialRate"` OrderNumber string `json:"orderNumber"` Rate float64 `json:"rate"` Status string `json:"status"` - Timestamp string `json:"timestamp"` - Total string `json:"total"` + Timestamp int64 `json:"timestamp"` + Total float64 `json:"total,string"` Type string `json:"type"` } +// TradHistoryResponse The full response for retrieving all user trade history +type TradHistoryResponse struct { + Code int `json:"code,omitempty"` + Elapsed string `json:"elapsed,omitempty"` + Message string `json:"message"` + Trades []TradesResponse `json:"trades"` + Result string `json:"result"` +} + +// TradesResponse details trade history +type TradesResponse struct { + ID string `json:"id"` + OrderID string `json:"orderid"` + Pair string `json:"pair"` + Type string `json:"type"` + Rate float64 `json:"rate,string"` + Amount float64 `json:"amount,string"` + Time string `json:"time"` + TimeUnix int64 `json:"time_unix,string"` +} + // WithdrawalFees the large list of predefined withdrawal fees // Prone to change var WithdrawalFees = map[string]float64{ diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index d64d16a7..04b3d835 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" "time" @@ -191,7 +192,7 @@ func (g *Gateio) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, order var submitOrderResponse exchange.SubmitOrderResponse var orderTypeFormat SpotNewOrderRequestParamsType - if side == exchange.Buy { + if side == exchange.BuyOrderSide { orderTypeFormat = SpotNewOrderRequestParamsTypeBuy } else { orderTypeFormat = SpotNewOrderRequestParamsTypeSell @@ -319,3 +320,77 @@ func (g *Gateio) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (g *Gateio) GetWithdrawCapabilities() uint32 { return g.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (g *Gateio) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var currPair string + if len(getOrdersRequest.Currencies) == 1 { + currPair = getOrdersRequest.Currencies[0].Pair().String() + } + + resp, err := g.GetOpenOrders(currPair) + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, order := range resp.Orders { + if order.Status != "open" { + continue + } + + symbol := pair.NewCurrencyPairDelimiter(order.CurrencyPair, g.ConfigCurrencyPairFormat.Delimiter) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + orderDate := time.Unix(order.Timestamp, 0) + + orders = append(orders, exchange.OrderDetail{ + ID: order.OrderNumber, + Amount: order.Amount, + Price: order.Rate, + RemainingAmount: order.FilledAmount, + OrderDate: orderDate, + OrderSide: side, + Exchange: g.Name, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (g *Gateio) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var trades []TradesResponse + for _, currency := range getOrdersRequest.Currencies { + resp, err := g.GetTradeHistory(currency.Pair().String()) + if err != nil { + return nil, err + } + trades = append(trades, resp.Trades...) + } + + var orders []exchange.OrderDetail + for _, trade := range trades { + symbol := pair.NewCurrencyPairDelimiter(trade.Pair, g.ConfigCurrencyPairFormat.Delimiter) + side := exchange.OrderSide(strings.ToUpper(trade.Type)) + orderDate := time.Unix(trade.TimeUnix, 0) + orders = append(orders, exchange.OrderDetail{ + ID: trade.OrderID, + Amount: trade.Amount, + Price: trade.Rate, + OrderDate: orderDate, + OrderSide: side, + Exchange: g.Name, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 257044ae..497811d6 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -373,19 +373,22 @@ func (g *Gemini) GetOrderStatus(orderID int64) (Order, error) { // GetOrders returns active orders in the market func (g *Gemini) GetOrders() ([]Order, error) { - var response struct { - orders []Order - Message string `json:"message"` + var response interface{} + + type orders struct { + orders []Order } err := g.SendAuthenticatedHTTPRequest("POST", geminiOrders, nil, &response) if err != nil { - return response.orders, err + return nil, err } - if response.Message != "" { - return response.orders, errors.New(response.Message) + switch r := response.(type) { + case orders: + return r.orders, nil + default: + return []Order{}, nil } - return response.orders, nil } // GetTradeHistory returns an array of trades that have been on the exchange @@ -512,9 +515,12 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st PayloadBase64 := common.Base64Encode(PayloadJSON) hmac := common.GetHMAC(common.HashSHA512_384, []byte(PayloadBase64), []byte(g.APISecret)) + headers["Content-Length"] = "0" + headers["Content-Type"] = "text/plain" headers["X-GEMINI-APIKEY"] = g.APIKey headers["X-GEMINI-PAYLOAD"] = PayloadBase64 headers["X-GEMINI-SIGNATURE"] = common.HexEncodeToString(hmac) + headers["Cache-Control"] = "no-cache" return g.SendPayload(method, g.APIUrl+"/v1/"+path, headers, strings.NewReader(""), result, true, g.Verbose) } diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index a19da251..a7874587 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -24,17 +24,17 @@ const ( apiKeyRole2 = "" sessionHeartBeat2 = false - canManipulateRealOrders = !false + canManipulateRealOrders = false ) func TestAddSession(t *testing.T) { var g1 Gemini if Session[1] == nil { - err := AddSession(&g1, 1, apiKey1, apiSecret1, apiKeyRole1, true, false) + err := AddSession(&g1, 1, apiKey1, apiSecret1, apiKeyRole1, true, true) if err != nil { t.Error("Test failed - AddSession() error", err) } - err = AddSession(&g1, 1, apiKey1, apiSecret1, apiKeyRole1, true, false) + err = AddSession(&g1, 1, apiKey1, apiSecret1, apiKeyRole1, true, true) if err == nil { t.Error("Test failed - AddSession() error", err) } @@ -55,7 +55,6 @@ func TestSetDefaults(t *testing.T) { } func TestSetup(t *testing.T) { - cfg := config.GetConfig() cfg.LoadConfig("../../testdata/configtest.json") geminiConfig, err := cfg.GetExchangeConfig("Gemini") @@ -74,6 +73,8 @@ func TestSetup(t *testing.T) { Session[2].APIKey = apiKey2 Session[2].APISecret = apiSecret2 + Session[1].APIUrl = geminiSandboxAPIURL + Session[2].APIUrl = geminiSandboxAPIURL } func TestGetSymbols(t *testing.T) { @@ -249,7 +250,6 @@ func setFeeBuilder() exchange.FeeBuilder { } func TestGetFee(t *testing.T) { - var feeBuilder = setFeeBuilder() if apiKey1 != "" && apiSecret1 != "" { // CryptocurrencyTradeFee Basic @@ -330,16 +330,54 @@ func TestFormatWithdrawPermissions(t *testing.T) { TestAddSession(t) TestSetDefaults(t) TestSetup(t) - // Arrange + expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - // Act + withdrawPermissions := Session[1].FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + TestAddSession(t) + TestSetDefaults(t) + TestSetup(t) + Session[1].Verbose = true + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := Session[1].GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + TestAddSession(t) + TestSetDefaults(t) + TestSetup(t) + Session[1].Verbose = true + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := Session[1].GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -364,7 +402,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.LTC, SecondCurrency: symbol.BTC, } - response, err := Session[1].SubmitOrder(p, exchange.Buy, exchange.Market, 1, 10, "1234234") + response, err := Session[1].SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "1234234") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -373,7 +411,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange TestAddSession(t) TestSetDefaults(t) TestSetup(t) @@ -391,12 +428,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := Session[1].CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -404,7 +438,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange TestAddSession(t) TestSetDefaults(t) TestSetup(t) @@ -422,12 +455,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := Session[1].CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -462,7 +493,7 @@ func TestWithdraw(t *testing.T) { _, err := Session[1].WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/gemini/gemini_types.go b/exchanges/gemini/gemini_types.go index 371b0a80..47f360bf 100644 --- a/exchanges/gemini/gemini_types.go +++ b/exchanges/gemini/gemini_types.go @@ -117,6 +117,9 @@ type TradeHistory struct { Exchange string `json:"exchange"` IsAuctionFilled bool `json:"is_auction_fill"` ClientOrderID string `json:"client_order_id"` + // Used to store values + BaseCurrency string + QuoteCurrency string } // TradeVolume holds Volume information diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 734d533e..f2a172ac 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -5,7 +5,9 @@ import ( "fmt" "net/url" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -241,3 +243,89 @@ func (g *Gemini) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (g *Gemini) GetWithdrawCapabilities() uint32 { return g.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (g *Gemini) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := g.GetOrders() + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, order := range resp { + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, g.ConfigCurrencyPairFormat.Delimiter) + var orderType exchange.OrderType + if order.Type == "exchange limit" { + orderType = exchange.LimitOrderType + } else if order.Type == "market buy" || order.Type == "market sell" { + orderType = exchange.MarketOrderType + } + + side := exchange.OrderSide(strings.ToUpper(order.Type)) + orderDate := time.Unix(order.Timestamp, 0) + + orders = append(orders, exchange.OrderDetail{ + Amount: order.OriginalAmount, + RemainingAmount: order.RemainingAmount, + ID: fmt.Sprintf("%v", order.OrderID), + ExecutedAmount: order.ExecutedAmount, + Exchange: g.Name, + OrderType: orderType, + OrderSide: side, + Price: order.Price, + CurrencyPair: symbol, + OrderDate: orderDate, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (g *Gemini) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("Currency must be supplied") + } + + var trades []TradeHistory + for _, currency := range getOrdersRequest.Currencies { + resp, err := g.GetTradeHistory(exchange.FormatExchangeCurrency(g.Name, currency).String(), getOrdersRequest.StartTicks.Unix()) + if err != nil { + return nil, err + } + + for _, trade := range resp { + trade.BaseCurrency = currency.FirstCurrency.String() + trade.QuoteCurrency = currency.SecondCurrency.String() + trades = append(trades, trade) + } + } + + var orders []exchange.OrderDetail + for _, trade := range trades { + side := exchange.OrderSide(strings.ToUpper(trade.Type)) + orderDate := time.Unix(trade.Timestamp, 0) + + orders = append(orders, exchange.OrderDetail{ + Amount: trade.Amount, + ID: fmt.Sprintf("%v", trade.OrderID), + Exchange: g.Name, + OrderDate: orderDate, + OrderSide: side, + Fee: trade.FeeAmount, + Price: trade.Price, + CurrencyPair: pair.NewCurrencyPairWithDelimiter(trade.BaseCurrency, trade.QuoteCurrency, g.ConfigCurrencyPairFormat.Delimiter), + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index 360cba4b..1961fd4c 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -35,6 +35,8 @@ const ( apiV2CryptoAddress = "api/2/account/crypto/address" apiV2CryptoWithdraw = "api/2/account/crypto/withdraw" apiV2TradeHistory = "api/2/history/trades" + apiV2OrderHistory = "api/2/history/order" + apiv2OpenOrders = "api/2/order" apiV2FeeInfo = "api/2/trading/fee" orders = "order" orderBuy = "api/2/order" @@ -387,8 +389,8 @@ func (h *HitBTC) GetActiveorders(currency string) ([]Order, error) { return resp, err } -// GetAuthenticatedTradeHistory returns your trade history -func (h *HitBTC) GetAuthenticatedTradeHistory(currency, start, end string) (interface{}, error) { +// GetTradeHistoryForCurrency returns your trade history +func (h *HitBTC) GetTradeHistoryForCurrency(currency, start, end string) (AuthenticatedTradeHistoryResponse, error) { values := url.Values{} if start != "" { @@ -399,11 +401,22 @@ func (h *HitBTC) GetAuthenticatedTradeHistory(currency, start, end string) (inte values.Set("end", end) } - if currency != "" && currency != "all" { - values.Set("currencyPair", currency) - result := AuthenticatedTradeHistoryResponse{} + values.Set("currencyPair", currency) + result := AuthenticatedTradeHistoryResponse{} - return result, h.SendAuthenticatedHTTPRequest("POST", apiV2TradeHistory, values, &result.Data) + return result, h.SendAuthenticatedHTTPRequest("POST", apiV2TradeHistory, values, &result.Data) +} + +// GetTradeHistoryForAllCurrencies returns your trade history +func (h *HitBTC) GetTradeHistoryForAllCurrencies(currency, start, end string) (AuthenticatedTradeHistoryAll, error) { + values := url.Values{} + + if start != "" { + values.Set("start", start) + } + + if end != "" { + values.Set("end", end) } values.Set("currencyPair", "all") @@ -412,6 +425,24 @@ func (h *HitBTC) GetAuthenticatedTradeHistory(currency, start, end string) (inte return result, h.SendAuthenticatedHTTPRequest("POST", apiV2TradeHistory, values, &result.Data) } +// GetOrders List of your order history. +func (h *HitBTC) GetOrders(currency string) ([]OrderHistoryResponse, error) { + values := url.Values{} + values.Set("symbol", currency) + var result []OrderHistoryResponse + + return result, h.SendAuthenticatedHTTPRequest("GET", apiV2OrderHistory, values, &result) +} + +// GetOpenOrders List of your currently open orders. +func (h *HitBTC) GetOpenOrders(currency string) ([]OrderHistoryResponse, error) { + values := url.Values{} + values.Set("symbol", currency) + var result []OrderHistoryResponse + + return result, h.SendAuthenticatedHTTPRequest("GET", apiv2OpenOrders, values, &result) +} + // PlaceOrder places an order on the exchange func (h *HitBTC) PlaceOrder(currency string, rate, amount float64, orderType, side string) (OrderResponse, error) { result := OrderResponse{} diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index 8776d265..d7af31b0 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -30,7 +30,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - HitBTC Setup() init error") } - hitbtcConfig.AuthenticatedAPISupport = true hitbtcConfig.APIKey = apiKey hitbtcConfig.APISecret = apiSecret @@ -164,17 +163,50 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange h.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := h.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + h.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.ETH, symbol.BTC)}, + } + + _, err := h.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + h.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.ETH, symbol.BTC)}, + } + + _, err := h.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -198,7 +230,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.DGD, SecondCurrency: symbol.BTC, } - response, err := h.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 10, "1234234") + response, err := h.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "1234234") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -207,7 +239,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange h.SetDefaults() TestSetup(t) @@ -224,12 +255,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := h.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -237,7 +265,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange h.SetDefaults() TestSetup(t) @@ -254,12 +281,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := h.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -293,7 +318,7 @@ func TestWithdraw(t *testing.T) { _, err := h.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/hitbtc/hitbtc_types.go b/exchanges/hitbtc/hitbtc_types.go index fb998b89..4833d6db 100644 --- a/exchanges/hitbtc/hitbtc_types.go +++ b/exchanges/hitbtc/hitbtc_types.go @@ -184,6 +184,23 @@ type AuthenticatedTradeHistoryResponse struct { Data []AuthentictedTradeHistory } +// OrderHistoryResponse used for GetOrderHistory +type OrderHistoryResponse struct { + ID string `json:"id"` + ClientOrderID string `json:"clientOrderId"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Status string `json:"status"` + Type string `json:"type"` + TimeInForce string `json:"timeInForce"` + Price float64 `json:"price,string"` + Quantity float64 `json:"quantity,string"` + PostOnly bool `json:"postOnly"` + CumQuantity float64 `json:"cumQuantity,string"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + // ResultingTrades holds resulting trade information type ResultingTrades struct { Amount float64 `json:"amount,string"` diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 2ddbc2df..8b5a2211 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -1,9 +1,12 @@ package hitbtc import ( + "errors" "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -264,3 +267,88 @@ func (h *HitBTC) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (h *HitBTC) GetWithdrawCapabilities() uint32 { return h.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (h *HitBTC) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("Currency must be supplied") + } + + var allOrders []OrderHistoryResponse + for _, currency := range getOrdersRequest.Currencies { + resp, err := h.GetOpenOrders(currency.Pair().String()) + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, h.ConfigCurrencyPairFormat.Delimiter) + side := exchange.OrderSide(strings.ToUpper(order.Side)) + orderDate, err := time.Parse(time.RFC3339, order.CreatedAt) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + h.Name, "GetActiveOrders", order.ID, order.CreatedAt) + } + + orders = append(orders, exchange.OrderDetail{ + ID: order.ID, + Amount: order.Quantity, + Exchange: h.Name, + Price: order.Price, + OrderDate: orderDate, + OrderSide: side, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (h *HitBTC) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("Currency must be supplied") + } + + var allOrders []OrderHistoryResponse + for _, currency := range getOrdersRequest.Currencies { + resp, err := h.GetOrders(currency.Pair().String()) + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, h.ConfigCurrencyPairFormat.Delimiter) + side := exchange.OrderSide(strings.ToUpper(order.Side)) + orderDate, err := time.Parse(time.RFC3339, order.CreatedAt) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + h.Name, "GetOrderHistory", order.ID, order.CreatedAt) + } + + orders = append(orders, exchange.OrderDetail{ + ID: order.ID, + Amount: order.Quantity, + Exchange: h.Name, + Price: order.Price, + OrderDate: orderDate, + OrderSide: side, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index 178eccb1..bae84724 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -48,7 +48,7 @@ const ( huobiGetOrder = "order/orders/%s" huobiGetOrderMatch = "order/orders/%s/matchresults" huobiGetOrders = "order/orders" - huobiGetOpenOrders = "order/order/openOrders" + huobiGetOpenOrders = "order/openOrders" huobiGetOrdersMatch = "orders/matchresults" huobiMarginTransferIn = "dw/transfer-in/margin" huobiMarginTransferOut = "dw/transfer-out/margin" @@ -570,7 +570,9 @@ func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int) ([]Order vals := url.Values{} vals.Set("symbol", symbol) vals.Set("accountID", accountID) - vals.Set("side", side) + if len(side) > 0 { + vals.Set("side", side) + } vals.Set("size", fmt.Sprintf("%v", size)) var result response diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index cc2bfe13..fdf4767f 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -66,7 +66,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Huobi Setup() init error") } - hConfig.AuthenticatedAPISupport = true hConfig.APIKey = apiKey hConfig.APISecret = apiSecret @@ -385,17 +384,50 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange h.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := h.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + h.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.USDT)}, + } + + _, err := h.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + h.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.USDT)}, + } + + _, err := h.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -430,7 +462,7 @@ func TestSubmitOrder(t *testing.T) { t.Errorf("Failed to get accounts. Err: %s", err) } - response, err := h.SubmitOrder(p, exchange.Buy, exchange.Limit, 1, 10, strconv.FormatInt(accounts[0].ID, 10)) + response, err := h.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, strconv.FormatInt(accounts[0].ID, 10)) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -439,7 +471,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange h.SetDefaults() TestSetup(t) @@ -456,12 +487,10 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := h.CancelOrder(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -469,7 +498,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange h.SetDefaults() TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -484,11 +512,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := h.CancelAllOrders(orderCancellation) - // Assert + if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -536,7 +563,7 @@ func TestWithdraw(t *testing.T) { _, err := h.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index b7dab784..19f49aaf 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -141,23 +141,26 @@ type CancelOrderBatch struct { // OrderInfo stores the order info type OrderInfo struct { - ID int `json:"id"` - Symbol string `json:"symbol"` - AccountID int `json:"account-id"` - Amount string `json:"amount"` - Price string `json:"price"` - CreatedAt int64 `json:"created-at"` - Type string `json:"type"` - FieldAmount string `json:"field-amount"` - FieldCashAmount string `json:"field-cash-amount"` - FieldFees string `json:"field-fees"` - FinishedAt int64 `json:"finished-at"` - UserID int `json:"user-id"` - Source string `json:"source"` - State string `json:"state"` - CanceledAt int `json:"canceled-at"` - Exchange string `json:"exchange"` - Batch string `json:"batch"` + ID int `json:"id"` + Symbol string `json:"symbol"` + AccountID float64 `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"` + Source string `json:"source"` + State string `json:"state"` + CanceledAt int `json:"canceled-at"` + Exchange string `json:"exchange"` + Batch string `json:"batch"` } // OrderMatchInfo stores the order match info diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index ce8d2cd7..49156952 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" @@ -259,14 +261,14 @@ func (h *HUOBI) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderT AccountID: int(accountID), } - if side == exchange.Buy && orderType == exchange.Market { + if side == exchange.BuyOrderSide && orderType == exchange.MarketOrderType { formattedType = SpotNewOrderRequestTypeBuyMarket - } else if side == exchange.Sell && orderType == exchange.Market { + } else if side == exchange.SellOrderSide && orderType == exchange.MarketOrderType { formattedType = SpotNewOrderRequestTypeSellMarket - } else if side == exchange.Buy && orderType == exchange.Limit { + } else if side == exchange.BuyOrderSide && orderType == exchange.LimitOrderType { formattedType = SpotNewOrderRequestTypeBuyLimit params.Price = price - } else if side == exchange.Sell && orderType == exchange.Limit { + } else if side == exchange.SellOrderSide && orderType == exchange.LimitOrderType { formattedType = SpotNewOrderRequestTypeSellLimit params.Price = price } else { @@ -374,3 +376,84 @@ func (h *HUOBI) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (h *HUOBI) GetWithdrawCapabilities() uint32 { return h.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (h *HUOBI) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("Currency must be supplied") + } + + side := "" + if getOrdersRequest.OrderSide == exchange.AnyOrderSide || getOrdersRequest.OrderSide == "" { + side = "" + } else if getOrdersRequest.OrderSide == exchange.SellOrderSide { + side = strings.ToLower(string(getOrdersRequest.OrderSide)) + } + + var allOrders []OrderInfo + for _, currency := range getOrdersRequest.Currencies { + resp, err := h.GetOpenOrders(h.ClientID, currency.Pair().Lower().String(), side, 500) + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, h.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(order.CreatedAt, 0) + + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.ID), + Exchange: h.Name, + Amount: order.Amount, + Price: order.Price, + OrderDate: orderDate, + ExecutedAmount: order.FilledAmount, + RemainingAmount: (order.Amount - order.FilledAmount), + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (h *HUOBI) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("Currency must be supplied") + } + + states := "partial-canceled,filled,canceled" + var allOrders []OrderInfo + for _, currency := range getOrdersRequest.Currencies { + resp, err := h.GetOrders(currency.Pair().Lower().String(), "", "", "", states, "", "", "") + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, h.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(order.CreatedAt, 0) + + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.ID), + Exchange: h.Name, + Amount: order.Amount, + Price: order.Price, + OrderDate: orderDate, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + + return orders, nil +} diff --git a/exchanges/huobihadax/huobihadax.go b/exchanges/huobihadax/huobihadax.go index cce2bfeb..8eae8c60 100644 --- a/exchanges/huobihadax/huobihadax.go +++ b/exchanges/huobihadax/huobihadax.go @@ -37,7 +37,7 @@ const ( huobihadaxAccountBalance = "account/accounts/%s/balance" huobihadaxOrderPlace = "order/orders/place" huobihadaxOrderCancel = "order/orders/%s/submitcancel" - huobihadaxGetOpenOrders = "order/order/openOrders" + huobihadaxGetOpenOrders = "order/openOrders" huobihadaxOrderCancelBatch = "order/orders/batchcancel" huobiHadaxBatchCancelOpenOrders = "order/orders/batchCancelOpenOrders" huobihadaxGetOrder = "order/orders/%s" @@ -497,7 +497,9 @@ func (h *HUOBIHADAX) GetOpenOrders(accountID, symbol, side string, size int) ([] vals := url.Values{} vals.Set("symbol", symbol) vals.Set("accountID", accountID) - vals.Set("side", side) + if len(side) > 0 { + vals.Set("side", side) + } vals.Set("size", fmt.Sprintf("%v", size)) var result response diff --git a/exchanges/huobihadax/huobihadax_test.go b/exchanges/huobihadax/huobihadax_test.go index a002c17b..a3ce08fb 100644 --- a/exchanges/huobihadax/huobihadax_test.go +++ b/exchanges/huobihadax/huobihadax_test.go @@ -62,7 +62,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - HuobiHadax Setup() init error") } - hadaxConfig.AuthenticatedAPISupport = true hadaxConfig.APIKey = apiKey hadaxConfig.APISecret = apiSecret @@ -365,17 +364,50 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange h.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := h.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + h.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.USDT)}, + } + + _, err := h.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + h.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.USDT)}, + } + + _, err := h.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -410,7 +442,7 @@ func TestSubmitOrder(t *testing.T) { t.Errorf("Failed to get accounts. Err: %s", err) } - response, err := h.SubmitOrder(p, exchange.Buy, exchange.Limit, 1, 10, strconv.FormatInt(accounts[0].ID, 10)) + response, err := h.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, strconv.FormatInt(accounts[0].ID, 10)) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -419,8 +451,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange - h.SetDefaults() TestSetup(t) @@ -437,12 +467,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := h.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -450,7 +477,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange h.SetDefaults() TestSetup(t) @@ -467,12 +493,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := h.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -520,7 +544,7 @@ func TestWithdraw(t *testing.T) { _, err := h.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/huobihadax/huobihadax_types.go b/exchanges/huobihadax/huobihadax_types.go index 85a24d6f..8bc591c5 100644 --- a/exchanges/huobihadax/huobihadax_types.go +++ b/exchanges/huobihadax/huobihadax_types.go @@ -123,23 +123,26 @@ type CancelOrderBatch struct { // OrderInfo stores the order info type OrderInfo struct { - ID int `json:"id"` - Symbol string `json:"symbol"` - AccountID int `json:"account-id"` - Amount string `json:"amount"` - Price string `json:"price"` - CreatedAt int64 `json:"created-at"` - Type string `json:"type"` - FieldAmount string `json:"field-amount"` - FieldCashAmount string `json:"field-cash-amount"` - FieldFees string `json:"field-fees"` - FinishedAt int64 `json:"finished-at"` - UserID int `json:"user-id"` - Source string `json:"source"` - State string `json:"state"` - CanceledAt int `json:"canceled-at"` - Exchange string `json:"exchange"` - Batch string `json:"batch"` + ID int `json:"id"` + Symbol string `json:"symbol"` + AccountID float64 `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"` + Source string `json:"source"` + State string `json:"state"` + CanceledAt int `json:"canceled-at"` + Exchange string `json:"exchange"` + Batch string `json:"batch"` } // OrderMatchInfo stores the order match info diff --git a/exchanges/huobihadax/huobihadax_wrapper.go b/exchanges/huobihadax/huobihadax_wrapper.go index 808cd00b..364b0140 100644 --- a/exchanges/huobihadax/huobihadax_wrapper.go +++ b/exchanges/huobihadax/huobihadax_wrapper.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -224,14 +226,14 @@ func (h *HUOBIHADAX) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, o AccountID: int(accountID), } - if side == exchange.Buy && orderType == exchange.Market { + if side == exchange.BuyOrderSide && orderType == exchange.MarketOrderType { formattedType = SpotNewOrderRequestTypeBuyMarket - } else if side == exchange.Sell && orderType == exchange.Market { + } else if side == exchange.SellOrderSide && orderType == exchange.MarketOrderType { formattedType = SpotNewOrderRequestTypeSellMarket - } else if side == exchange.Buy && orderType == exchange.Limit { + } else if side == exchange.BuyOrderSide && orderType == exchange.LimitOrderType { formattedType = SpotNewOrderRequestTypeBuyLimit params.Price = price - } else if side == exchange.Sell && orderType == exchange.Limit { + } else if side == exchange.SellOrderSide && orderType == exchange.LimitOrderType { formattedType = SpotNewOrderRequestTypeSellLimit params.Price = price } else { @@ -339,3 +341,84 @@ func (h *HUOBIHADAX) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, erro func (h *HUOBIHADAX) GetWithdrawCapabilities() uint32 { return h.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (h *HUOBIHADAX) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("Currency must be supplied") + } + + side := "" + if getOrdersRequest.OrderSide == exchange.AnyOrderSide || getOrdersRequest.OrderSide == "" { + side = "" + } else if getOrdersRequest.OrderSide == exchange.SellOrderSide { + side = strings.ToLower(string(getOrdersRequest.OrderSide)) + } + + var allOrders []OrderInfo + for _, currency := range getOrdersRequest.Currencies { + resp, err := h.GetOpenOrders(h.ClientID, currency.Pair().Lower().String(), side, 500) + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, h.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(order.CreatedAt, 0) + + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.ID), + Exchange: h.Name, + Amount: order.Amount, + Price: order.Price, + OrderDate: orderDate, + ExecutedAmount: order.FilledAmount, + RemainingAmount: (order.Amount - order.FilledAmount), + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (h *HUOBIHADAX) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("Currency must be supplied") + } + + states := "partial-canceled,filled,canceled" + var allOrders []OrderInfo + for _, currency := range getOrdersRequest.Currencies { + resp, err := h.GetOrders(currency.Pair().Lower().String(), "", "", "", states, "", "", "") + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, h.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(order.CreatedAt, 0) + + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.ID), + Exchange: h.Name, + Amount: order.Amount, + Price: order.Price, + OrderDate: orderDate, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + + return orders, nil +} diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index 8a01e239..828fa288 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -32,7 +32,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Gemini Setup() init error") } - itbitConfig.AuthenticatedAPISupport = true itbitConfig.APIKey = apiKey itbitConfig.APISecret = apiSecret @@ -234,17 +233,48 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange i.SetDefaults() expectedResult := exchange.WithdrawCryptoViaWebsiteOnlyText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - // Act + withdrawPermissions := i.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + i.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := i.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + i.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := i.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -267,7 +297,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.USDT, } - response, err := i.SubmitOrder(p, exchange.Buy, exchange.Limit, 1, 10, "hi") + response, err := i.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -276,7 +306,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange i.SetDefaults() TestSetup(t) @@ -293,12 +322,10 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := i.CancelOrder(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -306,7 +333,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange i.SetDefaults() TestSetup(t) @@ -323,12 +349,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := i.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 18ecdfbc..a2737977 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -4,7 +4,9 @@ import ( "fmt" "net/url" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -280,3 +282,98 @@ func (i *ItBit) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (i *ItBit) GetWithdrawCapabilities() uint32 { return i.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (i *ItBit) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + wallets, err := i.GetWallets(url.Values{}) + if err != nil { + return nil, err + } + + var allOrders []Order + for _, wallet := range wallets { + resp, err := i.GetOrders(wallet.ID, "", "open", 0, 0) + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Instrument, i.ConfigCurrencyPairFormat.Delimiter) + side := exchange.OrderSide(strings.ToUpper(order.Side)) + orderDate, err := time.Parse(time.RFC3339, order.CreatedTime) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + i.Name, "GetActiveOrders", order.ID, order.CreatedTime) + } + + orders = append(orders, exchange.OrderDetail{ + ID: order.ID, + OrderSide: side, + Amount: order.Amount, + ExecutedAmount: order.AmountFilled, + RemainingAmount: (order.Amount - order.AmountFilled), + Exchange: i.Name, + OrderDate: orderDate, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (i *ItBit) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + wallets, err := i.GetWallets(url.Values{}) + if err != nil { + return nil, err + } + + var allOrders []Order + for _, wallet := range wallets { + resp, err := i.GetOrders(wallet.ID, "", "", 0, 0) + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + if order.Type == "open" { + continue + } + + symbol := pair.NewCurrencyPairDelimiter(order.Instrument, i.ConfigCurrencyPairFormat.Delimiter) + side := exchange.OrderSide(strings.ToUpper(order.Side)) + orderDate, err := time.Parse(time.RFC3339, order.CreatedTime) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + i.Name, "GetActiveOrders", order.ID, order.CreatedTime) + } + + orders = append(orders, exchange.OrderDetail{ + ID: order.ID, + OrderSide: side, + Amount: order.Amount, + ExecutedAmount: order.AmountFilled, + RemainingAmount: (order.Amount - order.AmountFilled), + Exchange: i.Name, + OrderDate: orderDate, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 032ce552..6b60109d 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -540,15 +540,15 @@ func (k *Kraken) GetTradeBalance(args ...TradeBalanceOptions) (TradeBalanceInfo, } // GetOpenOrders returns all current open orders -func (k *Kraken) GetOpenOrders(args ...OrderInfoOptions) (OpenOrders, error) { +func (k *Kraken) GetOpenOrders(args OrderInfoOptions) (OpenOrders, error) { params := url.Values{} - if args[0].Trades { + if args.Trades { params.Set("trades", "true") } - if args[0].UserRef != 0 { - params.Set("userref", strconv.FormatInt(int64(args[0].UserRef), 10)) + if args.UserRef != 0 { + params.Set("userref", strconv.FormatInt(int64(args.UserRef), 10)) } var response struct { @@ -564,33 +564,31 @@ func (k *Kraken) GetOpenOrders(args ...OrderInfoOptions) (OpenOrders, error) { } // GetClosedOrders returns a list of closed orders -func (k *Kraken) GetClosedOrders(args ...GetClosedOrdersOptions) (ClosedOrders, error) { +func (k *Kraken) GetClosedOrders(args GetClosedOrdersOptions) (ClosedOrders, error) { params := url.Values{} - if args != nil { - if args[0].Trades { - params.Set("trades", "true") - } + if args.Trades { + params.Set("trades", "true") + } - if args[0].UserRef != 0 { - params.Set("userref", strconv.FormatInt(int64(args[0].UserRef), 10)) - } + if args.UserRef != 0 { + params.Set("userref", strconv.FormatInt(int64(args.UserRef), 10)) + } - if len(args[0].Start) != 0 { - params.Set("start", args[0].Start) - } + if len(args.Start) != 0 { + params.Set("start", args.Start) + } - if len(args[0].End) != 0 { - params.Set("end", args[0].End) - } + if len(args.End) != 0 { + params.Set("end", args.End) + } - if args[0].Ofs != 0 { - params.Set("ofs", strconv.FormatInt(args[0].Ofs, 10)) - } + if args.Ofs != 0 { + params.Set("ofs", strconv.FormatInt(args.Ofs, 10)) + } - if len(args[0].CloseTime) != 0 { - params.Set("closetime", args[0].CloseTime) - } + if len(args.CloseTime) != 0 { + params.Set("closetime", args.CloseTime) } var response struct { diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index fc2531ef..0c7e8791 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -30,7 +30,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - kraken Setup() init error", err) } - krakenConfig.AuthenticatedAPISupport = true krakenConfig.APIKey = apiKey krakenConfig.APISecret = apiSecret @@ -319,17 +318,48 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange k.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.AutoWithdrawFiatWithSetupText + " & " + exchange.WithdrawFiatWith2FAText - // Act + withdrawPermissions := k.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + k.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := k.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + k.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := k.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -353,7 +383,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.XBT, SecondCurrency: symbol.CAD, } - response, err := k.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 10, "hi") + response, err := k.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -362,7 +392,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange k.SetDefaults() TestSetup(t) @@ -379,12 +408,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := k.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -392,7 +418,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange k.SetDefaults() TestSetup(t) @@ -409,12 +434,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := k.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -463,7 +486,7 @@ func TestWithdraw(t *testing.T) { _, err := k.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -488,7 +511,7 @@ func TestWithdrawFiat(t *testing.T) { _, err := k.WithdrawFiatFunds(withdrawFiatRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) @@ -513,7 +536,7 @@ func TestWithdrawInternationalBank(t *testing.T) { _, err := k.WithdrawFiatFundsToInternationalBank(withdrawFiatRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 4439095b..5ec83791 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -2,8 +2,10 @@ package kraken import ( "errors" + "fmt" "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -290,3 +292,77 @@ func (k *Kraken) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (k *Kraken) GetWithdrawCapabilities() uint32 { return k.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (k *Kraken) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := k.GetOpenOrders(OrderInfoOptions{}) + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for ID, order := range resp.Open { + symbol := pair.NewCurrencyPairDelimiter(order.Descr.Pair, k.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(int64(order.StartTm), 0) + side := exchange.OrderSide(strings.ToUpper(order.Descr.Type)) + + orders = append(orders, exchange.OrderDetail{ + ID: ID, + Amount: order.Vol, + RemainingAmount: (order.Vol - order.VolExec), + ExecutedAmount: order.VolExec, + Exchange: k.Name, + OrderDate: orderDate, + Price: order.Price, + OrderSide: side, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (k *Kraken) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + request := GetClosedOrdersOptions{} + if getOrdersRequest.StartTicks.Unix() > 0 { + request.Start = fmt.Sprintf("%v", getOrdersRequest.StartTicks.Unix()) + } + if getOrdersRequest.EndTicks.Unix() > 0 { + request.End = fmt.Sprintf("%v", getOrdersRequest.EndTicks.Unix()) + } + + resp, err := k.GetClosedOrders(request) + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for ID, order := range resp.Closed { + symbol := pair.NewCurrencyPairDelimiter(order.Descr.Pair, k.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(int64(order.StartTm), 0) + side := exchange.OrderSide(strings.ToUpper(order.Descr.Type)) + + orders = append(orders, exchange.OrderDetail{ + ID: ID, + Amount: order.Vol, + RemainingAmount: (order.Vol - order.VolExec), + ExecutedAmount: order.VolExec, + Exchange: k.Name, + OrderDate: orderDate, + Price: order.Price, + OrderSide: side, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index 6299ff10..9892c7db 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -30,7 +30,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - LakeBTC Setup() init error") } - lakebtcConfig.AuthenticatedAPISupport = true lakebtcConfig.APIKey = apiKey lakebtcConfig.APISecret = apiSecret @@ -227,17 +226,48 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange l.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - // Act + withdrawPermissions := l.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + l.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := l.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + l.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := l.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -261,7 +291,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.EUR, } - response, err := l.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 10, "hi") + response, err := l.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -270,7 +300,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange l.SetDefaults() TestSetup(t) @@ -287,12 +316,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := l.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -300,7 +326,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange l.SetDefaults() TestSetup(t) @@ -317,12 +342,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := l.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -356,7 +379,7 @@ func TestWithdraw(t *testing.T) { _, err := l.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index ae3c4cc0..194d4566 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -150,7 +151,7 @@ func (l *LakeBTC) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]e // SubmitOrder submits a new order func (l *LakeBTC) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - isBuyOrder := side == exchange.Buy + isBuyOrder := side == exchange.BuyOrderSide response, err := l.Trade(isBuyOrder, amount, price, common.StringToLower(p.Pair().String())) if response.ID > 0 { @@ -264,3 +265,70 @@ func (l *LakeBTC) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) func (l *LakeBTC) GetWithdrawCapabilities() uint32 { return l.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (l *LakeBTC) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := l.GetOpenOrders() + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, order := range resp { + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, l.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(order.At, 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + + orders = append(orders, exchange.OrderDetail{ + Amount: order.Amount, + ID: fmt.Sprintf("%v", order.ID), + Price: order.Price, + OrderSide: side, + OrderDate: orderDate, + CurrencyPair: symbol, + Exchange: l.Name, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (l *LakeBTC) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := l.GetOrders([]int64{}) + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, order := range resp { + if order.State == "active" { + continue + } + + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, l.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(order.At, 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + + orders = append(orders, exchange.OrderDetail{ + Amount: order.Amount, + ID: fmt.Sprintf("%v", order.ID), + Price: order.Price, + OrderSide: side, + OrderDate: orderDate, + CurrencyPair: symbol, + Exchange: l.Name, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + + return orders, nil +} diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index be7f6ddc..9013bdba 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -212,8 +212,8 @@ func (l *Liqui) Trade(pair, orderType string, amount, price float64) (float64, e return result.OrderID, err } -// GetActiveOrders returns the list of your active orders. -func (l *Liqui) GetActiveOrders(pair string) (map[string]ActiveOrders, error) { +// GetOpenOrders returns the list of your active orders. +func (l *Liqui) GetOpenOrders(pair string) (map[string]ActiveOrders, error) { result := make(map[string]ActiveOrders) req := url.Values{} diff --git a/exchanges/liqui/liqui_test.go b/exchanges/liqui/liqui_test.go index ae0dc811..2081d228 100644 --- a/exchanges/liqui/liqui_test.go +++ b/exchanges/liqui/liqui_test.go @@ -30,7 +30,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - liqui Setup() init error") } - liquiConfig.AuthenticatedAPISupport = true liquiConfig.APIKey = apiKey liquiConfig.APISecret = apiSecret @@ -90,7 +89,7 @@ func TestAuthRequests(t *testing.T) { t.Error("Test Failed - liqui Trade() error", err) } - _, err = l.GetActiveOrders("eth_btc") + _, err = l.GetOpenOrders("eth_btc") if err == nil { t.Error("Test Failed - liqui GetActiveOrders() error", err) } @@ -223,17 +222,47 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange l.SetDefaults() expectedResult := exchange.WithdrawCryptoWithAPIPermissionText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := l.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + l.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.BTC, symbol.USDT)}, + } + + _, err := l.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + l.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := l.GetOrderHistory(getOrdersRequest) + if err != common.ErrFunctionNotSupported { + t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -256,7 +285,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.EUR, } - response, err := l.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 10, "hi") + response, err := l.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -265,7 +294,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange l.SetDefaults() TestSetup(t) @@ -281,11 +309,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := l.CancelOrder(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -293,7 +319,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange l.SetDefaults() TestSetup(t) @@ -309,11 +334,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := l.CancelAllOrders(orderCancellation) - // Assert + if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -347,7 +371,7 @@ func TestWithdraw(t *testing.T) { _, err := l.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/liqui/liqui_wrapper.go b/exchanges/liqui/liqui_wrapper.go index 02d06302..3ff6c157 100644 --- a/exchanges/liqui/liqui_wrapper.go +++ b/exchanges/liqui/liqui_wrapper.go @@ -1,9 +1,12 @@ package liqui import ( + "errors" "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -192,7 +195,7 @@ func (l *Liqui) CancelAllOrders(orderCancellation exchange.OrderCancellation) (e cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ OrderStatus: make(map[string]string), } - activeOrders, err := l.GetActiveOrders("") + activeOrders, err := l.GetOpenOrders("") if err != nil { return cancelAllOrdersResponse, err } @@ -260,3 +263,45 @@ func (l *Liqui) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (l *Liqui) GetWithdrawCapabilities() uint32 { return l.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (l *Liqui) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if len(getOrdersRequest.Currencies) <= 0 { + return nil, errors.New("Currency must be supplied") + } + + var orders []exchange.OrderDetail + for _, currency := range getOrdersRequest.Currencies { + resp, err := l.GetOpenOrders(exchange.FormatExchangeCurrency(l.Name, currency).String()) + if err != nil { + return nil, err + } + + for ID, order := range resp { + symbol := pair.NewCurrencyPairDelimiter(order.Pair, l.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(int64(order.TimestampCreated), 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + + orders = append(orders, exchange.OrderDetail{ + Amount: order.Amount, + ID: ID, + Price: order.Rate, + OrderSide: side, + OrderDate: orderDate, + Exchange: l.Name, + CurrencyPair: symbol, + }) + } + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (l *Liqui) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + return nil, common.ErrFunctionNotSupported +} diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index ff80f2e3..2b5de929 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -98,6 +98,8 @@ const ( localbitcoinsAuthRate = 0 localbitcoinsUnauthRate = 1 + // String response used with order status + null = "null" ) var ( diff --git a/exchanges/localbitcoins/localbitcoins_test.go b/exchanges/localbitcoins/localbitcoins_test.go index 748dd855..c69dae32 100644 --- a/exchanges/localbitcoins/localbitcoins_test.go +++ b/exchanges/localbitcoins/localbitcoins_test.go @@ -31,7 +31,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - LakeBTC Setup() init error") } - localbitcoinsConfig.AuthenticatedAPISupport = true localbitcoinsConfig.APIKey = apiKey localbitcoinsConfig.APISecret = apiSecret @@ -188,17 +187,48 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange l.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - // Act + withdrawPermissions := l.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + l.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := l.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + l.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := l.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -222,7 +252,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.EUR, } - response, err := l.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 10, "hi") + response, err := l.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -231,7 +261,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange l.SetDefaults() TestSetup(t) @@ -248,12 +277,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := l.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -261,7 +287,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange l.SetDefaults() TestSetup(t) @@ -278,12 +303,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := l.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -317,7 +340,7 @@ func TestWithdraw(t *testing.T) { _, err := l.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 24b7cf0a..6e7e4c7b 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/currency/symbol" @@ -293,3 +294,108 @@ func (l *LocalBitcoins) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, e func (l *LocalBitcoins) GetWithdrawCapabilities() uint32 { return l.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := l.GetDashboardInfo() + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for _, trade := range resp { + orderDate, err := time.Parse(time.RFC3339, trade.Data.CreatedAt) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + l.Name, "GetActiveOrders", trade.Data.Advertisement.ID, trade.Data.CreatedAt) + } + + var side exchange.OrderSide + if trade.Data.IsBuying { + side = exchange.BuyOrderSide + } else if trade.Data.IsSelling { + side = exchange.SellOrderSide + } + + orders = append(orders, exchange.OrderDetail{ + Amount: trade.Data.AmountBTC, + Price: trade.Data.Amount, + ID: fmt.Sprintf("%v", trade.Data.Advertisement.ID), + OrderDate: orderDate, + Fee: trade.Data.FeeBTC, + OrderSide: side, + CurrencyPair: pair.NewCurrencyPairWithDelimiter(symbol.BTC, trade.Data.Currency, l.ConfigCurrencyPairFormat.Delimiter), + Exchange: l.Name, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var allTrades []DashBoardInfo + resp, err := l.GetDashboardCancelledTrades() + if err != nil { + return nil, err + } + allTrades = append(allTrades, resp...) + + resp, err = l.GetDashboardClosedTrades() + if err != nil { + return nil, err + } + allTrades = append(allTrades, resp...) + + resp, err = l.GetDashboardReleasedTrades() + if err != nil { + return nil, err + } + allTrades = append(allTrades, resp...) + + var orders []exchange.OrderDetail + for _, trade := range allTrades { + orderDate, err := time.Parse(time.RFC3339, trade.Data.CreatedAt) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + l.Name, "GetActiveOrders", trade.Data.Advertisement.ID, trade.Data.CreatedAt) + } + + var side exchange.OrderSide + if trade.Data.IsBuying { + side = exchange.BuyOrderSide + } else if trade.Data.IsSelling { + side = exchange.SellOrderSide + } + + status := "" + if trade.Data.ReleasedAt != "" && trade.Data.ReleasedAt != null { + status = "Released" + } else if trade.Data.CanceledAt != "" && trade.Data.CanceledAt != null { + status = "Cancelled" + } else if trade.Data.ClosedAt != "" && trade.Data.ClosedAt != null { + status = "Closed" + } + + orders = append(orders, exchange.OrderDetail{ + Amount: trade.Data.AmountBTC, + Price: trade.Data.Amount, + ID: fmt.Sprintf("%v", trade.Data.Advertisement.ID), + OrderDate: orderDate, + Fee: trade.Data.FeeBTC, + OrderSide: side, + Status: status, + CurrencyPair: pair.NewCurrencyPairWithDelimiter(symbol.BTC, trade.Data.Currency, l.ConfigCurrencyPairFormat.Delimiter), + Exchange: l.Name, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/okcoin/okcoin.go b/exchanges/okcoin/okcoin.go index 2b7e7714..10be8d25 100644 --- a/exchanges/okcoin/okcoin.go +++ b/exchanges/okcoin/okcoin.go @@ -533,11 +533,11 @@ func (o *OKCoin) GetOrderInfoBatch(orderID []int64, symbol string) ([]OrderInfo, return result.Orders, nil } -// GetOrderHistory returns a history of orders -func (o *OKCoin) GetOrderHistory(pageLength, currentPage int64, status, symbol string) (OrderHistory, error) { +// GetOrderHistoryForCurrency returns a history of orders +func (o *OKCoin) GetOrderHistoryForCurrency(pageLength, currentPage, status int64, symbol string) (OrderHistory, error) { v := url.Values{} v.Set("symbol", symbol) - v.Set("status", status) + v.Set("status", strconv.FormatInt(status, 10)) v.Set("current_page", strconv.FormatInt(currentPage, 10)) v.Set("page_length", strconv.FormatInt(pageLength, 10)) result := OrderHistory{} diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 73d1c5fa..c7bff661 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -31,7 +31,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - OKCoin Setup() init error") } - okcoinConfig.AuthenticatedAPISupport = true okcoinConfig.APIKey = apiKey okcoinConfig.APISecret = apiSecret @@ -138,17 +137,50 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange o.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - // Act + withdrawPermissions := o.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + o.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := o.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + o.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := o.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -172,7 +204,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.EUR, } - response, err := o.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 10, "hi") + response, err := o.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -181,7 +213,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange o.SetDefaults() TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -197,11 +228,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := o.CancelOrder(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -209,7 +238,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange o.SetDefaults() TestSetup(t) @@ -226,12 +254,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := o.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -267,7 +293,7 @@ func TestWithdraw(t *testing.T) { _, err := o.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 9801c3f0..0cb5a066 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -202,14 +204,14 @@ func (o *OKCoin) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]ex func (o *OKCoin) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse var oT string - if orderType == exchange.Limit { - if side == exchange.Buy { + if orderType == exchange.LimitOrderType { + if side == exchange.BuyOrderSide { oT = "buy" } else { oT = "sell" } - } else if orderType == exchange.Market { - if side == exchange.Buy { + } else if orderType == exchange.MarketOrderType { + if side == exchange.BuyOrderSide { oT = "buy_market" } else { oT = "sell_market" @@ -329,3 +331,75 @@ func (o *OKCoin) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (o *OKCoin) GetWithdrawCapabilities() uint32 { return o.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (o *OKCoin) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var allOrders []OrderInfo + for _, currency := range getOrdersRequest.Currencies { + resp, err := o.GetOrderHistoryForCurrency(200, 0, 0, exchange.FormatExchangeCurrency(o.Name, currency).String()) + if err != nil { + return nil, err + } + + allOrders = append(allOrders, resp.Orders...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + // Status 2 == Filled, -1 == Cancelled. + if order.Status == 2 || order.Status == -1 { + continue + } + + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(order.Created, 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.OrderID), + Amount: order.Amount, + OrderDate: orderDate, + Price: order.Price, + OrderSide: side, + CurrencyPair: symbol, + Exchange: o.Name, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (o *OKCoin) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var allOrders []OrderInfo + for _, currency := range getOrdersRequest.Currencies { + resp, err := o.GetOrderInformation(-1, exchange.FormatExchangeCurrency(o.Name, currency).String()) + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp...) + } + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(order.Created, 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.OrderID), + Amount: order.Amount, + OrderDate: orderDate, + Price: order.Price, + OrderSide: side, + CurrencyPair: symbol, + Exchange: o.Name, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/okex/okex.go b/exchanges/okex/okex.go index 7c97f113..91f172e5 100644 --- a/exchanges/okex/okex.go +++ b/exchanges/okex/okex.go @@ -64,7 +64,8 @@ const ( spotTrade = "trade" spotBatchTrade = "batch_trade" spotCancelTrade = "cancel_order" - spotOrderInfo = "order_info" + spotOrderInfo = "order_info.do" + spotOrderHistory = "order_history.do" spotMultiOrderInfo = "orders_info" spotWithdraw = "withdraw.do" spotCancelWithdraw = "cancel_withdraw" @@ -1256,3 +1257,44 @@ func (o *OKEX) Withdrawal(symbol string, fee float64, tradePWD, address string, return resp.WithdrawID, nil } + +// GetOrderInformation withdraws a cryptocurrency to a supplied address +func (o *OKEX) GetOrderInformation(orderID int64, symbol string) ([]OrderInfo, error) { + type Response struct { + Result bool `json:"result"` + Orders []OrderInfo `json:"orders"` + } + + v := url.Values{} + v.Set("symbol", symbol) + v.Set("order_id", strconv.FormatInt(orderID, 10)) + result := Response{} + + err := o.SendAuthenticatedHTTPRequest(spotOrderInfo, v, &result) + if err != nil { + return nil, err + } + + if !result.Result { + return nil, errors.New("unable to retrieve order info") + } + + return result.Orders, nil +} + +// GetOrderHistoryForCurrency returns a history of orders +func (o *OKEX) GetOrderHistoryForCurrency(pageLength, currentPage, status int64, symbol string) (OrderHistory, error) { + v := url.Values{} + v.Set("symbol", symbol) + v.Set("status", strconv.FormatInt(status, 10)) + v.Set("current_page", strconv.FormatInt(currentPage, 10)) + v.Set("page_length", strconv.FormatInt(pageLength, 10)) + result := OrderHistory{} + + err := o.SendAuthenticatedHTTPRequest(spotOrderHistory, v, &result) + if err != nil { + return result, err + } + + return result, nil +} diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index b54f58f9..2eb9efef 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -33,7 +33,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Okex Setup() init error") } - okexConfig.AuthenticatedAPISupport = true okexConfig.APIKey = apiKey okexConfig.APISecret = apiSecret @@ -387,17 +386,50 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange o.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := o.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + o.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := o.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + o.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := o.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -421,7 +453,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.EUR, } - response, err := o.SubmitOrder(p, exchange.Buy, exchange.Market, 1, 10, "hi") + response, err := o.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -430,7 +462,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange o.SetDefaults() TestSetup(t) @@ -447,12 +478,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := o.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -460,7 +488,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange o.SetDefaults() TestSetup(t) @@ -477,12 +504,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := o.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -532,7 +557,7 @@ func TestWithdraw(t *testing.T) { _, err := o.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/okex/okex_types.go b/exchanges/okex/okex_types.go index 197644e7..9f06b244 100644 --- a/exchanges/okex/okex_types.go +++ b/exchanges/okex/okex_types.go @@ -469,3 +469,26 @@ type WithdrawalResponse struct { WithdrawID int `json:"withdraw_id"` Result bool `json:"result"` } + +// OrderInfo holds data on an order +type OrderInfo struct { + Amount float64 `json:"amount"` + AvgPrice float64 `json:"avg_price"` + Created int64 `json:"create_date"` + DealAmount float64 `json:"deal_amount"` + OrderID int64 `json:"order_id"` + OrdersID int64 `json:"orders_id"` + Price float64 `json:"price"` + Status int `json:"status"` + Symbol string `json:"symbol"` + Type string `json:"type"` +} + +// OrderHistory holds information on order history +type OrderHistory struct { + CurrentPage int `json:"current_page"` + Orders []OrderInfo `json:"orders"` + PageLength int `json:"page_length"` + Result bool `json:"result"` + Total int `json:"total"` +} diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go index b29b1a31..055d706d 100644 --- a/exchanges/okex/okex_wrapper.go +++ b/exchanges/okex/okex_wrapper.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -194,14 +196,14 @@ func (o *OKEX) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderTy var submitOrderResponse exchange.SubmitOrderResponse var oT SpotNewOrderRequestType - if orderType == exchange.Limit { - if side == exchange.Buy { + if orderType == exchange.LimitOrderType { + if side == exchange.BuyOrderSide { oT = SpotNewOrderRequestTypeBuy } else { oT = SpotNewOrderRequestTypeSell } - } else if orderType == exchange.Market { - if side == exchange.Buy { + } else if orderType == exchange.MarketOrderType { + if side == exchange.BuyOrderSide { oT = SpotNewOrderRequestTypeBuyMarket } else { oT = SpotNewOrderRequestTypeSellMarket @@ -322,3 +324,77 @@ func (o *OKEX) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (o *OKEX) GetWithdrawCapabilities() uint32 { return o.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (o *OKEX) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var allOrders []OrderInfo + for _, currency := range getOrdersRequest.Currencies { + resp, err := o.GetOrderHistoryForCurrency(200, 0, 0, exchange.FormatExchangeCurrency(o.Name, currency).String()) + if err != nil { + return nil, err + } + + allOrders = append(allOrders, resp.Orders...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + // Status 2 == Filled, -1 == Cancelled. + if order.Status == 2 || order.Status == -1 { + continue + } + + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(order.Created, 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.OrderID), + Amount: order.Amount, + OrderDate: orderDate, + Price: order.Price, + OrderSide: side, + CurrencyPair: symbol, + Exchange: o.Name, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (o *OKEX) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var allOrders []OrderInfo + for _, currency := range getOrdersRequest.Currencies { + resp, err := o.GetOrderInformation(-1, exchange.FormatExchangeCurrency(o.Name, currency).String()) + if err != nil { + return nil, err + } + + allOrders = append(allOrders, resp...) + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(order.Created, 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.OrderID), + Amount: order.Amount, + OrderDate: orderDate, + Price: order.Price, + OrderSide: side, + CurrencyPair: symbol, + Exchange: o.Name, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index c329fdb4..687f450f 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -456,35 +456,24 @@ func (p *Poloniex) GetOpenOrdersForAllCurrencies() (OpenOrdersResponseAll, error return result, nil } -// GetAuthenticatedTradeHistory returns account trade history -func (p *Poloniex) GetAuthenticatedTradeHistory(currency, start, end, limit string) (interface{}, error) { +// GetAuthenticatedTradeHistoryForCurrency returns account trade history +func (p *Poloniex) GetAuthenticatedTradeHistoryForCurrency(currency string, start, end, limit int64) (AuthenticatedTradeHistoryResponse, error) { values := url.Values{} - if start != "" { - values.Set("start", start) + if start > 0 { + values.Set("start", strconv.FormatInt(start, 10)) } - if limit != "" { - values.Set("limit", limit) + if limit > 0 { + values.Set("limit", strconv.FormatInt(limit, 10)) } - if end != "" { - values.Set("end", end) + if end > 0 { + values.Set("end", strconv.FormatInt(end, 10)) } - if currency != "" && currency != "all" { - values.Set("currencyPair", currency) - result := AuthenticatedTradeHistoryResponse{} - - err := p.SendAuthenticatedHTTPRequest("POST", poloniexTradeHistory, values, &result.Data) - if err != nil { - return result, err - } - - return result, nil - } - values.Set("currencyPair", "all") - result := AuthenticatedTradeHistoryAll{} + values.Set("currencyPair", currency) + result := AuthenticatedTradeHistoryResponse{} err := p.SendAuthenticatedHTTPRequest("POST", poloniexTradeHistory, values, &result.Data) if err != nil { @@ -494,6 +483,39 @@ func (p *Poloniex) GetAuthenticatedTradeHistory(currency, start, end, limit stri return result, nil } +// GetAuthenticatedTradeHistory returns account trade history +func (p *Poloniex) GetAuthenticatedTradeHistory(start, end, limit int64) (AuthenticatedTradeHistoryAll, error) { + values := url.Values{} + + if start > 0 { + values.Set("start", strconv.FormatInt(start, 10)) + } + + if limit > 0 { + values.Set("limit", strconv.FormatInt(limit, 10)) + } + + if end > 0 { + values.Set("end", strconv.FormatInt(end, 10)) + } + + values.Set("currencyPair", "all") + var result interface{} + + err := p.SendAuthenticatedHTTPRequest("POST", poloniexTradeHistory, values, &result) + if err != nil { + return AuthenticatedTradeHistoryAll{}, err + } + + // If there are no orders, Poloniex returns an empty array + switch r := result.(type) { + case AuthenticatedTradeHistoryAll: + return r, nil + default: + return AuthenticatedTradeHistoryAll{}, nil + } +} + // PlaceOrder places a new order on the exchange func (p *Poloniex) PlaceOrder(currency string, rate, amount float64, immediate, fillOrKill, buy bool) (OrderResponse, error) { result := OrderResponse{} diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index dea71b89..8d92ec75 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -31,7 +31,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Poloniex Setup() init error") } - poloniexConfig.AuthenticatedAPISupport = true poloniexConfig.APIKey = apiKey poloniexConfig.APISecret = apiSecret @@ -183,17 +182,48 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange p.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := p.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + p.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := p.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + p.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + } + + _, err := p.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -217,7 +247,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.LTC, } - response, err := p.SubmitOrder(pair, exchange.Buy, exchange.Market, 1, 10, "hi") + response, err := p.SubmitOrder(pair, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -226,7 +256,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange p.SetDefaults() TestSetup(t) @@ -243,12 +272,10 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := p.CancelOrder(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -256,7 +283,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange p.SetDefaults() TestSetup(t) @@ -273,12 +299,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := p.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -312,7 +336,7 @@ func TestWithdraw(t *testing.T) { _, err := p.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 7bc8ea78..9a7fc0d2 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -3,7 +3,9 @@ package poloniex import ( "fmt" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -164,8 +166,8 @@ func (p *Poloniex) GetExchangeHistory(currencyPair pair.CurrencyPair, assetType // SubmitOrder submits a new order func (p *Poloniex) SubmitOrder(currencyPair pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - fillOrKill := orderType == exchange.Market - isBuyOrder := side == exchange.Buy + fillOrKill := orderType == exchange.MarketOrderType + isBuyOrder := side == exchange.BuyOrderSide response, err := p.PlaceOrder(currencyPair.Pair().String(), price, amount, false, fillOrKill, isBuyOrder) if response.OrderNumber > 0 { @@ -289,3 +291,79 @@ func (p *Poloniex) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) func (p *Poloniex) GetWithdrawCapabilities() uint32 { return p.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (p *Poloniex) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := p.GetOpenOrdersForAllCurrencies() + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for currencyPair, openOrders := range resp.Data { + symbol := pair.NewCurrencyPairDelimiter(currencyPair, p.ConfigCurrencyPairFormat.Delimiter) + + for _, order := range openOrders { + orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) + orderDate, err := time.Parse(time.RFC3339, order.Date) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + p.Name, "GetActiveOrders", order.OrderNumber, order.Date) + } + + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.OrderNumber), + OrderSide: orderSide, + Amount: order.Amount, + OrderDate: orderDate, + Price: order.Rate, + CurrencyPair: symbol, + Exchange: p.Name, + }) + } + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (p *Poloniex) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + resp, err := p.GetAuthenticatedTradeHistory(getOrdersRequest.StartTicks.Unix(), getOrdersRequest.EndTicks.Unix(), 10000) + if err != nil { + return nil, err + } + + var orders []exchange.OrderDetail + for currencyPair, historicOrders := range resp.Data { + symbol := pair.NewCurrencyPairDelimiter(currencyPair, p.ConfigCurrencyPairFormat.Delimiter) + + for _, order := range historicOrders { + orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) + orderDate, err := time.Parse(time.RFC3339, order.Date) + if err != nil { + log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + p.Name, "GetActiveOrders", order.OrderNumber, order.Date) + } + + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.GlobalTradeID), + OrderSide: orderSide, + Amount: order.Amount, + OrderDate: orderDate, + Price: order.Rate, + CurrencyPair: symbol, + Exchange: p.Name, + }) + } + } + + exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/wex/wex.go b/exchanges/wex/wex.go index 65f63ba6..6e1db9dd 100644 --- a/exchanges/wex/wex.go +++ b/exchanges/wex/wex.go @@ -189,8 +189,8 @@ func (w *WEX) GetAccountInformation() (AccountInfo, error) { return result, nil } -// GetActiveOrders returns the active orders for a specific currency -func (w *WEX) GetActiveOrders(pair string) (map[string]ActiveOrders, error) { +// GetOpenOrders returns the active orders for a specific currency +func (w *WEX) GetOpenOrders(pair string) (map[string]ActiveOrders, error) { req := url.Values{} req.Add("pair", pair) @@ -266,20 +266,20 @@ func (w *WEX) GetTransactionHistory(TIDFrom, Count, TIDEnd int64, order, since, } // GetTradeHistory returns the trade history -func (w *WEX) GetTradeHistory(TIDFrom, Count, TIDEnd int64, order, since, end, pair string) (map[string]TradeHistory, error) { +func (w *WEX) GetTradeHistory(TIDFrom, Count, TIDEnd, since, end int64, order, pair string) (map[string]TradeHistory, error) { req := url.Values{} req.Add("from", strconv.FormatInt(TIDFrom, 10)) req.Add("count", strconv.FormatInt(Count, 10)) req.Add("from_id", strconv.FormatInt(TIDFrom, 10)) req.Add("end_id", strconv.FormatInt(TIDEnd, 10)) req.Add("order", order) - req.Add("since", since) - req.Add("end", end) + req.Add("since", strconv.FormatInt(since, 10)) + req.Add("end", strconv.FormatInt(end, 10)) req.Add("pair", pair) - var result map[string]TradeHistory + result := TradeHistoryResponse{} - return result, w.SendAuthenticatedHTTPRequest(wexTradeHistory, req, &result) + return result.Data, w.SendAuthenticatedHTTPRequest(wexTradeHistory, req, &result) } // WithdrawCoins withdraws coins for a specific coin diff --git a/exchanges/wex/wex_test.go b/exchanges/wex/wex_test.go index 76300a9a..0c3d529b 100644 --- a/exchanges/wex/wex_test.go +++ b/exchanges/wex/wex_test.go @@ -1,7 +1,9 @@ package wex import ( + "math" "testing" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" @@ -104,14 +106,14 @@ func TestGetAccountInfo(t *testing.T) { } } -func TestGetActiveOrders(t *testing.T) { +func GetOpenOrders(t *testing.T) { if isWexEncounteringIssues { t.Skip() } t.Parallel() - _, err := w.GetActiveOrders("") + _, err := w.GetOpenOrders("") if err == nil { - t.Error("Test Failed - GetActiveOrders() error", err) + t.Error("Test Failed - GetOpenOrders() error", err) } } @@ -159,17 +161,6 @@ func TestGetTransactionHistory(t *testing.T) { } } -func TestGetTradeHistory(t *testing.T) { - if isWexEncounteringIssues { - t.Skip() - } - t.Parallel() - _, err := w.GetTradeHistory(0, 0, 0, "", "", "", "") - if err == nil { - t.Error("Test Failed - GetTradeHistory() error", err) - } -} - func TestWithdrawCoins(t *testing.T) { if isWexEncounteringIssues { t.Skip() @@ -313,17 +304,53 @@ func TestFormatWithdrawPermissions(t *testing.T) { if isWexEncounteringIssues { t.Skip() } - // Arrange + w.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := w.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + w.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := w.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + w.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + StartTicks: time.Unix(0, 0), + EndTicks: time.Unix(math.MaxInt64, 0), + } + + _, err := w.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -350,7 +377,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.USD, } - response, err := w.SubmitOrder(pair, exchange.Buy, exchange.Market, 1, 10, "hi") + response, err := w.SubmitOrder(pair, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -362,7 +389,7 @@ func TestCancelExchangeOrder(t *testing.T) { if isWexEncounteringIssues { t.Skip() } - // Arrange + w.SetDefaults() TestSetup(t) @@ -379,12 +406,10 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := w.CancelOrder(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -395,7 +420,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { if isWexEncounteringIssues { t.Skip() } - // Arrange + w.SetDefaults() TestSetup(t) @@ -412,12 +437,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := w.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -451,7 +474,7 @@ func TestWithdraw(t *testing.T) { _, err := w.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/wex/wex_types.go b/exchanges/wex/wex_types.go index 9bfc40eb..45378248 100644 --- a/exchanges/wex/wex_types.go +++ b/exchanges/wex/wex_types.go @@ -114,6 +114,12 @@ type TransHistory struct { Timestamp float64 `json:"timestamp"` } +//TradeHistoryResponse returns all your trade history +type TradeHistoryResponse struct { + Success int64 `json:"success"` + Data map[string]TradeHistory `json:"return,omitempty"` +} + // TradeHistory stores trade history type TradeHistory struct { Pair string `json:"pair"` diff --git a/exchanges/wex/wex_wrapper.go b/exchanges/wex/wex_wrapper.go index f066f7a9..c2cc4ca9 100644 --- a/exchanges/wex/wex_wrapper.go +++ b/exchanges/wex/wex_wrapper.go @@ -2,8 +2,11 @@ package wex import ( "fmt" + "math" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -205,7 +208,7 @@ func (w *WEX) CancelAllOrders(orderCancellation exchange.OrderCancellation) (exc var allActiveOrders map[string]ActiveOrders for _, pair := range w.EnabledPairs { - activeOrders, err := w.GetActiveOrders(pair) + activeOrders, err := w.GetOpenOrders(pair) if err != nil { return cancelAllOrdersResponse, err } @@ -274,3 +277,70 @@ func (w *WEX) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (w *WEX) GetWithdrawCapabilities() uint32 { return w.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (w *WEX) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var orders []exchange.OrderDetail + for _, currency := range getOrdersRequest.Currencies { + resp, err := w.GetOpenOrders(exchange.FormatExchangeCurrency(w.Name, currency).String()) + if err != nil { + return nil, err + } + + for ID, order := range resp { + symbol := pair.NewCurrencyPairDelimiter(order.Pair, w.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(int64(order.TimestampCreated), 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + orders = append(orders, exchange.OrderDetail{ + ID: ID, + Amount: order.Amount, + Price: order.Rate, + OrderSide: side, + OrderDate: orderDate, + CurrencyPair: symbol, + Exchange: w.Name, + }) + } + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (w *WEX) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var allOrders []TradeHistory + for _, currency := range getOrdersRequest.Currencies { + resp, err := w.GetTradeHistory(0, 10000, math.MaxInt64, getOrdersRequest.StartTicks.Unix(), getOrdersRequest.EndTicks.Unix(), "DESC", exchange.FormatExchangeCurrency(w.Name, currency).String()) + if err != nil { + return nil, err + } + + for _, order := range resp { + allOrders = append(allOrders, order) + } + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Pair, w.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(int64(order.Timestamp), 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.OrderID), + Amount: order.Amount, + Price: order.Rate, + OrderSide: side, + OrderDate: orderDate, + CurrencyPair: symbol, + Exchange: w.Name, + }) + } + + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/yobit/yobit.go b/exchanges/yobit/yobit.go index f5269d61..5033777b 100644 --- a/exchanges/yobit/yobit.go +++ b/exchanges/yobit/yobit.go @@ -194,8 +194,8 @@ func (y *Yobit) Trade(pair, orderType string, amount, price float64) (int64, err return int64(result.OrderID), nil } -// GetActiveOrders returns the active orders for a specific currency -func (y *Yobit) GetActiveOrders(pair string) (map[string]ActiveOrders, error) { +// GetOpenOrders returns the active orders for a specific currency +func (y *Yobit) GetOpenOrders(pair string) (map[string]ActiveOrders, error) { req := url.Values{} req.Add("pair", pair) @@ -232,20 +232,28 @@ func (y *Yobit) CancelExistingOrder(OrderID int64) (bool, error) { } // GetTradeHistory returns the trade history -func (y *Yobit) GetTradeHistory(TIDFrom, Count, TIDEnd int64, order, since, end, pair string) (map[string]TradeHistory, error) { +func (y *Yobit) GetTradeHistory(TIDFrom, Count, TIDEnd, since, end int64, order, pair string) (map[string]TradeHistory, error) { req := url.Values{} req.Add("from", strconv.FormatInt(TIDFrom, 10)) req.Add("count", strconv.FormatInt(Count, 10)) req.Add("from_id", strconv.FormatInt(TIDFrom, 10)) req.Add("end_id", strconv.FormatInt(TIDEnd, 10)) req.Add("order", order) - req.Add("since", since) - req.Add("end", end) + req.Add("since", strconv.FormatInt(since, 10)) + req.Add("end", strconv.FormatInt(end, 10)) req.Add("pair", pair) - result := map[string]TradeHistory{} + result := TradeHistoryResponse{} - return result, y.SendAuthenticatedHTTPRequest(privateTradeHistory, req, &result) + err := y.SendAuthenticatedHTTPRequest(privateTradeHistory, req, &result) + if err != nil { + return nil, err + } + if result.Success == 0 { + return nil, errors.New(result.Error) + } + + return result.Data, nil } // GetCryptoDepositAddress returns the deposit address for a specific currency diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index 2838f4c7..9687ad73 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -1,7 +1,9 @@ package yobit import ( + "math" "testing" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" @@ -77,11 +79,11 @@ func TestGetAccountInfo(t *testing.T) { } } -func TestGetActiveOrders(t *testing.T) { +func TestGetOpenOrders(t *testing.T) { t.Parallel() - _, err := y.GetActiveOrders("") + _, err := y.GetOpenOrders("") if err == nil { - t.Error("Test Failed - GetActiveOrders() error", err) + t.Error("Test Failed - GetOpenOrders() error", err) } } @@ -109,14 +111,6 @@ func TestTrade(t *testing.T) { } } -func TestGetTradeHistory(t *testing.T) { - t.Parallel() - _, err := y.GetTradeHistory(0, 0, 0, "", "", "", "") - if err == nil { - t.Error("Test Failed - GetTradeHistory() error", err) - } -} - func TestWithdrawCoinsToAddress(t *testing.T) { t.Parallel() _, err := y.WithdrawCoinsToAddress("", 0, "") @@ -294,17 +288,52 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange y.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - // Act + withdrawPermissions := y.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + y.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := y.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + y.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + StartTicks: time.Unix(0, 0), + EndTicks: time.Unix(math.MaxInt64, 0), + } + + _, err := y.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -328,7 +357,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.BTC, SecondCurrency: symbol.USD, } - response, err := y.SubmitOrder(pair, exchange.Buy, exchange.Market, 1, 10, "hi") + response, err := y.SubmitOrder(pair, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -337,7 +366,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange y.SetDefaults() TestSetup(t) @@ -354,12 +382,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := y.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -367,7 +392,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange y.SetDefaults() TestSetup(t) @@ -384,12 +408,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := y.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -423,7 +445,7 @@ func TestWithdraw(t *testing.T) { _, err := y.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/yobit/yobit_types.go b/exchanges/yobit/yobit_types.go index a2d40331..42fc436e 100644 --- a/exchanges/yobit/yobit_types.go +++ b/exchanges/yobit/yobit_types.go @@ -105,6 +105,13 @@ type Trade struct { Error string `json:"error"` } +//TradeHistoryResponse returns all your trade history +type TradeHistoryResponse struct { + Success int64 `json:"success"` + Data map[string]TradeHistory `json:"return,omitempty"` + Error string `json:"error,omitempty"` +} + // TradeHistory stores trade history type TradeHistory struct { Pair string `json:"pair"` diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index ef63a3ce..9116dc3a 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -3,8 +3,11 @@ package yobit import ( "errors" "fmt" + "math" "strconv" + "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -187,7 +190,7 @@ func (y *Yobit) CancelAllOrders(orderCancellation exchange.OrderCancellation) (e var allActiveOrders []map[string]ActiveOrders for _, pair := range y.EnabledPairs { - activeOrdersForPair, err := y.GetActiveOrders(pair) + activeOrdersForPair, err := y.GetOpenOrders(pair) if err != nil { return cancelAllOrdersResponse, err } @@ -267,3 +270,70 @@ func (y *Yobit) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (y *Yobit) GetWithdrawCapabilities() uint32 { return y.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +func (y *Yobit) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var orders []exchange.OrderDetail + for _, currency := range getOrdersRequest.Currencies { + resp, err := y.GetOpenOrders(exchange.FormatExchangeCurrency(y.Name, currency).String()) + if err != nil { + return nil, err + } + + for ID, order := range resp { + symbol := pair.NewCurrencyPairDelimiter(order.Pair, y.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(int64(order.TimestampCreated), 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + orders = append(orders, exchange.OrderDetail{ + ID: ID, + Amount: order.Amount, + Price: order.Rate, + OrderSide: side, + OrderDate: orderDate, + CurrencyPair: symbol, + Exchange: y.Name, + }) + } + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +func (y *Yobit) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var allOrders []TradeHistory + for _, currency := range getOrdersRequest.Currencies { + resp, err := y.GetTradeHistory(0, 10000, math.MaxInt64, getOrdersRequest.StartTicks.Unix(), getOrdersRequest.EndTicks.Unix(), "DESC", exchange.FormatExchangeCurrency(y.Name, currency).String()) + if err != nil { + return nil, err + } + + for _, order := range resp { + allOrders = append(allOrders, order) + } + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Pair, y.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(int64(order.Timestamp), 0) + side := exchange.OrderSide(strings.ToUpper(order.Type)) + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.OrderID), + Amount: order.Amount, + Price: order.Rate, + OrderSide: side, + OrderDate: orderDate, + CurrencyPair: symbol, + Exchange: y.Name, + }) + } + + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index af865c3c..644bd3f8 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -33,6 +33,7 @@ const ( zbTickers = "allTicker" zbDepth = "depth" zbUnfinishedOrdersIgnoreTradeType = "getUnfinishedOrdersIgnoreTradeType" + zbGetOrdersGet = "getOrders" zbWithdraw = "withdraw" zbDepositAddress = "getUserAddress" @@ -194,14 +195,14 @@ func (z *ZB) GetAccountInformation() (AccountsResponse, error) { } // GetUnfinishedOrdersIgnoreTradeType returns unfinished orders -func (z *ZB) GetUnfinishedOrdersIgnoreTradeType(currency, pageindex, pagesize string) ([]UnfinishedOpenOrder, error) { - var result []UnfinishedOpenOrder +func (z *ZB) GetUnfinishedOrdersIgnoreTradeType(currency string, pageindex, pagesize int64) ([]Order, error) { + var result []Order vals := url.Values{} vals.Set("accesskey", z.APIKey) vals.Set("method", zbUnfinishedOrdersIgnoreTradeType) vals.Set("currency", currency) - vals.Set("pageIndex", pageindex) - vals.Set("pageSize", pagesize) + vals.Set("pageIndex", strconv.FormatInt(pageindex, 10)) + vals.Set("pageSize", strconv.FormatInt(pagesize, 10)) err := z.SendAuthenticatedHTTPRequest("GET", vals, &result) if err != nil { @@ -211,6 +212,24 @@ func (z *ZB) GetUnfinishedOrdersIgnoreTradeType(currency, pageindex, pagesize st return result, nil } +// GetOrders returns finished orders +func (z *ZB) GetOrders(currency string, pageindex, side int64) ([]Order, error) { + var response []Order + vals := url.Values{} + vals.Set("accesskey", z.APIKey) + vals.Set("method", zbGetOrdersGet) + vals.Set("currency", currency) + vals.Set("pageIndex", strconv.FormatInt(pageindex, 10)) + vals.Set("tradeType", strconv.FormatInt(side, 10)) + + err := z.SendAuthenticatedHTTPRequest("GET", vals, &response) + if err != nil { + return response, err + } + + return response, nil +} + // GetMarkets returns market information including pricing, symbols and // each symbols decimal precision func (z *ZB) GetMarkets() (map[string]MarketResponseItem, error) { diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index ffd5de5f..66bddd10 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -31,7 +31,6 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - ZB Setup() init error") } - zbConfig.AuthenticatedAPISupport = true zbConfig.APIKey = apiKey zbConfig.APISecret = apiSecret @@ -220,17 +219,51 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - // Arrange z.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - // Act + withdrawPermissions := z.FormatWithdrawPermissions() - // Assert + if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } +func TestGetActiveOrders(t *testing.T) { + z.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := z.GetActiveOrders(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get open orders: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestGetOrderHistory(t *testing.T) { + z.SetDefaults() + TestSetup(t) + + var getOrdersRequest = exchange.GetOrdersRequest{ + OrderType: exchange.AnyOrderType, + OrderSide: exchange.BuyOrderSide, + Currencies: []pair.CurrencyPair{pair.NewCurrencyPair(symbol.LTC, symbol.BTC)}, + } + + _, err := z.GetOrderHistory(getOrdersRequest) + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not get order history: %s", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -253,7 +286,7 @@ func TestSubmitOrder(t *testing.T) { FirstCurrency: symbol.QTUM, SecondCurrency: symbol.USDT, } - response, err := z.SubmitOrder(pair, exchange.Buy, exchange.Market, 1, 10, "hi") + response, err := z.SubmitOrder(pair, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -262,7 +295,6 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - // Arrange z.SetDefaults() TestSetup(t) @@ -279,12 +311,9 @@ func TestCancelExchangeOrder(t *testing.T) { CurrencyPair: currencyPair, } - // Act err := z.CancelOrder(orderCancellation) - - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -292,7 +321,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - // Arrange z.SetDefaults() TestSetup(t) @@ -309,12 +337,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - // Act resp, err := z.CancelAllOrders(orderCancellation) - // Assert if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Could not cancel orders: %v", err) @@ -363,7 +389,7 @@ func TestWithdraw(t *testing.T) { _, err := z.WithdrawCryptocurrencyFunds(withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { - t.Errorf("Expecting an error when no keys are set: %v", err) + t.Error("Expecting an error when no keys are set") } if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) diff --git a/exchanges/zb/zb_types.go b/exchanges/zb/zb_types.go index 346cdcf5..73791558 100644 --- a/exchanges/zb/zb_types.go +++ b/exchanges/zb/zb_types.go @@ -1,7 +1,11 @@ package zb -import "time" -import "github.com/thrasher-/gocryptotrader/currency/symbol" +import ( + "time" + + "github.com/thrasher-/gocryptotrader/currency/symbol" + exchange "github.com/thrasher-/gocryptotrader/exchanges" +) // OrderbookResponse holds the orderbook data for a symbol type OrderbookResponse struct { @@ -29,8 +33,8 @@ type AccountsBaseResponse struct { AuthMobileEnabled bool `json:"auth_mobile_enabled"` //是否开通手机验证 } -// UnfinishedOpenOrder is the order details for retrieving all open orders -type UnfinishedOpenOrder struct { +// Order is the order details for retrieving all orders +type Order struct { Currency string `json:"currency"` ID int64 `json:"id"` Price int `json:"price"` @@ -39,7 +43,7 @@ type UnfinishedOpenOrder struct { TradeAmount int `json:"trade_amount"` TradeDate int `json:"trade_date"` TradeMoney int `json:"trade_money"` - Type int `json:"type"` + Type int64 `json:"type"` } // AccountsResponse 用户基本信息 @@ -235,3 +239,9 @@ var WithdrawalFees = map[string]float64{ symbol.PAX: 5, symbol.XTZ: 0.1, } + +// orderSideMap holds order type info based on Alphapoint data +var orderSideMap = map[int64]exchange.OrderSide{ + 0: exchange.BuyOrderSide, + 1: exchange.SellOrderSide, +} diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index b75a8944..f6f3f5e2 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -1,9 +1,11 @@ package zb import ( + "errors" "fmt" "strconv" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -170,7 +172,7 @@ func (z *ZB) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType var submitOrderResponse exchange.SubmitOrderResponse var oT SpotNewOrderRequestParamsType - if side == exchange.Buy { + if side == exchange.BuyOrderSide { oT = SpotNewOrderRequestParamsTypeBuy } else { oT = SpotNewOrderRequestParamsTypeSell @@ -217,14 +219,23 @@ func (z *ZB) CancelAllOrders(orderCancellation exchange.OrderCancellation) (exch cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ OrderStatus: make(map[string]string), } - var allOpenOrders []UnfinishedOpenOrder + var allOpenOrders []Order for _, currency := range z.GetEnabledCurrencies() { - openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(exchange.FormatExchangeCurrency(z.Name, currency).String(), "1", "10") - if err != nil { - return cancelAllOrdersResponse, err - } + var pageNumber int64 + // Limiting to 10 pages + for i := 0; i < 10; i++ { + openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(exchange.FormatExchangeCurrency(z.Name, currency).String(), 1, 10) + if err != nil { + return cancelAllOrdersResponse, err + } - allOpenOrders = append(allOpenOrders, openOrders...) + if len(openOrders) == 0 { + break + } + + allOpenOrders = append(allOpenOrders, openOrders...) + pageNumber++ + } } for _, openOrder := range allOpenOrders { @@ -285,3 +296,100 @@ func (z *ZB) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) { func (z *ZB) GetWithdrawCapabilities() uint32 { return z.GetWithdrawPermissions() } + +// GetActiveOrders retrieves any orders that are active/open +// This function is not concurrency safe due to orderSide/orderType maps +func (z *ZB) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var allOrders []Order + for _, currency := range getOrdersRequest.Currencies { + var pageNumber int64 + // Limiting to 10 pages + for i := 0; i < 10; i++ { + resp, err := z.GetUnfinishedOrdersIgnoreTradeType(exchange.FormatExchangeCurrency(z.Name, currency).String(), pageNumber, 10) + if err != nil { + return nil, err + } + if len(resp) == 0 { + break + } + + allOrders = append(allOrders, resp...) + pageNumber++ + } + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Currency, z.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(int64(order.TradeDate), 0) + orderSide := orderSideMap[order.Type] + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.ID), + Amount: order.TotalAmount, + Exchange: z.Name, + OrderDate: orderDate, + Price: float64(order.Price), + OrderSide: orderSide, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + + return orders, nil +} + +// GetOrderHistory retrieves account order information +// Can Limit response to specific order status +// This function is not concurrency safe due to orderSide/orderType maps +func (z *ZB) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + if getOrdersRequest.OrderSide == exchange.AnyOrderSide || getOrdersRequest.OrderSide == "" { + return nil, errors.New("Specific order side is required") + } + + var allOrders []Order + + var side int64 + if getOrdersRequest.OrderSide == exchange.BuyOrderSide { + side = 1 + } + + for _, currency := range getOrdersRequest.Currencies { + var pageNumber int64 + // Limiting to 10 pages + for i := 0; i < 10; i++ { + resp, err := z.GetOrders(exchange.FormatExchangeCurrency(z.Name, currency).String(), pageNumber, side) + if err != nil { + return nil, err + } + + if len(resp) == 0 { + break + } + + allOrders = append(allOrders, resp...) + pageNumber++ + } + } + + var orders []exchange.OrderDetail + for _, order := range allOrders { + symbol := pair.NewCurrencyPairDelimiter(order.Currency, z.ConfigCurrencyPairFormat.Delimiter) + orderDate := time.Unix(int64(order.TradeDate), 0) + orderSide := orderSideMap[order.Type] + orders = append(orders, exchange.OrderDetail{ + ID: fmt.Sprintf("%v", order.ID), + Amount: order.TotalAmount, + Exchange: z.Name, + OrderDate: orderDate, + Price: float64(order.Price), + OrderSide: orderSide, + CurrencyPair: symbol, + }) + } + + exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + + return orders, nil +}