From 4044e90c652e56304be7b0dcacf4f08599584e87 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Fri, 28 Feb 2025 12:12:10 +1100 Subject: [PATCH] shift tif policies to TimeInForce --- cmd/exchange_wrapper_issues/main.go | 2 - .../exchange_wrapper_standards_test.go | 4 +- engine/order_manager.go | 7 +- exchanges/binance/binance_wrapper.go | 4 +- exchanges/btcmarkets/btcmarkets.go | 6 +- exchanges/btcmarkets/btcmarkets_test.go | 12 +-- exchanges/btcmarkets/btcmarkets_wrapper.go | 2 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 4 +- exchanges/deribit/deribit_types.go | 1 - exchanges/deribit/deribit_websocket.go | 2 - exchanges/deribit/deribit_wrapper.go | 21 +++-- exchanges/gateio/gateio_test.go | 22 ++--- exchanges/gateio/gateio_wrapper.go | 29 ++++--- exchanges/huobi/huobi_types.go | 9 ++- exchanges/huobi/huobi_wrapper.go | 37 ++++----- exchanges/kraken/futures_types.go | 10 +-- exchanges/kraken/kraken_futures.go | 17 ++-- exchanges/kraken/kraken_test.go | 2 +- exchanges/kraken/kraken_wrapper.go | 4 +- exchanges/kucoin/kucoin_wrapper.go | 39 ++++----- exchanges/okx/helpers.go | 51 +++++++----- exchanges/okx/okx_test.go | 63 ++++++++------- exchanges/okx/okx_wrapper.go | 47 +++++------ exchanges/order/order_test.go | 81 ++++++++----------- exchanges/order/order_types.go | 31 +++---- exchanges/order/orders.go | 56 ++++++------- 26 files changed, 283 insertions(+), 280 deletions(-) diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go index 6fb7983ba2a..75eb39c73ea 100644 --- a/cmd/exchange_wrapper_issues/main.go +++ b/cmd/exchange_wrapper_issues/main.go @@ -275,8 +275,6 @@ func parseOrderType(orderType string) order.Type { return order.Limit case order.Market.String(): return order.Market - case order.ImmediateOrCancel.String(): - return order.ImmediateOrCancel case order.Stop.String(): return order.Stop case order.TrailingStop.String(): diff --git a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go index 6bd58fadeef..17664f1fe34 100644 --- a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go +++ b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go @@ -453,7 +453,7 @@ func generateMethodArg(ctx context.Context, t *testing.T, argGenerator *MethodAr Amount: 1, ClientID: "1337", ClientOrderID: "13371337", - TimeInForce: order.IOC, + TimeInForce: order.ImmediateOrCancel, Leverage: 1, }) case argGenerator.MethodInputType.AssignableTo(orderModifyParam): @@ -467,7 +467,7 @@ func generateMethodArg(ctx context.Context, t *testing.T, argGenerator *MethodAr Amount: 1, ClientOrderID: "13371337", OrderID: "1337", - TimeInForce: order.IOC, + TimeInForce: order.ImmediateOrCancel, }) case argGenerator.MethodInputType.AssignableTo(orderCancelParam): input = reflect.ValueOf(&order.Cancel{ diff --git a/engine/order_manager.go b/engine/order_manager.go index 9d87e3d25b5..2aa4a3d5eb9 100644 --- a/engine/order_manager.go +++ b/engine/order_manager.go @@ -404,10 +404,9 @@ func (m *OrderManager) Modify(ctx context.Context, mod *order.Modify) (*order.Mo // Populate additional Modify fields as some of them are required by various // exchange implementations. - mod.Pair = det.Pair // Used by Bithumb. - mod.Side = det.Side // Used by Bithumb. - mod.PostOnly = det.PostOnly // Used by Poloniex. - mod.TimeInForce = det.TimeInForce + mod.Pair = det.Pair // Used by Bithumb. + mod.Side = det.Side // Used by Bithumb. + mod.TimeInForce = det.TimeInForce // PostOnly used by Poloniex. // Following is just a precaution to not modify orders by mistake if exchange // implementations do not check fields of the Modify struct for zero values. diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index dcb7793576e..983fbb970cf 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -889,8 +889,8 @@ func (b *Binance) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm timeInForce = "" requestParamsOrderType = BinanceRequestParamsOrderMarket case order.Limit: - if s.TimeInForce == order.IOC { - timeInForce = order.IOC.String() + if s.TimeInForce == order.ImmediateOrCancel { + timeInForce = order.ImmediateOrCancel.String() } requestParamsOrderType = BinanceRequestParamsOrderLimit default: diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 9dbc2830f88..6c5b4174853 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -78,10 +78,6 @@ const ( askSide = "Ask" bidSide = "Bid" - // time in force - immediateOrCancel = "IOC" - fillOrKill = "FOK" - subscribe = "subscribe" fundChange = "fundChange" orderChange = "orderChange" @@ -378,7 +374,7 @@ func (b *BTCMarkets) formatOrderSide(o order.Side) (string, error) { // getTimeInForce returns a string depending on the options in order.Submit func (b *BTCMarkets) getTimeInForce(s *order.Submit) string { switch s.TimeInForce { - case order.IOC, order.FOK: + case order.ImmediateOrCancel, order.FillOrKill: return s.TimeInForce.String() default: return "" // GTC (good till cancelled, default value) diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index 77fbf8e0bb8..e5b9bc7746a 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -966,14 +966,14 @@ func TestGetTimeInForce(t *testing.T) { t.Fatal("unexpected value") } - f = b.getTimeInForce(&order.Submit{TimeInForce: order.IOC}) - if f != immediateOrCancel { - t.Fatalf("received: '%v' but expected: '%v'", f, immediateOrCancel) + f = b.getTimeInForce(&order.Submit{TimeInForce: order.ImmediateOrCancel}) + if f != "IOC" { + t.Fatalf("received: '%v' but expected: '%v'", f, "IOC") } - f = b.getTimeInForce(&order.Submit{TimeInForce: order.FOK}) - if f != fillOrKill { - t.Fatalf("received: '%v' but expected: '%v'", f, fillOrKill) + f = b.getTimeInForce(&order.Submit{TimeInForce: order.FillOrKill}) + if f != "FOK" { + t.Fatalf("received: '%v' but expected: '%v'", f, "FOK") } } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 57ddfa7c0da..6d15bf7086b 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -635,7 +635,7 @@ func (b *BTCMarkets) GetOrderInfo(ctx context.Context, orderID string, _ currenc case stop: resp.Type = order.Stop case takeProfit: - resp.Type = order.ImmediateOrCancel + resp.Type = order.TakeProfit default: resp.Type = order.UnknownType } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 452830963bf..4ba92510bdb 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -430,8 +430,8 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. "") case order.Limit: timeInForce := order.GoodTillCancel.String() - if s.TimeInForce == order.IOC { - timeInForce = order.IOC.String() + if s.TimeInForce == order.ImmediateOrCancel { + timeInForce = order.ImmediateOrCancel.String() } orderID, err = c.PlaceLimitOrder(ctx, "", diff --git a/exchanges/deribit/deribit_types.go b/exchanges/deribit/deribit_types.go index 3d553278533..059b1e1e0c6 100644 --- a/exchanges/deribit/deribit_types.go +++ b/exchanges/deribit/deribit_types.go @@ -44,7 +44,6 @@ var ( errWebsocketConnectionNotAuthenticated = errors.New("websocket connection is not authenticated") errResolutionNotSet = errors.New("resolution not set") errInvalidDestinationID = errors.New("invalid destination id") - errUnsupportedChannel = errors.New("channels not supported") errUnacceptableAPIKey = errors.New("unacceptable api key name") errInvalidUsername = errors.New("new username has to be specified") errSubAccountNameChangeFailed = errors.New("subaccount name change failed") diff --git a/exchanges/deribit/deribit_websocket.go b/exchanges/deribit/deribit_websocket.go index bff18b7db0f..cf3e90a292f 100644 --- a/exchanges/deribit/deribit_websocket.go +++ b/exchanges/deribit/deribit_websocket.go @@ -105,8 +105,6 @@ var defaultSubscriptions = subscription.List{ } var ( - indexENUMS = []string{"ada_usd", "algo_usd", "avax_usd", "bch_usd", "bnb_usd", "btc_usd", "doge_usd", "dot_usd", "eth_usd", "link_usd", "ltc_usd", "luna_usd", "matic_usd", "near_usd", "shib_usd", "sol_usd", "trx_usd", "uni_usd", "usdc_usd", "xrp_usd", "ada_usdc", "bch_usdc", "algo_usdc", "avax_usdc", "btc_usdc", "doge_usdc", "dot_usdc", "bch_usdc", "bnb_usdc", "eth_usdc", "link_usdc", "ltc_usdc", "luna_usdc", "matic_usdc", "near_usdc", "shib_usdc", "sol_usdc", "trx_usdc", "uni_usdc", "xrp_usdc", "btcdvol_usdc", "ethdvol_usdc"} - pingMessage = WsSubscriptionInput{ ID: 2, JSONRPCVersion: rpcVersion, diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index e8fa1d582e6..9ec233ea489 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -597,9 +597,9 @@ func (d *Deribit) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm timeInForce = "good_til_cancelled" case order.GoodTillDay: timeInForce = "good_till_day" - case order.FOK: + case order.FillOrKill: timeInForce = "fill_or_kill" - case order.IOC: + case order.ImmediateOrCancel: timeInForce = "immediate_or_cancel" } var data *PrivateTradeData @@ -794,9 +794,9 @@ func (d *Deribit) GetOrderInfo(ctx context.Context, orderID string, _ currency.P case "good_til_day": tif = order.GoodTillDay case "fill_or_kill": - tif = order.FOK + tif = order.FillOrKill case "immediate_or_cancel": - tif = order.IOC + tif = order.ImmediateOrCancel } return &order.Detail{ AssetType: assetType, @@ -919,10 +919,14 @@ func (d *Deribit) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M if ordersData[y].OrderState != "open" { continue } + + var tif order.TimeInForce + if ordersData[y].PostOnly { // TODO: Set ordersData[y].TimeInForce values + tif = order.PostOnly + } resp = append(resp, order.Detail{ AssetType: getOrdersRequest.AssetType, Exchange: d.Name, - PostOnly: ordersData[y].PostOnly, Price: ordersData[y].Price, Amount: ordersData[y].Amount, ExecutedAmount: ordersData[y].FilledAmount, @@ -934,6 +938,7 @@ func (d *Deribit) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M Side: orderSide, Type: orderType, Status: orderStatus, + TimeInForce: tif, }) } } @@ -988,10 +993,13 @@ func (d *Deribit) GetOrderHistory(ctx context.Context, getOrdersRequest *order.M return resp, fmt.Errorf("%v: orderStatus %s not supported", d.Name, ordersData[y].OrderState) } } + var tif order.TimeInForce + if ordersData[y].PostOnly { // TODO: Set ordersData[y].TimeInForce values + tif = order.PostOnly + } resp = append(resp, order.Detail{ AssetType: getOrdersRequest.AssetType, Exchange: d.Name, - PostOnly: ordersData[y].PostOnly, Price: ordersData[y].Price, Amount: ordersData[y].Amount, ExecutedAmount: ordersData[y].FilledAmount, @@ -1003,6 +1011,7 @@ func (d *Deribit) GetOrderHistory(ctx context.Context, getOrdersRequest *order.M Side: orderSide, Type: orderType, Status: orderStatus, + TimeInForce: tif, }) } } diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index dee849a5b32..75c6c24a13f 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -3370,21 +3370,21 @@ func TestGetClientOrderIDFromText(t *testing.T) { func TestGetTypeFromTimeInForce(t *testing.T) { t.Parallel() - typeResp, postOnly := getTypeFromTimeInForce("gtc") + typeResp, tif := getTypeFromTimeInForce("gtc") assert.Equal(t, order.Limit, typeResp, "should be a limit order") - assert.False(t, postOnly, "should return false") + assert.False(t, tif.Is(order.PostOnly), "should return false") - typeResp, postOnly = getTypeFromTimeInForce("ioc") + typeResp, tif = getTypeFromTimeInForce("ioc") assert.Equal(t, order.Market, typeResp, "should be market order") - assert.False(t, postOnly, "should return false") + assert.False(t, tif.Is(order.PostOnly), "should return false") - typeResp, postOnly = getTypeFromTimeInForce("poc") + typeResp, tif = getTypeFromTimeInForce("poc") assert.Equal(t, order.Limit, typeResp, "should be limit order") - assert.True(t, postOnly, "should return true") + assert.True(t, tif.Is(order.PostOnly), "should return true") - typeResp, postOnly = getTypeFromTimeInForce("fok") + typeResp, tif = getTypeFromTimeInForce("fok") assert.Equal(t, order.Market, typeResp, "should be market order") - assert.False(t, postOnly, "should return false") + assert.False(t, tif.Is(order.PostOnly), "should return false") } func TestGetSideAndAmountFromSize(t *testing.T) { @@ -3417,14 +3417,14 @@ func TestGetFutureOrderSize(t *testing.T) { func TestGetTimeInForce(t *testing.T) { t.Parallel() - _, err := getTimeInForce(&order.Submit{Type: order.Market, TimeInForce: order.PostOnlyGTC}) + _, err := getTimeInForce(&order.Submit{Type: order.Market, TimeInForce: order.PostOnly}) assert.ErrorIs(t, err, order.ErrInvalidTimeInForce) ret, err := getTimeInForce(&order.Submit{Type: order.Market}) require.NoError(t, err) assert.Equal(t, "ioc", ret) - ret, err = getTimeInForce(&order.Submit{Type: order.Limit, TimeInForce: order.PostOnlyGTC}) + ret, err = getTimeInForce(&order.Submit{Type: order.Limit, TimeInForce: order.PostOnly}) require.NoError(t, err) assert.Equal(t, "poc", ret) @@ -3432,7 +3432,7 @@ func TestGetTimeInForce(t *testing.T) { require.NoError(t, err) assert.Equal(t, "gtc", ret) - ret, err = getTimeInForce(&order.Submit{Type: order.Market, TimeInForce: order.FOK}) + ret, err = getTimeInForce(&order.Submit{Type: order.Market, TimeInForce: order.FillOrKill}) require.NoError(t, err) assert.Equal(t, "fok", ret) } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 2b89f743306..e1dc2898278 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -1488,7 +1488,7 @@ func (g *Gateio) GetOrderInfo(ctx context.Context, orderID string, pair currency side, amount, remaining := getSideAndAmountFromSize(fOrder.Size, fOrder.RemainingAmount) - ordertype, postonly := getTypeFromTimeInForce(fOrder.TimeInForce) + ordertype, tif := getTypeFromTimeInForce(fOrder.TimeInForce) return &order.Detail{ Amount: amount, ExecutedAmount: amount - remaining, @@ -1504,7 +1504,7 @@ func (g *Gateio) GetOrderInfo(ctx context.Context, orderID string, pair currency Pair: pair, AssetType: a, Type: ordertype, - PostOnly: postonly, + TimeInForce: tif, Side: side, }, nil case asset.Options: @@ -1719,6 +1719,11 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque continue } + var tif order.TimeInForce + if futuresOrders[x].TimeInForce == "poc" { + tif = order.PostOnly + } + side, amount, remaining := getSideAndAmountFromSize(futuresOrders[x].Size, futuresOrders[x].RemainingAmount) orders = append(orders, order.Detail{ Status: order.Open, @@ -1738,7 +1743,7 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque Type: order.Limit, SettlementCurrency: settlement, ReduceOnly: futuresOrders[x].IsReduceOnly, - PostOnly: futuresOrders[x].TimeInForce == "poc", + TimeInForce: tif, AverageExecutedPrice: futuresOrders[x].FillPrice.Float64(), }) } @@ -2506,16 +2511,18 @@ func getClientOrderIDFromText(text string) string { } // getTypeFromTimeInForce returns the order type and if the order is post only -func getTypeFromTimeInForce(tif string) (orderType order.Type, postOnly bool) { +// TODO: Add in price param to correctly determine if order is market or limit as a zero value price must be a market order +// IOC and POC can be limits if price is supplied. +func getTypeFromTimeInForce(tif string) (orderType order.Type, postOnly order.TimeInForce) { switch tif { case "ioc": - return order.Market, false + return order.Market, order.UnsetTIF case "fok": - return order.Market, false + return order.Market, order.UnsetTIF case "poc": - return order.Limit, true + return order.Limit, order.PostOnly default: - return order.Limit, false + return order.Limit, order.UnsetTIF } } @@ -2544,11 +2551,11 @@ func getFutureOrderSize(s *order.Submit) (float64, error) { func getTimeInForce(s *order.Submit) (string, error) { var timeInForce string switch s.TimeInForce { - case order.IOC: + case order.ImmediateOrCancel: timeInForce = "ioc" // market taker only - case order.FOK: + case order.FillOrKill: timeInForce = "fok" - case order.PostOnlyGTC: + case order.PostOnly: timeInForce = "poc" case order.GoodTillCancel: timeInForce = "gtc" diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index 80a04aaa1c0..d1fa2e3c72c 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -980,10 +980,11 @@ type WsTradeUpdate struct { // OrderVars stores side, status and type for any order/trade type OrderVars struct { - Side order.Side - Status order.Status - OrderType order.Type - Fee float64 + Side order.Side + Status order.Status + OrderType order.Type + TimeInForce order.TimeInForce + Fee float64 } // Variables below are used to check api requests being sent out diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 0b882edb995..58bb667d733 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -1020,10 +1020,10 @@ func (h *HUOBI) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submit oDirection = "SELL" } var oType string - switch s.Type { - case order.Limit: + switch { + case s.Type == order.Limit: oType = "limit" - case order.PostOnly: + case s.TimeInForce == order.PostOnly: oType = "post_only" } offset := "open" @@ -1052,8 +1052,8 @@ func (h *HUOBI) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submit oDirection = "SELL" } var oType string - switch s.Type { - case order.Market: + switch { + case s.Type == order.Market: // https://huobiapi.github.io/docs/dm/v1/en/#order-and-trade // At present, Huobi Futures does not support market price when placing an order. // To increase the probability of a transaction, users can choose to place an order based on BBO price (opponent), @@ -1063,13 +1063,14 @@ func (h *HUOBI) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submit // It is important to note that the above methods will not guarantee the order to be filled in 100%. // The system will obtain the optimal N price at that moment and place the order. oType = "optimal_20" - if s.TimeInForce == order.IOC { + if s.TimeInForce.Is(order.ImmediateOrCancel) { oType = "optimal_20_ioc" } - case order.Limit: + case s.Type == order.Limit: oType = "limit" - case order.PostOnly: - oType = "post_only" + if s.TimeInForce.Is(order.PostOnly) { + oType = "post_only" + } } offset := "open" if s.ReduceOnly { @@ -1357,7 +1358,7 @@ func (h *HUOBI) GetOrderInfo(ctx context.Context, orderID string, pair currency. return nil, err } maker := true - if orderVars.OrderType == order.Limit || orderVars.OrderType == order.PostOnly { + if orderVars.OrderType == order.Limit || orderVars.TimeInForce.Is(order.PostOnly) { maker = false } orderDetail.Trades = append(orderDetail.Trades, order.TradeHistory{ @@ -1395,7 +1396,7 @@ func (h *HUOBI) GetOrderInfo(ctx context.Context, orderID string, pair currency. TID: orderInfo.Data[x].OrderIDString, Type: orderVars.OrderType, Side: orderVars.Side, - IsMaker: orderVars.OrderType == order.Limit || orderVars.OrderType == order.PostOnly, + IsMaker: orderVars.OrderType == order.Limit || orderVars.TimeInForce.Is(order.PostOnly), }) } default: @@ -1528,9 +1529,8 @@ func (h *HUOBI) GetActiveOrders(ctx context.Context, req *order.MultiOrderReques return orders, err } - var orderVars OrderVars for x := range openOrders.Data.Orders { - orderVars, err = compatibleVars(openOrders.Data.Orders[x].Direction, + orderVars, err := compatibleVars(openOrders.Data.Orders[x].Direction, openOrders.Data.Orders[x].OrderPriceType, openOrders.Data.Orders[x].Status) if err != nil { @@ -1541,7 +1541,7 @@ func (h *HUOBI) GetActiveOrders(ctx context.Context, req *order.MultiOrderReques return orders, err } orders = append(orders, order.Detail{ - PostOnly: orderVars.OrderType == order.PostOnly, + TimeInForce: orderVars.TimeInForce, Leverage: openOrders.Data.Orders[x].LeverageRate, Price: openOrders.Data.Orders[x].Price, Amount: openOrders.Data.Orders[x].Volume, @@ -1583,7 +1583,7 @@ func (h *HUOBI) GetActiveOrders(ctx context.Context, req *order.MultiOrderReques return orders, err } orders = append(orders, order.Detail{ - PostOnly: orderVars.OrderType == order.PostOnly, + TimeInForce: orderVars.TimeInForce, Leverage: openOrders.Data.Orders[x].LeverageRate, Price: openOrders.Data.Orders[x].Price, Amount: openOrders.Data.Orders[x].Volume, @@ -1684,7 +1684,7 @@ func (h *HUOBI) GetOrderHistory(ctx context.Context, req *order.MultiOrderReques return orders, err } orders = append(orders, order.Detail{ - PostOnly: orderVars.OrderType == order.PostOnly, + TimeInForce: orderVars.TimeInForce, Leverage: orderHistory.Data.Orders[x].LeverageRate, Price: orderHistory.Data.Orders[x].Price, Amount: orderHistory.Data.Orders[x].Volume, @@ -1742,7 +1742,7 @@ func (h *HUOBI) GetOrderHistory(ctx context.Context, req *order.MultiOrderReques return orders, err } orders = append(orders, order.Detail{ - PostOnly: orderVars.OrderType == order.PostOnly, + TimeInForce: orderVars.TimeInForce, Leverage: openOrders.Data.Orders[x].LeverageRate, Price: openOrders.Data.Orders[x].Price, Amount: openOrders.Data.Orders[x].Volume, @@ -1985,7 +1985,8 @@ func compatibleVars(side, orderPriceType string, status int64) (OrderVars, error case "opponent": resp.OrderType = order.Market case "post_only": - resp.OrderType = order.PostOnly + resp.OrderType = order.Limit + resp.TimeInForce = order.PostOnly default: return resp, errors.New("invalid orderPriceType") } diff --git a/exchanges/kraken/futures_types.go b/exchanges/kraken/futures_types.go index b840320acd7..cd3a48b0ccc 100644 --- a/exchanges/kraken/futures_types.go +++ b/exchanges/kraken/futures_types.go @@ -8,12 +8,10 @@ import ( var ( validOrderTypes = map[order.Type]string{ - order.ImmediateOrCancel: "ioc", - order.Limit: "lmt", - order.Stop: "stp", - order.PostOnly: "post", - order.TakeProfit: "take_profit", - order.Market: "mkt", + order.Limit: "lmt", + order.Stop: "stp", + order.TakeProfit: "take_profit", + order.Market: "mkt", } validSide = []string{"buy", "sell"} diff --git a/exchanges/kraken/kraken_futures.go b/exchanges/kraken/kraken_futures.go index a58d73ceb62..5ad6a2465f3 100644 --- a/exchanges/kraken/kraken_futures.go +++ b/exchanges/kraken/kraken_futures.go @@ -156,19 +156,22 @@ func (k *Kraken) FuturesEditOrder(ctx context.Context, orderID, clientOrderID st } // FuturesSendOrder sends a futures order -func (k *Kraken) FuturesSendOrder(ctx context.Context, orderType order.Type, symbol currency.Pair, side, triggerSignal, clientOrderID, reduceOnly string, - ioc bool, - size, limitPrice, stopPrice float64) (FuturesSendOrderData, error) { +func (k *Kraken) FuturesSendOrder(ctx context.Context, orderType order.Type, symbol currency.Pair, side, triggerSignal, clientOrderID, reduceOnly string, tif order.TimeInForce, size, limitPrice, stopPrice float64) (FuturesSendOrderData, error) { var resp FuturesSendOrderData - if ioc && orderType != order.Market { - orderType = order.ImmediateOrCancel - } - oType, ok := validOrderTypes[orderType] if !ok { return resp, errors.New("invalid orderType") } + + if oType != "mkt" { + if tif.Is(order.PostOnly) { + oType = "post" + } else if tif.Is(order.ImmediateOrCancel) { + oType = "ioc" + } + } + params := url.Values{} params.Set("orderType", oType) symbolValue, err := k.FormatSymbol(symbol, asset.Futures) diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 234ac536347..25058637225 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -181,7 +181,7 @@ func TestFuturesSendOrder(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders) - _, err := k.FuturesSendOrder(context.Background(), order.Limit, futuresTestPair, "buy", "", "", "", true, 1, 1, 0.9) + _, err := k.FuturesSendOrder(context.Background(), order.Limit, futuresTestPair, "buy", "", "", "", order.ImmediateOrCancel, 1, 1, 0.9) assert.NoError(t, err, "FuturesSendOrder should not error") } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 3a1c31624a2..57689d5825e 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -717,7 +717,7 @@ func (k *Kraken) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi switch s.AssetType { case asset.Spot: timeInForce := order.GoodTillCancel.String() - if s.TimeInForce == order.IOC { + if s.TimeInForce.Is(order.ImmediateOrCancel) { timeInForce = s.TimeInForce.String() } if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() { @@ -764,7 +764,7 @@ func (k *Kraken) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi "", s.ClientOrderID, "", - s.TimeInForce == order.IOC, + s.TimeInForce, s.Amount, s.Price, 0, diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index f4a19169311..ed5fc4ae105 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -692,10 +692,10 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm var timeInForce string if oType == order.Limit { switch { - case s.TimeInForce == order.FOK: - timeInForce = order.FOK.String() - case s.TimeInForce == order.IOC: - timeInForce = order.IOC.String() + case s.TimeInForce == order.FillOrKill: + timeInForce = order.FillOrKill.String() + case s.TimeInForce == order.ImmediateOrCancel: + timeInForce = order.ImmediateOrCancel.String() case s.PostOnly: default: timeInForce = order.GoodTillCancel.String() @@ -1033,8 +1033,7 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc Price: orderDetail.Price, Date: orderDetail.CreatedAt.Time(), HiddenOrder: orderDetail.Hidden, - TimeInForce: StringToTimeInForce(orderDetail.TimeInForce), - PostOnly: orderDetail.PostOnly, + TimeInForce: StringToTimeInForce(orderDetail.TimeInForce, orderDetail.PostOnly), ReduceOnly: orderDetail.ReduceOnly, Leverage: orderDetail.Leverage, AverageExecutedPrice: orderDetail.Price, @@ -1105,8 +1104,7 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc Price: orderDetail.Price.Float64(), Date: orderDetail.CreatedAt.Time(), HiddenOrder: orderDetail.Hidden, - TimeInForce: StringToTimeInForce(orderDetail.TimeInForce), - PostOnly: orderDetail.PostOnly, + TimeInForce: StringToTimeInForce(orderDetail.TimeInForce, orderDetail.PostOnly), AverageExecutedPrice: orderDetail.Price.Float64(), FeeAsset: currency.NewCode(orderDetail.FeeCurrency), ClientOrderID: orderDetail.ClientOID, @@ -1278,8 +1276,7 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M Side: side, Type: oType, Pair: dPair, - TimeInForce: StringToTimeInForce(futuresOrders.Items[x].TimeInForce), - PostOnly: futuresOrders.Items[x].PostOnly, + TimeInForce: StringToTimeInForce(futuresOrders.Items[x].TimeInForce, futuresOrders.Items[x].PostOnly), ReduceOnly: futuresOrders.Items[x].ReduceOnly, Status: status, SettlementCurrency: currency.NewCode(futuresOrders.Items[x].SettleCurrency), @@ -1375,8 +1372,7 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M Side: side, Type: order.Stop, Pair: dPair, - TimeInForce: StringToTimeInForce(response.Items[a].TimeInForce), - PostOnly: response.Items[a].PostOnly, + TimeInForce: StringToTimeInForce(response.Items[a].TimeInForce, response.Items[a].PostOnly), Status: status, AssetType: getOrdersRequest.AssetType, HiddenOrder: response.Items[a].Hidden, @@ -1614,8 +1610,7 @@ func (ku *Kucoin) GetOrderHistory(ctx context.Context, getOrdersRequest *order.M Side: side, Type: order.Stop, Pair: dPair, - TimeInForce: StringToTimeInForce(response.Items[a].TimeInForce), - PostOnly: response.Items[a].PostOnly, + TimeInForce: StringToTimeInForce(response.Items[a].TimeInForce, response.Items[a].PostOnly), Status: status, AssetType: getOrdersRequest.AssetType, HiddenOrder: response.Items[a].Hidden, @@ -2462,14 +2457,20 @@ func (ku *Kucoin) GetCurrencyTradeURL(_ context.Context, a asset.Item, cp curren } // StringToTimeInForce returns an order.TimeInForder instance from string -func StringToTimeInForce(tif string) order.TimeInForce { +func StringToTimeInForce(tif string, postOnly bool) order.TimeInForce { + var out order.TimeInForce switch tif { case "GTT": - return order.GoodTillTime + out = order.GoodTillTime case "IOC": - return order.IOC + out = order.ImmediateOrCancel case "FOK": - return order.FOK + out = order.FillOrKill + default: + out = order.GoodTillCancel + } + if postOnly { + out |= order.PostOnly } - return order.GoodTillCancel + return out } diff --git a/exchanges/okx/helpers.go b/exchanges/okx/helpers.go index 6f79a4ae5f8..3ff912062ed 100644 --- a/exchanges/okx/helpers.go +++ b/exchanges/okx/helpers.go @@ -12,45 +12,56 @@ import ( ) // orderTypeFromString returns order.Type instance from string -func orderTypeFromString(orderType string) (order.Type, error) { +func orderTypeFromString(orderType string) (order.Type, order.TimeInForce, error) { orderType = strings.ToLower(orderType) switch orderType { case orderMarket: - return order.Market, nil + return order.Market, order.UnsetTIF, nil case orderLimit: - return order.Limit, nil + return order.Limit, order.UnsetTIF, nil case orderPostOnly: - return order.PostOnly, nil + return order.Limit, order.PostOnly, nil case orderFOK: - return order.FillOrKill, nil + return order.Limit, order.FillOrKill, nil case orderIOC: - return order.ImmediateOrCancel, nil + return order.Limit, order.ImmediateOrCancel, nil case orderOptimalLimitIOC: - return order.OptimalLimitIOC, nil + return order.OptimalLimitIOC, order.ImmediateOrCancel, nil case "mmp": - return order.MarketMakerProtection, nil + return order.MarketMakerProtection, order.UnsetTIF, nil case "mmp_and_post_only": - return order.MarketMakerProtectionAndPostOnly, nil + return order.MarketMakerProtectionAndPostOnly, order.PostOnly, nil case "twap": - return order.TWAP, nil + return order.TWAP, order.UnsetTIF, nil case "move_order_stop": - return order.TrailingStop, nil + return order.TrailingStop, 0, nil case "chase": - return order.Chase, nil + return order.Chase, order.UnsetTIF, nil default: - return order.UnknownType, fmt.Errorf("%w %v", order.ErrTypeIsInvalid, orderType) + return order.UnknownType, order.UnsetTIF, fmt.Errorf("%w %v", order.ErrTypeIsInvalid, orderType) } } // orderTypeString returns a string representation of order.Type instance -func orderTypeString(orderType order.Type) (string, error) { - switch orderType { +func orderTypeString(orderType order.Type, tif order.TimeInForce) (string, error) { + switch tif { + case order.PostOnly: + return orderPostOnly, nil + case order.FillOrKill: + return orderFOK, nil case order.ImmediateOrCancel: - return "ioc", nil - case order.Market, order.Limit, order.Trigger, - order.PostOnly, order.FillOrKill, order.OptimalLimitIOC, - order.MarketMakerProtection, order.MarketMakerProtectionAndPostOnly, - order.Chase, order.TWAP, order.OCO: + return orderIOC, nil + } + + switch orderType { + case order.Market, order.Limit, + order.Trigger, + order.OptimalLimitIOC, + order.MarketMakerProtection, + order.MarketMakerProtectionAndPostOnly, + order.Chase, + order.TWAP, + order.OCO: return orderType.Lower(), nil case order.ConditionalStop: return "conditional", nil diff --git a/exchanges/okx/okx_test.go b/exchanges/okx/okx_test.go index e3494a29c0a..4db1a07faee 100644 --- a/exchanges/okx/okx_test.go +++ b/exchanges/okx/okx_test.go @@ -6285,28 +6285,34 @@ func TestGetAccountInstruments(t *testing.T) { func TestOrderTypeString(t *testing.T) { t.Parallel() - var orderTypesToStringMap = map[order.Type]struct { + + type OrderTypeWithTIF struct { + OrderType order.Type + TIF order.TimeInForce + } + + var orderTypesToStringMap = map[OrderTypeWithTIF]struct { Expected string Error error }{ - order.Market: {Expected: orderMarket}, - order.Limit: {Expected: orderLimit}, - order.PostOnly: {Expected: orderPostOnly}, - order.FillOrKill: {Expected: orderFOK}, - order.ImmediateOrCancel: {Expected: orderIOC}, - order.OptimalLimitIOC: {Expected: orderOptimalLimitIOC}, - order.MarketMakerProtection: {Expected: "mmp"}, - order.MarketMakerProtectionAndPostOnly: {Expected: "mmp_and_post_only"}, - order.Liquidation: {Error: order.ErrUnsupportedOrderType}, - order.OCO: {Expected: "oco"}, - order.TrailingStop: {Expected: "move_order_stop"}, - order.Chase: {Expected: "chase"}, - order.TWAP: {Expected: "twap"}, - order.ConditionalStop: {Expected: "conditional"}, - order.Trigger: {Expected: "trigger"}, + {OrderType: order.Market, TIF: order.UnsetTIF}: {Expected: orderMarket}, + {OrderType: order.Limit, TIF: order.UnsetTIF}: {Expected: orderLimit}, + {OrderType: order.Limit, TIF: order.PostOnly}: {Expected: orderPostOnly}, + {OrderType: order.Limit, TIF: order.FillOrKill}: {Expected: orderFOK}, + {OrderType: order.Limit, TIF: order.ImmediateOrCancel}: {Expected: orderIOC}, + {OrderType: order.OptimalLimitIOC, TIF: order.UnsetTIF}: {Expected: orderOptimalLimitIOC}, + {OrderType: order.MarketMakerProtection, TIF: order.UnsetTIF}: {Expected: "mmp"}, + {OrderType: order.MarketMakerProtectionAndPostOnly, TIF: order.UnsetTIF}: {Expected: "mmp_and_post_only"}, + {OrderType: order.Liquidation, TIF: order.UnsetTIF}: {Error: order.ErrUnsupportedOrderType}, + {OrderType: order.OCO, TIF: order.UnsetTIF}: {Expected: "oco"}, + {OrderType: order.TrailingStop, TIF: order.UnsetTIF}: {Expected: "move_order_stop"}, + {OrderType: order.Chase, TIF: order.UnsetTIF}: {Expected: "chase"}, + {OrderType: order.TWAP, TIF: order.UnsetTIF}: {Expected: "twap"}, + {OrderType: order.ConditionalStop, TIF: order.UnsetTIF}: {Expected: "conditional"}, + {OrderType: order.Trigger, TIF: order.UnsetTIF}: {Expected: "trigger"}, } - for oType, val := range orderTypesToStringMap { - orderTypeString, err := orderTypeString(oType) + for tc, val := range orderTypesToStringMap { + orderTypeString, err := orderTypeString(tc.OrderType, tc.TIF) require.ErrorIs(t, err, val.Error) assert.Equal(t, val.Expected, orderTypeString) } @@ -6571,29 +6577,32 @@ func TestWsProcessSpreadTradesJSON(t *testing.T) { func TestOrderTypeFromString(t *testing.T) { t.Parallel() + orderTypeStrings := map[string]struct { OType order.Type + TIF order.TimeInForce Error error }{ "market": {OType: order.Market}, "LIMIT": {OType: order.Limit}, "limit": {OType: order.Limit}, - "post_only": {OType: order.PostOnly}, - "fok": {OType: order.FillOrKill}, - "ioc": {OType: order.ImmediateOrCancel}, - "optimal_limit_ioc": {OType: order.OptimalLimitIOC}, + "post_only": {OType: order.Limit, TIF: order.PostOnly}, + "fok": {OType: order.Limit, TIF: order.FillOrKill}, + "ioc": {OType: order.Limit, TIF: order.ImmediateOrCancel}, + "optimal_limit_ioc": {OType: order.OptimalLimitIOC, TIF: order.ImmediateOrCancel}, "mmp": {OType: order.MarketMakerProtection}, - "mmp_and_post_only": {OType: order.MarketMakerProtectionAndPostOnly}, + "mmp_and_post_only": {OType: order.MarketMakerProtectionAndPostOnly, TIF: order.PostOnly}, "trigger": {OType: order.UnknownType, Error: order.ErrTypeIsInvalid}, "chase": {OType: order.Chase}, "move_order_stop": {OType: order.TrailingStop}, "twap": {OType: order.TWAP}, "abcd": {OType: order.UnknownType, Error: order.ErrTypeIsInvalid}, } - for a := range orderTypeStrings { - oType, err := orderTypeFromString(a) - assert.ErrorIs(t, err, orderTypeStrings[a].Error) - assert.Equal(t, oType, orderTypeStrings[a].OType) + for s, exp := range orderTypeStrings { + oType, tif, err := orderTypeFromString(s) + require.ErrorIs(t, err, exp.Error) + assert.Equal(t, exp.OType, oType) + assert.Equal(t, exp.TIF.String(), tif.String(), s) } } diff --git a/exchanges/okx/okx_wrapper.go b/exchanges/okx/okx_wrapper.go index fe3a3a69249..65de31b3630 100644 --- a/exchanges/okx/okx_wrapper.go +++ b/exchanges/okx/okx_wrapper.go @@ -916,7 +916,7 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR } return s.DeriveSubmitResponse(placeSpreadOrderResponse.OrderID) } - orderTypeString, err := orderTypeString(s.Type) + orderTypeString, err := orderTypeString(s.Type, s.TimeInForce) if err != nil { return nil, err } @@ -1149,8 +1149,7 @@ func (ok *Okx) ModifyOrder(ctx context.Context, action *order.Modify) (*order.Mo return nil, currency.ErrCurrencyPairEmpty } switch action.Type { - case order.UnknownType, order.Market, order.Limit, order.PostOnly, order.FillOrKill, order.ImmediateOrCancel, - order.OptimalLimitIOC, order.MarketMakerProtection, order.MarketMakerProtectionAndPostOnly: + case order.UnknownType, order.Market, order.Limit, order.OptimalLimitIOC, order.MarketMakerProtection, order.MarketMakerProtectionAndPostOnly: amendRequest := AmendOrderRequestParams{ InstrumentID: pairFormat.Format(action.Pair), NewQuantity: action.Amount, @@ -1254,8 +1253,7 @@ func (ok *Okx) CancelOrder(ctx context.Context, ord *order.Cancel) error { } instrumentID := pairFormat.Format(ord.Pair) switch ord.Type { - case order.UnknownType, order.Market, order.Limit, order.PostOnly, order.FillOrKill, order.ImmediateOrCancel, - order.OptimalLimitIOC, order.MarketMakerProtection, order.MarketMakerProtectionAndPostOnly: + case order.UnknownType, order.Market, order.Limit, order.OptimalLimitIOC, order.MarketMakerProtection, order.MarketMakerProtectionAndPostOnly: req := CancelOrderRequestParam{ InstrumentID: instrumentID, OrderID: ord.OrderID, @@ -1312,8 +1310,7 @@ func (ok *Okx) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order. return nil, currency.ErrCurrencyPairsEmpty } switch ord.Type { - case order.UnknownType, order.Market, order.Limit, order.PostOnly, order.FillOrKill, order.ImmediateOrCancel, - order.OptimalLimitIOC, order.MarketMakerProtection, order.MarketMakerProtectionAndPostOnly: + case order.UnknownType, order.Market, order.Limit, order.OptimalLimitIOC, order.MarketMakerProtection, order.MarketMakerProtectionAndPostOnly: if o[x].ClientID == "" && o[x].OrderID == "" { return nil, fmt.Errorf("%w, order ID required for order of type %v", order.ErrOrderIDNotSet, o[x].Type) } @@ -1406,7 +1403,7 @@ func (ok *Okx) CancelAllOrders(ctx context.Context, orderCancellation *order.Can } var oType string if orderCancellation.Type != order.UnknownType && orderCancellation.Type != order.AnyType { - oType, err = orderTypeString(orderCancellation.Type) + oType, err = orderTypeString(orderCancellation.Type, orderCancellation.TimeInForce) if err != nil { return order.CancelAllResponse{}, err } @@ -1562,7 +1559,7 @@ func (ok *Okx) GetOrderInfo(ctx context.Context, orderID string, pair currency.P if err != nil { return nil, err } - orderType, err := orderTypeFromString(orderDetail.OrderType) + orderType, tif, err := orderTypeFromString(orderDetail.OrderType) if err != nil { return nil, err } @@ -1582,6 +1579,7 @@ func (ok *Okx) GetOrderInfo(ctx context.Context, orderID string, pair currency.P ExecutedAmount: orderDetail.RebateAmount.Float64(), Date: orderDetail.CreationTime.Time(), LastUpdated: orderDetail.UpdateTime.Time(), + TimeInForce: tif, }, nil } @@ -1727,7 +1725,7 @@ func (ok *Okx) GetActiveOrders(ctx context.Context, req *order.MultiOrderRequest instrumentType := GetInstrumentTypeFromAssetItem(req.AssetType) var orderType string if req.Type != order.UnknownType && req.Type != order.AnyType { - orderType, err = orderTypeString(req.Type) + orderType, err = orderTypeString(req.Type, req.TimeInForce) if err != nil { return nil, err } @@ -1771,13 +1769,11 @@ allOrders: continue } } - var orderStatus order.Status - orderStatus, err = order.StringToOrderStatus(strings.ToUpper(orderList[i].State)) + orderStatus, err := order.StringToOrderStatus(strings.ToUpper(orderList[i].State)) if err != nil { return nil, err } - var oType order.Type - oType, err = orderTypeFromString(orderList[i].OrderType) + oType, tif, err := orderTypeFromString(orderList[i].OrderType) if err != nil { return nil, err } @@ -1798,6 +1794,7 @@ allOrders: AssetType: req.AssetType, Date: orderList[i].CreationTime.Time(), LastUpdated: orderList[i].UpdateTime.Time(), + TimeInForce: tif, }) } if len(orderList) < 100 { @@ -1825,7 +1822,7 @@ func (ok *Okx) GetOrderHistory(ctx context.Context, req *order.MultiOrderRequest var resp []order.Detail // For Spread orders. if req.AssetType == asset.Spread { - oType, err := orderTypeString(req.Type) + oType, err := orderTypeString(req.Type, req.TimeInForce) if err != nil { return nil, err } @@ -1911,17 +1908,14 @@ allOrders: if !req.Pairs[j].Equal(pair) { continue } - var orderStatus order.Status - orderStatus, err = order.StringToOrderStatus(strings.ToUpper(orderList[i].State)) + orderStatus, err := order.StringToOrderStatus(strings.ToUpper(orderList[i].State)) if err != nil { log.Errorf(log.ExchangeSys, "%s %v", ok.Name, err) } if orderStatus == order.Active { continue } - orderSide := orderList[i].Side - var oType order.Type - oType, err = orderTypeFromString(orderList[i].OrderType) + oType, tif, err := orderTypeFromString(orderList[i].OrderType) if err != nil { return nil, err } @@ -1947,7 +1941,7 @@ allOrders: OrderID: orderList[i].OrderID, ClientOrderID: orderList[i].ClientOrderID, Type: oType, - Side: orderSide, + Side: orderList[i].Side, Status: orderStatus, AssetType: req.AssetType, Date: orderList[i].CreationTime.Time(), @@ -1955,6 +1949,7 @@ allOrders: Pair: pair, Cost: orderList[i].AveragePrice.Float64() * orderList[i].AccumulatedFillSize.Float64(), CostAsset: currency.NewCode(orderList[i].RebateCurrency), + TimeInForce: tif, }) } } @@ -2622,14 +2617,11 @@ func (ok *Okx) GetFuturesPositionOrders(ctx context.Context, req *futures.Positi if req.Pairs[i].String() != positions[j].InstrumentID { continue } - var orderStatus order.Status - orderStatus, err = order.StringToOrderStatus(strings.ToUpper(positions[j].State)) + orderStatus, err := order.StringToOrderStatus(strings.ToUpper(positions[j].State)) if err != nil { log.Errorf(log.ExchangeSys, "%s %v", ok.Name, err) } - orderSide := positions[j].Side - var oType order.Type - oType, err = orderTypeFromString(positions[j].OrderType) + oType, tif, err := orderTypeFromString(positions[j].OrderType) if err != nil { return nil, err } @@ -2660,7 +2652,7 @@ func (ok *Okx) GetFuturesPositionOrders(ctx context.Context, req *futures.Positi OrderID: positions[j].OrderID, ClientOrderID: positions[j].ClientOrderID, Type: oType, - Side: orderSide, + Side: positions[j].Side, Status: orderStatus, AssetType: req.Asset, Date: positions[j].CreationTime.Time(), @@ -2668,6 +2660,7 @@ func (ok *Okx) GetFuturesPositionOrders(ctx context.Context, req *futures.Positi Pair: req.Pairs[i], Cost: cost, CostAsset: currency.NewCode(positions[j].RebateCurrency), + TimeInForce: tif, }) } } diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index afd13abbc65..c58709cde91 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -373,9 +373,6 @@ func TestFilterOrdersByType(t *testing.T) { t.Parallel() var orders = []Detail{ - { - Type: ImmediateOrCancel, - }, { Type: Limit, }, @@ -698,8 +695,6 @@ func TestSortOrdersByOrderType(t *testing.T) { Type: Market, }, { Type: Limit, - }, { - Type: ImmediateOrCancel, }, { Type: TrailingStop, }, @@ -769,10 +764,6 @@ func TestStringToOrderType(t *testing.T) { {"market", Market, nil}, {"MARKET", Market, nil}, {"mArKeT", Market, nil}, - {"immediate_or_cancel", ImmediateOrCancel, nil}, - {"IMMEDIATE_OR_CANCEL", ImmediateOrCancel, nil}, - {"iMmEdIaTe_Or_CaNcEl", ImmediateOrCancel, nil}, - {"iMmEdIaTe Or CaNcEl", ImmediateOrCancel, nil}, {"stop", Stop, nil}, {"STOP", Stop, nil}, {"sToP", Stop, nil}, @@ -782,10 +773,7 @@ func TestStringToOrderType(t *testing.T) { {"TRAILING_STOP", TrailingStop, nil}, {"tRaIlInG_sToP", TrailingStop, nil}, {"tRaIlInG sToP", TrailingStop, nil}, - {"fOk", FillOrKill, nil}, - {"exchange fOk", FillOrKill, nil}, {"ios", IOS, nil}, - {"post_ONly", PostOnly, nil}, {"any", AnyType, nil}, {"ANY", AnyType, nil}, {"aNy", AnyType, nil}, @@ -912,8 +900,7 @@ func TestUpdateOrderFromModifyResponse(t *testing.T) { require.NoError(t, err) om := ModifyResponse{ - TimeInForce: IOC, - PostOnly: true, + TimeInForce: PostOnly | GoodTillTime, Price: 1, Amount: 1, TriggerPrice: 1, @@ -929,7 +916,8 @@ func TestUpdateOrderFromModifyResponse(t *testing.T) { od.UpdateOrderFromModifyResponse(&om) assert.NotEqual(t, UnknownTIF, od.TimeInForce) - assert.True(t, od.PostOnly) + assert.True(t, od.TimeInForce.Is(GoodTillTime)) + assert.True(t, od.TimeInForce.Is(PostOnly)) assert.Equal(t, 1., od.Price) assert.Equal(t, 1., od.Amount) assert.Equal(t, 1., od.TriggerPrice) @@ -961,9 +949,8 @@ func TestUpdateOrderFromDetail(t *testing.T) { require.ErrorIs(t, err, ErrOrderDetailIsNil) om := &Detail{ - TimeInForce: GoodTillCancel, + TimeInForce: GoodTillCancel | PostOnly, HiddenOrder: true, - PostOnly: true, Leverage: 1, Price: 1, Amount: 1, @@ -999,9 +986,9 @@ func TestUpdateOrderFromDetail(t *testing.T) { require.NoError(t, err) assert.Equal(t, od.InternalOrderID, id) - assert.Equal(t, GoodTillCancel, od.TimeInForce) + assert.True(t, od.TimeInForce.Is(GoodTillCancel)) + assert.True(t, od.TimeInForce.Is(PostOnly)) require.True(t, od.HiddenOrder) - assert.True(t, od.PostOnly) assert.Equal(t, 1., od.Leverage) assert.Equal(t, 1., od.Price) assert.Equal(t, 1., od.Amount) @@ -1696,15 +1683,15 @@ func TestSideUnmarshal(t *testing.T) { func TestIsValid(t *testing.T) { t.Parallel() var timeInForceValidityMap = map[TimeInForce]bool{ - TimeInForce(1): false, - IOC: true, - GoodTillTime: true, - GoodTillCancel: true, - GoodTillDay: true, - FOK: true, - PostOnlyGTC: true, - UnsetTIF: true, - UnknownTIF: false, + TimeInForce(1): false, + ImmediateOrCancel: true, + GoodTillTime: true, + GoodTillCancel: true, + GoodTillDay: true, + FillOrKill: true, + PostOnly: true, + UnsetTIF: true, + UnknownTIF: false, } var tif TimeInForce for tif = range timeInForceValidityMap { @@ -1724,22 +1711,22 @@ func TestStringToTimeInForce(t *testing.T) { "GOOD_TILL_CANCELED": {TIF: GoodTillCancel}, "GTT": {TIF: GoodTillTime}, "GOOD_TIL_TIME": {TIF: GoodTillTime}, - "FILLORKILL": {TIF: FOK}, - "POST_ONLY_GOOD_TIL_CANCELLED": {TIF: PostOnlyGTC}, - "immedIate_Or_Cancel": {TIF: IOC}, + "FILLORKILL": {TIF: FillOrKill}, + "POST_ONLY_GOOD_TIL_CANCELLED": {TIF: PostOnly}, + "immedIate_Or_Cancel": {TIF: ImmediateOrCancel}, "": {TIF: UnsetTIF}, - "IOC": {TIF: IOC}, - "immediate_or_cancel": {TIF: IOC}, - "IMMEDIATE_OR_CANCEL": {TIF: IOC}, - "IMMEDIATEORCANCEL": {TIF: IOC}, + "IOC": {TIF: ImmediateOrCancel}, + "immediate_or_cancel": {TIF: ImmediateOrCancel}, + "IMMEDIATE_OR_CANCEL": {TIF: ImmediateOrCancel}, + "IMMEDIATEORCANCEL": {TIF: ImmediateOrCancel}, "GOOD_TILL_CANCELLED": {TIF: GoodTillCancel}, "good_till_day": {TIF: GoodTillDay}, "GOOD_TILL_DAY": {TIF: GoodTillDay}, "GTD": {TIF: GoodTillDay}, "GOODtillday": {TIF: GoodTillDay}, "abcdfeg": {TIF: UnknownTIF, Error: ErrInvalidTimeInForce}, - "PoC": {TIF: IOC}, - "PendingORCANCEL": {TIF: IOC}, + "PoC": {TIF: PostOnly}, + "PendingORCANCEL": {TIF: PostOnly}, } for tk := range timeInForceStringToValueMap { @@ -1752,14 +1739,14 @@ func TestStringToTimeInForce(t *testing.T) { func TestString(t *testing.T) { t.Parallel() valMap := map[TimeInForce]string{ - IOC: "IOC", - GoodTillCancel: "GTC", - GoodTillTime: "GTT", - GoodTillDay: "GTD", - FOK: "FOK", - PostOnlyGTC: "POST_ONLY_GOOD_TIL_CANCELLED", - UnknownTIF: "UNKNOWN", - UnsetTIF: "", + ImmediateOrCancel: "IOC", + GoodTillCancel: "GTC", + GoodTillTime: "GTT", + GoodTillDay: "GTD", + FillOrKill: "FOK", + PostOnly: "POST_ONLY_GOOD_TIL_CANCELLED", + UnknownTIF: "UNKNOWN", + UnsetTIF: "", } for x := range valMap { result := x.String() @@ -1769,8 +1756,8 @@ func TestString(t *testing.T) { func TestIsIOC(t *testing.T) { t.Parallel() - require.True(t, IOC.IsIOC()) - require.False(t, FOK.IsIOC()) + require.True(t, ImmediateOrCancel.IsIOC()) + require.False(t, FillOrKill.IsIOC()) require.False(t, TimeInForce(0).IsIOC()) } diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index df5d4b2632c..7aaf9d09699 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -203,7 +203,6 @@ type ModifyResponse struct { type Detail struct { HiddenOrder bool TimeInForce TimeInForce - PostOnly bool ReduceOnly bool Leverage float64 Price float64 @@ -273,6 +272,7 @@ type Cancel struct { AssetType asset.Item Pair currency.Pair MarginType margin.Type + TimeInForce TimeInForce } // CancelAllResponse returns the status from attempting to @@ -308,12 +308,13 @@ type TradeHistory struct { type MultiOrderRequest struct { // Currencies Empty array = all currencies. Some endpoints only support // singular currency enquiries - Pairs currency.Pairs - AssetType asset.Item - Type Type - Side Side - StartTime time.Time - EndTime time.Time + Pairs currency.Pairs + AssetType asset.Item + Type Type + Side Side + TimeInForce TimeInForce + StartTime time.Time + EndTime time.Time // FromOrderID for some APIs require order history searching // from a specific orderID rather than via timestamps FromOrderID string @@ -358,15 +359,12 @@ const ( UnknownType Type = 0 Limit Type = 1 << iota Market - PostOnly - ImmediateOrCancel Stop StopLimit StopMarket TakeProfit TakeProfitMarket TrailingStop - FillOrKill IOS AnyType Liquidation @@ -409,18 +407,23 @@ const ( // TimeInForce enforces a standard for time-in-force values across the code base. type TimeInForce uint16 +// Is checks to see if the enum contains the flag +func (t TimeInForce) Is(in TimeInForce) bool { + return in != 0 && t&in == in +} + // TimeInForce types const ( UnsetTIF TimeInForce = 0 GoodTillCancel TimeInForce = 1 << iota GoodTillDay GoodTillTime - FOK // FOK represents FillOrKill - IOC // IOC represents ImmediateOrCancel - PostOnlyGTC // PostOnlyGCT represents PostOnlyGoodTilCancelled + FillOrKill // FOK represents FillOrKill + ImmediateOrCancel // IOC represents ImmediateOrCancel + PostOnly // PostOnlyGCT represents PostOnlyGoodTilCancelled UnknownTIF - supportedTimeInForceFlag = GoodTillCancel | GoodTillDay | GoodTillTime | FOK | IOC | PostOnlyGTC + supportedTimeInForceFlag = GoodTillCancel | GoodTillDay | GoodTillTime | FillOrKill | ImmediateOrCancel | PostOnly ) // ByPrice used for sorting orders by price diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index ad1d25295aa..b674cdac571 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -202,10 +202,6 @@ func (d *Detail) UpdateOrderFromDetail(m *Detail) error { d.AccountID = m.AccountID updated = true } - if m.PostOnly != d.PostOnly { - d.PostOnly = m.PostOnly - updated = true - } if !m.Pair.IsEmpty() && !m.Pair.Equal(d.Pair) { // TODO: Add a check to see if the original pair is empty as well, but // error if it is changing from BTC-USD -> LTC-USD. @@ -337,10 +333,6 @@ func (d *Detail) UpdateOrderFromModifyResponse(m *ModifyResponse) { d.TriggerPrice = m.TriggerPrice updated = true } - if m.PostOnly != d.PostOnly { - d.PostOnly = m.PostOnly - updated = true - } if !m.Pair.IsEmpty() && !m.Pair.Equal(d.Pair) { // TODO: Add a check to see if the original pair is empty as well, but // error if it is changing from BTC-USD -> LTC-USD. @@ -590,7 +582,6 @@ func (s *SubmitResponse) DeriveDetail(internal uuid.UUID) (*Detail, error) { AssetType: s.AssetType, TimeInForce: s.TimeInForce, - PostOnly: s.PostOnly, ReduceOnly: s.ReduceOnly, Leverage: s.Leverage, Price: s.Price, @@ -691,10 +682,10 @@ func (t Type) String() string { return "LIMIT" case Market: return "MARKET" - case PostOnly: - return "POST_ONLY" - case ImmediateOrCancel: - return "IMMEDIATE_OR_CANCEL" + // case PostOnly: + // return "POST_ONLY" + // case ImmediateOrCancel: + // return "IMMEDIATE_OR_CANCEL" case Stop: return "STOP" case ConditionalStop: @@ -717,8 +708,8 @@ func (t Type) String() string { return "TAKE PROFIT MARKET" case TrailingStop: return "TRAILING_STOP" - case FillOrKill: - return "FOK" + // case FillOrKill: + // return "FOK" case IOS: return "IOS" case Liquidation: @@ -737,7 +728,7 @@ func (t Type) String() string { // String implements the stringer interface. func (t TimeInForce) String() string { switch t { - case IOC: + case ImmediateOrCancel: return "IOC" case GoodTillCancel: return "GTC" @@ -745,11 +736,10 @@ func (t TimeInForce) String() string { return "GTD" case GoodTillTime: return "GTT" - case FOK: + case FillOrKill: return "FOK" - case PostOnlyGTC: - // Added in Bittrex exchange to represent PostOnly and GTC - return "POST_ONLY_GOOD_TIL_CANCELLED" + case PostOnly: + return "POSTONLY" case UnsetTIF: return "" default: @@ -759,7 +749,7 @@ func (t TimeInForce) String() string { // IsIOC determines whether the TimeInForce value is set to IOC (Immediate or Cancel). func (t TimeInForce) IsIOC() bool { - return t == IOC + return t == ImmediateOrCancel } // Lower returns the type lower case string @@ -1159,8 +1149,8 @@ func StringToOrderType(oType string) (Type, error) { return Limit, nil case Market.String(), "EXCHANGE MARKET": return Market, nil - case ImmediateOrCancel.String(), "IMMEDIATE OR CANCEL", "IOC", "EXCHANGE IOC": - return ImmediateOrCancel, nil + // case ImmediateOrCancel.String(), "IMMEDIATE OR CANCEL", "IOC", "EXCHANGE IOC": + // return ImmediateOrCancel, nil case Stop.String(), "STOP LOSS", "STOP_LOSS", "EXCHANGE STOP": return Stop, nil case StopLimit.String(), "EXCHANGE STOP LIMIT", "STOP_LIMIT": @@ -1169,12 +1159,12 @@ func StringToOrderType(oType string) (Type, error) { return StopMarket, nil case TrailingStop.String(), "TRAILING STOP", "EXCHANGE TRAILING STOP", "MOVE_ORDER_STOP": return TrailingStop, nil - case FillOrKill.String(), "EXCHANGE FOK": - return FillOrKill, nil + // case FillOrKill.String(), "EXCHANGE FOK": + // return FillOrKill, nil case IOS.String(): return IOS, nil - case PostOnly.String(): - return PostOnly, nil + // case PostOnly.String(): + // return PostOnly, nil case AnyType.String(): return AnyType, nil case Trigger.String(): @@ -1260,18 +1250,18 @@ func StringToOrderStatus(status string) (Status, error) { func StringToTimeInForce(timeInForce string) (TimeInForce, error) { timeInForce = strings.ToUpper(timeInForce) switch timeInForce { - case "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL", IOC.String(), "POC", "PENDINGORCANCEL": - return IOC, nil + case "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL", ImmediateOrCancel.String(), "POC", "PENDINGORCANCEL": + return ImmediateOrCancel, nil case "GOODTILLCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED", "GOOD_TILL_CANCELED", GoodTillCancel.String(): return GoodTillCancel, nil case "GOODTILLDAY", GoodTillDay.String(), "GOOD_TIL_DAY", "GOOD_TILL_DAY": return GoodTillDay, nil case "GOODTILLTIME", "GOOD_TIL_TIME", GoodTillTime.String(): return GoodTillTime, nil - case "FILLORKILL", "FILL_OR_KILL", FOK.String(): - return FOK, nil - case "POST_ONLY_GOOD_TILL_CANCELLED", PostOnlyGTC.String(): - return PostOnlyGTC, nil + case "FILLORKILL", "FILL_OR_KILL", FillOrKill.String(): + return FillOrKill, nil + case "POST_ONLY_GOOD_TILL_CANCELLED", PostOnly.String(): + return PostOnly, nil case "": return UnsetTIF, nil default: