diff --git a/engine/order_manager.go b/engine/order_manager.go index 54da6296..b3bcaf91 100644 --- a/engine/order_manager.go +++ b/engine/order_manager.go @@ -116,36 +116,29 @@ func (m *OrderManager) run() { } // CancelAllOrders iterates and cancels all orders for each exchange provided -func (m *OrderManager) CancelAllOrders(ctx context.Context, exchangeNames []exchange.IBotExchange) { +func (m *OrderManager) CancelAllOrders(ctx context.Context, exchanges []exchange.IBotExchange) { if m == nil || atomic.LoadInt32(&m.started) == 0 { return } - orders := m.orderStore.get() - if orders == nil { + allOrders := m.orderStore.get() + if len(allOrders) == 0 { return } - for i := range exchangeNames { - exchangeOrders, ok := orders[strings.ToLower(exchangeNames[i].GetName())] + for i := range exchanges { + orders, ok := allOrders[strings.ToLower(exchanges[i].GetName())] if !ok { continue } - for j := range exchangeOrders { - log.Debugf(log.OrderMgr, - "Order manager: Cancelling order(s) for exchange %s.", - exchangeNames[i].GetName()) - err := m.Cancel(ctx, &order.Cancel{ - Exchange: exchangeOrders[j].Exchange, - ID: exchangeOrders[j].ID, - AccountID: exchangeOrders[j].AccountID, - ClientID: exchangeOrders[j].ClientID, - WalletAddress: exchangeOrders[j].WalletAddress, - Type: exchangeOrders[j].Type, - Side: exchangeOrders[j].Side, - Pair: exchangeOrders[j].Pair, - AssetType: exchangeOrders[j].AssetType, - }) + for j := range orders { + log.Debugf(log.OrderMgr, "Order manager: Cancelling order(s) for exchange %s.", exchanges[i].GetName()) + cancel, err := orders[j].DeriveCancel() + if err != nil { + log.Error(log.OrderMgr, err) + continue + } + err = m.Cancel(ctx, cancel) if err != nil { log.Error(log.OrderMgr, err) } diff --git a/engine/order_manager_test.go b/engine/order_manager_test.go index 618fdf31..b3b09a9c 100644 --- a/engine/order_manager_test.go +++ b/engine/order_manager_test.go @@ -27,7 +27,6 @@ type omfExchange struct { // CancelOrder overrides testExchange's cancel order function // to do the bare minimum required with no API calls or credentials required func (f omfExchange) CancelOrder(ctx context.Context, o *order.Cancel) error { - o.Status = order.Cancelled return nil } @@ -414,9 +413,7 @@ func TestCancelOrder(t *testing.T) { Exchange: testExchange, ID: "1337", Side: order.Sell, - Status: order.New, AssetType: asset.Spot, - Date: time.Now(), Pair: pair, } err = m.Cancel(context.Background(), cancel) diff --git a/exchanges/huobi/huobi_futures.go b/exchanges/huobi/huobi_futures.go index 2711751a..d45fdc24 100644 --- a/exchanges/huobi/huobi_futures.go +++ b/exchanges/huobi/huobi_futures.go @@ -782,12 +782,13 @@ func (h *HUOBI) FPlaceBatchOrder(ctx context.Context, data []fBatchOrderData) (F } // FCancelOrder cancels a futures order -func (h *HUOBI) FCancelOrder(ctx context.Context, symbol, orderID, clientOrderID string) (FCancelOrderData, error) { +func (h *HUOBI) FCancelOrder(ctx context.Context, baseCurrency currency.Code, orderID, clientOrderID string) (FCancelOrderData, error) { var resp FCancelOrderData req := make(map[string]interface{}) - if symbol != "" { - req["symbol"] = symbol + if baseCurrency.IsEmpty() { + return resp, fmt.Errorf("cannot cancel futures order %w", currency.ErrCurrencyCodeEmpty) } + req["symbol"] = baseCurrency.String() // Upper and lower case are supported if orderID != "" { req["order_id"] = orderID } diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index a78ae3ec..00461dbb 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -505,11 +505,12 @@ func TestFPlaceBatchOrder(t *testing.T) { } func TestFCancelOrder(t *testing.T) { + t.Parallel() if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("skipping test: api keys not set or canManipulateRealOrders set to false") } - t.Parallel() - _, err := h.FCancelOrder(context.Background(), "BTC", "123", "") + + _, err := h.FCancelOrder(context.Background(), currency.BTC, "123", "") if err != nil { t.Error(err) } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 7068cb9e..abfd880f 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -1020,7 +1020,7 @@ func (h *HUOBI) CancelOrder(ctx context.Context, o *order.Cancel) error { case asset.CoinMarginedFutures: _, err = h.CancelSwapOrder(ctx, o.ID, o.ClientID, o.Pair) case asset.Futures: - _, err = h.FCancelOrder(ctx, o.Symbol, o.ClientID, o.ClientOrderID) + _, err = h.FCancelOrder(ctx, o.Pair.Base, o.ClientID, o.ClientOrderID) default: return fmt.Errorf("%v assetType not supported", o.AssetType) } diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 3b8710fe..8c9043a5 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -1634,3 +1634,43 @@ func TestDetail_CopyPointerOrderSlice(t *testing.T) { } } } + +func TestDeriveCancel(t *testing.T) { + t.Parallel() + var o *Detail + if _, err := o.DeriveCancel(); !errors.Is(err, errOrderDetailIsNil) { + t.Fatalf("received: '%v' but expected: '%v'", err, errOrderDetailIsNil) + } + + pair := currency.NewPair(currency.BTC, currency.AUD) + + o = &Detail{ + Exchange: "wow", + ID: "wow1", + AccountID: "wow2", + ClientID: "wow3", + ClientOrderID: "wow4", + WalletAddress: "wow5", + Type: Market, + Side: Long, + Pair: pair, + AssetType: asset.Futures, + } + cancel, err := o.DeriveCancel() + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if cancel.Exchange != "wow" || + cancel.ID != "wow1" || + cancel.AccountID != "wow2" || + cancel.ClientID != "wow3" || + cancel.ClientOrderID != "wow4" || + cancel.WalletAddress != "wow5" || + cancel.Type != Market || + cancel.Side != Long || + !cancel.Pair.Equal(pair) || + cancel.AssetType != asset.Futures { + t.Fatal("unexpected values") + } +} diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index d0b7b7e4..b9771cf1 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -181,8 +181,6 @@ type Filter struct { // Each exchange has their own requirements, so not all fields // are required to be populated type Cancel struct { - Price float64 - Amount float64 Exchange string ID string ClientOrderID string @@ -191,12 +189,8 @@ type Cancel struct { WalletAddress string Type Type Side Side - Status Status AssetType asset.Item - Date time.Time Pair currency.Pair - Symbol string - Trades []TradeHistory } // CancelAllResponse returns the status from attempting to diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index ae0c3406..b5b669b4 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -29,6 +29,7 @@ var ( errUnrecognisedOrderSide = errors.New("unrecognised order side") errUnrecognisedOrderType = errors.New("unrecognised order type") errUnrecognisedOrderStatus = errors.New("unrecognised order status") + errOrderDetailIsNil = errors.New("order detail is nil") ) // Validate checks the supplied data and returns whether or not it's valid @@ -493,6 +494,25 @@ func CopyPointerOrderSlice(old []*Detail) []*Detail { return copySlice } +// DeriveCancel populates a cancel struct by the managed order details +func (d *Detail) DeriveCancel() (*Cancel, error) { + if d == nil { + return nil, errOrderDetailIsNil + } + return &Cancel{ + Exchange: d.Exchange, + ID: d.ID, + AccountID: d.AccountID, + ClientID: d.ClientID, + ClientOrderID: d.ClientOrderID, + WalletAddress: d.WalletAddress, + Type: d.Type, + Side: d.Side, + Pair: d.Pair, + AssetType: d.AssetType, + }, nil +} + // String implements the stringer interface func (t Type) String() string { switch t {