From a8a3bc4ee2954f10c49d9055896c520862e01816 Mon Sep 17 00:00:00 2001 From: "Samuael A." <39623015+samuael@users.noreply.github.com> Date: Thu, 12 Jun 2025 08:39:17 +0300 Subject: [PATCH] exchanges: Order types update (#1850) * Added TimeInForce type and updated related files * Linter issue fix and minor coinbasepro type update * Bitrex consts update * added unit test and minor changes in bittrex * Unit tests update * Fix minor linter issues * Update TestStringToTimeInForce unit test * fix conflict with gateio timeInForce * Update order tests * Complete updating the order unit tests * update kucoin and deribit wrapper to match the time in force change * fix time-in-force related test errors * linter issue fix * time in force constants, functions and unit tests update * shift tif policies to TimeInForce * Update time-in-force, related functions, and unit tests * fix linter issue and time-in-force processing * added a good till crossing tif value * order type fix and fix related tim-in-force entries * update time-in-force unmarshaling and unit test * update order type to support time-in-force and hybrid order type representation * added tests for type and time-in-force check from order type * fix time-in-force error in gateio * fix minor unit test issues * linter issue fix * update based on review comments * add unit test and fix missing issues * minor fix and added benchmark unit test * change GTT to GTC for limit * fix linter issue * linter issues fix * update order types declaration and handling by endpoints * added time-in-force value to place order param * fix minor issues based on review comment and move tif code to separate files * update on exchanges linked to time-in-force * resolve missing review comments * minor linter issues fix * added time-in-force handler and update timeInForce parametered endpoint * minor fixes based on review * nits fix * update based on review * linter fix * rm getTimeInForce func and minor change to time-in-force * minor change * update based on review comments * wrappers and time-in-force calling approach * minor change * update gateio string to timeInForce conversion and unit test * fix types to string conversion * fix build errors * update on order types handling and unit tests * fix linter issue * order type unit tests update * order types string as constant replacement and unit tests update * update order type-string functions unit test --- exchanges/kucoin/kucoin_test.go | 2 +- exchanges/okx/helpers.go | 75 +++++++++++++++---------- exchanges/okx/okx.go | 6 +- exchanges/okx/okx_test.go | 69 +++++++++++++---------- exchanges/okx/okx_types.go | 27 +++++---- exchanges/okx/okx_wrapper.go | 18 +++--- exchanges/order/order_test.go | 68 ++++++++++++++++++++++- exchanges/order/order_types.go | 50 +++++++++++++---- exchanges/order/orders.go | 98 ++++++++++++++++----------------- 9 files changed, 265 insertions(+), 148 deletions(-) diff --git a/exchanges/kucoin/kucoin_test.go b/exchanges/kucoin/kucoin_test.go index b461bced..20ba655c 100644 --- a/exchanges/kucoin/kucoin_test.go +++ b/exchanges/kucoin/kucoin_test.go @@ -2226,7 +2226,7 @@ func TestGetActiveOrders(t *testing.T) { Side: order.Buy, } - getOrdersRequest.Type = order.OptimalLimitIOC + getOrdersRequest.Type = order.OptimalLimit _, err = ku.GetActiveOrders(t.Context(), &getOrdersRequest) require.ErrorIs(t, err, order.ErrUnsupportedOrderType) diff --git a/exchanges/okx/helpers.go b/exchanges/okx/helpers.go index 8a3f3e37..2d940707 100644 --- a/exchanges/okx/helpers.go +++ b/exchanges/okx/helpers.go @@ -24,49 +24,64 @@ func orderTypeFromString(orderType string) (order.Type, order.TimeInForce, error case orderIOC: return order.Limit, order.ImmediateOrCancel, nil case orderOptimalLimitIOC: - return order.OptimalLimitIOC, order.ImmediateOrCancel, nil - case "mmp": + return order.OptimalLimit, order.ImmediateOrCancel, nil + case orderMarketMakerProtection: return order.MarketMakerProtection, order.UnknownTIF, nil - case "mmp_and_post_only": - return order.MarketMakerProtectionAndPostOnly, order.PostOnly, nil - case "twap": + case orderMarketMakerProtectionAndPostOnly: + return order.MarketMakerProtection, order.PostOnly, nil + case orderTWAP: return order.TWAP, order.UnknownTIF, nil - case "move_order_stop": + case orderMoveOrderStop: return order.TrailingStop, order.UnknownTIF, nil - case "chase": + case orderChase: return order.Chase, order.UnknownTIF, nil default: - return order.UnknownType, order.UnknownTIF, fmt.Errorf("%w %v", order.ErrTypeIsInvalid, orderType) + return order.UnknownType, order.UnknownTIF, fmt.Errorf("%w %q", order.ErrTypeIsInvalid, orderType) } } // orderTypeString returns a string representation of order.Type instance 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 orderIOC, nil - } switch orderType { - case order.Market, - order.Limit, - order.Trigger, - order.OptimalLimitIOC, - order.MarketMakerProtection, - order.MarketMakerProtectionAndPostOnly, + case order.MarketMakerProtection: + if tif == order.PostOnly { + return orderMarketMakerProtectionAndPostOnly, nil + } + return orderMarketMakerProtection, nil + case order.OptimalLimit: + return orderOptimalLimitIOC, nil + case order.Limit: + if tif == order.PostOnly { + return orderPostOnly, nil + } + return orderLimit, nil + case order.Market: + switch tif { + case order.FillOrKill: + return orderFOK, nil + case order.ImmediateOrCancel: + return orderIOC, nil + } + return orderMarket, nil + case order.Trigger, order.Chase, order.TWAP, order.OCO: return orderType.Lower(), nil case order.ConditionalStop: - return "conditional", nil + return orderConditional, nil case order.TrailingStop: - return "move_order_stop", nil + return orderMoveOrderStop, nil default: - return "", fmt.Errorf("%w: `%v`", order.ErrUnsupportedOrderType, orderType) + switch tif { + case order.PostOnly: + return orderPostOnly, nil + case order.FillOrKill: + return orderFOK, nil + case order.ImmediateOrCancel: + return orderIOC, nil + } + return "", fmt.Errorf("%w: %q", order.ErrUnsupportedOrderType, orderType) } } @@ -152,15 +167,15 @@ func assetTypeFromInstrumentType(instrumentType string) (asset.Item, error) { func assetTypeString(assetType asset.Item) (string, error) { switch assetType { case asset.Spot: - return "SPOT", nil + return instTypeSpot, nil case asset.Margin: - return "MARGIN", nil + return instTypeMargin, nil case asset.Futures: - return "FUTURES", nil + return instTypeFutures, nil case asset.Options: - return "OPTION", nil + return instTypeOption, nil case asset.PerpetualSwap: - return "SWAP", nil + return instTypeSwap, nil default: return "", asset.ErrNotSupported } diff --git a/exchanges/okx/okx.go b/exchanges/okx/okx.go index 0f4e933b..8d9ecedc 100644 --- a/exchanges/okx/okx.go +++ b/exchanges/okx/okx.go @@ -462,7 +462,7 @@ func (ok *Okx) PlaceTakeProfitStopLossOrder(ctx context.Context, arg *AlgoOrderP if *arg == (AlgoOrderParams{}) { return nil, common.ErrEmptyParams } - if arg.OrderType != "conditional" { + if arg.OrderType != orderConditional { return nil, fmt.Errorf("%w for TPSL: %q", order.ErrTypeIsInvalid, arg.OrderType) } if arg.StopLossTriggerPrice <= 0 { @@ -481,7 +481,7 @@ func (ok *Okx) PlaceChaseAlgoOrder(ctx context.Context, arg *AlgoOrderParams) (* if *arg == (AlgoOrderParams{}) { return nil, common.ErrEmptyParams } - if arg.OrderType != "chase" { + if arg.OrderType != orderChase { return nil, fmt.Errorf("%w: order type value 'chase' is only supported for chase orders", order.ErrTypeIsInvalid) } if (arg.MaxChaseType == "" || arg.MaxChaseValue == 0) && @@ -496,7 +496,7 @@ func (ok *Okx) PlaceTriggerAlgoOrder(ctx context.Context, arg *AlgoOrderParams) if *arg == (AlgoOrderParams{}) { return nil, common.ErrEmptyParams } - if arg.OrderType != "trigger" { + if arg.OrderType != orderTrigger { return nil, fmt.Errorf("%w for Trigger: %q", order.ErrTypeIsInvalid, arg.OrderType) } if arg.TriggerPrice <= 0 { diff --git a/exchanges/okx/okx_test.go b/exchanges/okx/okx_test.go index cd6b01b1..3eb3b139 100644 --- a/exchanges/okx/okx_test.go +++ b/exchanges/okx/okx_test.go @@ -1222,7 +1222,7 @@ func TestPlaceChaseAlgoOrder(t *testing.T) { _, err = ok.PlaceChaseAlgoOrder(contextGenerate(), arg) require.ErrorIs(t, err, order.ErrTypeIsInvalid) - arg.OrderType = "chase" + arg.OrderType = orderChase arg.MaxChaseType = "percentage" _, err = ok.PlaceChaseAlgoOrder(contextGenerate(), arg) require.ErrorIs(t, err, errPriceTrackingNotSet) @@ -1250,7 +1250,7 @@ func TestPlaceChaseAlgoOrder(t *testing.T) { AlgoClientOrderID: "681096944655273984", InstrumentID: mainPair.String(), LimitPrice: 100.22, - OrderType: "chase", + OrderType: orderChase, TradeMode: "cross", Side: order.Sell.Lower(), MaxChaseType: "distance", @@ -1297,14 +1297,14 @@ func TestPlaceTrailingStopOrder(t *testing.T) { assert.ErrorIs(t, err, common.ErrEmptyParams) _, err = ok.PlaceTrailingStopOrder(contextGenerate(), &AlgoOrderParams{Size: 2}) assert.ErrorIs(t, err, order.ErrTypeIsInvalid) - _, err = ok.PlaceTrailingStopOrder(contextGenerate(), &AlgoOrderParams{Size: 2, OrderType: "move_order_stop"}) + _, err = ok.PlaceTrailingStopOrder(contextGenerate(), &AlgoOrderParams{Size: 2, OrderType: orderMoveOrderStop}) assert.ErrorIs(t, err, errPriceTrackingNotSet) // Offline error handling unit tests for the base function PlaceAlgoOrder are already covered within unit test TestPlaceAlgoOrder. sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders) result, err := ok.PlaceTrailingStopOrder(contextGenerate(), &AlgoOrderParams{ AlgoClientOrderID: "681096944655273984", CallbackRatio: 0.01, - InstrumentID: mainPair.String(), OrderType: "move_order_stop", + InstrumentID: mainPair.String(), OrderType: orderMoveOrderStop, Side: order.Buy.Lower(), TradeMode: "isolated", Size: 2, ActivePrice: 1234, }) @@ -5843,26 +5843,35 @@ func TestOrderTypeString(t *testing.T) { Expected string Error error }{ - {OrderType: order.Market, TIF: order.UnknownTIF}: {Expected: orderMarket}, - {OrderType: order.Limit, TIF: order.UnknownTIF}: {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.UnknownTIF}: {Expected: orderOptimalLimitIOC}, - {OrderType: order.MarketMakerProtection, TIF: order.UnknownTIF}: {Expected: "mmp"}, - {OrderType: order.MarketMakerProtectionAndPostOnly, TIF: order.UnknownTIF}: {Expected: "mmp_and_post_only"}, - {OrderType: order.Liquidation, TIF: order.UnknownTIF}: {Error: order.ErrUnsupportedOrderType}, - {OrderType: order.OCO, TIF: order.UnknownTIF}: {Expected: "oco"}, - {OrderType: order.TrailingStop, TIF: order.UnknownTIF}: {Expected: "move_order_stop"}, - {OrderType: order.Chase, TIF: order.UnknownTIF}: {Expected: "chase"}, - {OrderType: order.TWAP, TIF: order.UnknownTIF}: {Expected: "twap"}, - {OrderType: order.ConditionalStop, TIF: order.UnknownTIF}: {Expected: "conditional"}, - {OrderType: order.Trigger, TIF: order.UnknownTIF}: {Expected: "trigger"}, + {OrderType: order.Market, TIF: order.UnknownTIF}: {Expected: orderMarket}, + {OrderType: order.Limit, TIF: order.UnknownTIF}: {Expected: orderLimit}, + {OrderType: order.Limit, TIF: order.PostOnly}: {Expected: orderPostOnly}, + {OrderType: order.Market, TIF: order.FillOrKill}: {Expected: orderFOK}, + {OrderType: order.Market, TIF: order.ImmediateOrCancel}: {Expected: orderIOC}, + {OrderType: order.OptimalLimit, TIF: order.ImmediateOrCancel}: {Expected: orderOptimalLimitIOC}, + {OrderType: order.MarketMakerProtection, TIF: order.UnknownTIF}: {Expected: orderMarketMakerProtection}, + {OrderType: order.MarketMakerProtection, TIF: order.PostOnly}: {Expected: orderMarketMakerProtectionAndPostOnly}, + {OrderType: order.Liquidation, TIF: order.UnknownTIF}: {Error: order.ErrUnsupportedOrderType}, + {OrderType: order.OCO, TIF: order.UnknownTIF}: {Expected: orderOCO}, + {OrderType: order.TrailingStop, TIF: order.UnknownTIF}: {Expected: orderMoveOrderStop}, + {OrderType: order.Chase, TIF: order.UnknownTIF}: {Expected: orderChase}, + {OrderType: order.TWAP, TIF: order.UnknownTIF}: {Expected: orderTWAP}, + {OrderType: order.ConditionalStop, TIF: order.UnknownTIF}: {Expected: orderConditional}, + {OrderType: order.Chase, TIF: order.GoodTillCancel}: {Expected: orderChase}, + {OrderType: order.TWAP, TIF: order.ImmediateOrCancel}: {Expected: orderTWAP}, + {OrderType: order.ConditionalStop, TIF: order.GoodTillDay}: {Expected: orderConditional}, + {OrderType: order.Trigger, TIF: order.UnknownTIF}: {Expected: orderTrigger}, + {OrderType: order.UnknownType, TIF: order.PostOnly}: {Expected: orderPostOnly}, + {OrderType: order.UnknownType, TIF: order.FillOrKill}: {Expected: orderFOK}, + {OrderType: order.UnknownType, TIF: order.ImmediateOrCancel}: {Expected: orderIOC}, } for tc, val := range orderTypesToStringMap { - orderTypeString, err := orderTypeString(tc.OrderType, tc.TIF) - require.ErrorIs(t, err, val.Error) - assert.Equal(t, val.Expected, orderTypeString) + t.Run(tc.OrderType.String()+"/"+tc.TIF.String(), func(t *testing.T) { + t.Parallel() + orderTypeString, err := orderTypeString(tc.OrderType, tc.TIF) + require.ErrorIs(t, err, val.Error) + assert.Equal(t, val.Expected, orderTypeString) + }) } } @@ -6127,7 +6136,6 @@ func TestWsProcessSpreadTradesJSON(t *testing.T) { func TestOrderTypeFromString(t *testing.T) { t.Parallel() - orderTypeStrings := map[string]struct { OType order.Type TIF order.TimeInForce @@ -6139,9 +6147,9 @@ func TestOrderTypeFromString(t *testing.T) { "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}, + "optimal_limit_ioc": {OType: order.OptimalLimit, TIF: order.ImmediateOrCancel}, "mmp": {OType: order.MarketMakerProtection}, - "mmp_and_post_only": {OType: order.MarketMakerProtectionAndPostOnly, TIF: order.PostOnly}, + "mmp_and_post_only": {OType: order.MarketMakerProtection, TIF: order.PostOnly}, "trigger": {OType: order.UnknownType, Error: order.ErrTypeIsInvalid}, "chase": {OType: order.Chase}, "move_order_stop": {OType: order.TrailingStop}, @@ -6149,10 +6157,13 @@ func TestOrderTypeFromString(t *testing.T) { "abcd": {OType: order.UnknownType, Error: order.ErrTypeIsInvalid}, } 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) + t.Run(s, func(t *testing.T) { + t.Parallel() + 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()) + }) } } diff --git a/exchanges/okx/okx_types.go b/exchanges/okx/okx_types.go index 22f42bea..6d116870 100644 --- a/exchanges/okx/okx_types.go +++ b/exchanges/okx/okx_types.go @@ -41,19 +41,22 @@ const ( positionSideNet = "net" ) +// order types, margin balance types, and instrument types constants const ( - // orderLimit Limit order - orderLimit = "limit" - // orderMarket Market order - orderMarket = "market" - // orderPostOnly POST_ONLY order type - orderPostOnly = "post_only" - // orderFOK fill or kill order type - orderFOK = "fok" - // orderIOC IOC (immediate or cancel) - orderIOC = "ioc" - // orderOptimalLimitIOC OPTIMAL_LIMIT_IOC - orderOptimalLimitIOC = "optimal_limit_ioc" + orderLimit = "limit" + orderMarket = "market" + orderPostOnly = "post_only" + orderFOK = "fok" + orderIOC = "ioc" + orderOptimalLimitIOC = "optimal_limit_ioc" + orderConditional = "conditional" + orderMoveOrderStop = "move_order_stop" + orderChase = "chase" + orderTWAP = "twap" + orderTrigger = "trigger" + orderMarketMakerProtectionAndPostOnly = "mmp_and_post_only" + orderMarketMakerProtection = "mmp" + orderOCO = "oco" // represents a margin balance type marginBalanceReduce = "reduce" diff --git a/exchanges/okx/okx_wrapper.go b/exchanges/okx/okx_wrapper.go index f335460a..219366e6 100644 --- a/exchanges/okx/okx_wrapper.go +++ b/exchanges/okx/okx_wrapper.go @@ -958,7 +958,7 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR return nil, err } return s.DeriveSubmitResponse(placeOrderResponse.OrderID) - case "trigger": + case orderTrigger: result, err = ok.PlaceTriggerAlgoOrder(ctx, &AlgoOrderParams{ InstrumentID: pairString, TradeMode: tradeMode, @@ -970,7 +970,7 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR TriggerPrice: s.TriggerPrice, TriggerPriceType: priceTypeString(s.TriggerPriceType), }) - case "conditional": + case orderConditional: // Trigger Price and type are used as a stop losss trigger price and type. result, err = ok.PlaceTakeProfitStopLossOrder(ctx, &AlgoOrderParams{ InstrumentID: pairString, @@ -984,7 +984,7 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR StopLossOrderPrice: s.Price, StopLossTriggerPriceType: priceTypeString(s.TriggerPriceType), }) - case "chase": + case orderChase: if s.TrackingMode == order.UnknownTrackingMode { return nil, fmt.Errorf("%w, tracking mode unset", order.ErrUnknownTrackingMode) } @@ -1002,7 +1002,7 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR MaxChaseType: s.TrackingMode.String(), MaxChaseValue: s.TrackingValue, }) - case "move_order_stop": + case orderMoveOrderStop: if s.TrackingMode == order.UnknownTrackingMode { return nil, fmt.Errorf("%w, tracking mode unset", order.ErrUnknownTrackingMode) } @@ -1025,7 +1025,7 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR CallbackSpreadVariance: callbackSpread, ActivePrice: s.TriggerPrice, }) - case "twap": + case orderTWAP: if s.TrackingMode == order.UnknownTrackingMode { return nil, fmt.Errorf("%w, tracking mode unset", order.ErrUnknownTrackingMode) } @@ -1050,7 +1050,7 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR LimitPrice: s.Price, TimeInterval: kline.FifteenMin, }) - case "oco": + case orderOCO: switch { case s.RiskManagementModes.TakeProfit.Price <= 0: return nil, fmt.Errorf("%w, take profit price is required", order.ErrPriceBelowMin) @@ -1144,7 +1144,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.OptimalLimitIOC, order.MarketMakerProtection, order.MarketMakerProtectionAndPostOnly: + case order.UnknownType, order.Market, order.Limit, order.OptimalLimit, order.MarketMakerProtection: amendRequest := AmendOrderRequestParams{ InstrumentID: pairFormat.Format(action.Pair), NewQuantity: action.Amount, @@ -1248,7 +1248,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.OptimalLimitIOC, order.MarketMakerProtection, order.MarketMakerProtectionAndPostOnly: + case order.UnknownType, order.Market, order.Limit, order.OptimalLimit, order.MarketMakerProtection: req := CancelOrderRequestParam{ InstrumentID: instrumentID, OrderID: ord.OrderID, @@ -1301,7 +1301,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.OptimalLimitIOC, order.MarketMakerProtection, order.MarketMakerProtectionAndPostOnly: + case order.UnknownType, order.Market, order.Limit, order.OptimalLimit, order.MarketMakerProtection: 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) } diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 1a589ac4..698b2917 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -297,6 +297,33 @@ func TestTitle(t *testing.T) { require.Equal(t, "Limit", orderType.Title()) } +func TestOrderIs(t *testing.T) { + t.Parallel() + orderComparisonList := []struct { + Type Type + Targets []Type + }{ + {Type: Limit | TakeProfit, Targets: []Type{TakeProfit, Limit}}, + {Type: IOS, Targets: []Type{IOS}}, + {Type: Stop, Targets: []Type{Stop}}, + {Type: AnyType, Targets: []Type{AnyType}}, + {Type: StopLimit, Targets: []Type{Stop, Limit}}, + {Type: TakeProfit, Targets: []Type{TakeProfit}}, + {Type: StopMarket, Targets: []Type{Stop, Market}}, + {Type: TrailingStop, Targets: []Type{TrailingStop}}, + {Type: UnknownType | Limit, Targets: []Type{Limit}}, + {Type: TakeProfitMarket, Targets: []Type{TakeProfit, Market}}, + } + for _, oType := range orderComparisonList { + t.Run(oType.Type.String(), func(t *testing.T) { + t.Parallel() + for _, target := range oType.Targets { + assert.Truef(t, oType.Type.Is(target), "expected %v, got %q", target, oType.Type.String()) + } + }) + } +} + func TestOrderTypes(t *testing.T) { t.Parallel() var orderType Type @@ -305,6 +332,41 @@ func TestOrderTypes(t *testing.T) { assert.Equal(t, "Unknown", orderType.Title()) } +func TestOrderTypeToString(t *testing.T) { + t.Parallel() + orderToToStringsList := []struct { + OrderType Type + String string + }{ + {StopMarket, "STOP MARKET"}, + {StopLimit, "STOP LIMIT"}, + {Limit, "LIMIT"}, + {Market, "MARKET"}, + {Stop, "STOP"}, + {ConditionalStop, "CONDITIONAL"}, + {TWAP, "TWAP"}, + {Chase, "CHASE"}, + {TakeProfit, "TAKE PROFIT"}, + {TakeProfitMarket, "TAKE PROFIT MARKET"}, + {TrailingStop, "TRAILING_STOP"}, + {IOS, "IOS"}, + {Liquidation, "LIQUIDATION"}, + {Trigger, "TRIGGER"}, + {OCO, "OCO"}, + {OptimalLimit, "OPTIMAL_LIMIT"}, + {MarketMakerProtection, "MMP"}, + {AnyType, "ANY"}, + {UnknownType | Limit, "LIMIT"}, + {StopMarket | ConditionalStop, "UNKNOWN"}, + } + for _, tt := range orderToToStringsList { + t.Run(tt.String, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.String, tt.OrderType.String()) + }) + } +} + func TestInferCostsAndTimes(t *testing.T) { t.Parallel() var detail Detail @@ -736,6 +798,7 @@ func BenchmarkStringToOrderSide(b *testing.B) { } func TestStringToOrderType(t *testing.T) { + t.Parallel() cases := []struct { in string out Type @@ -766,14 +829,12 @@ func TestStringToOrderType(t *testing.T) { {"conDitiOnal", ConditionalStop, nil}, {"oCo", OCO, nil}, {"mMp", MarketMakerProtection, nil}, - {"Mmp_And_Post_oNly", MarketMakerProtectionAndPostOnly, nil}, {"tWaP", TWAP, nil}, {"TWAP", TWAP, nil}, {"woahMan", UnknownType, errUnrecognisedOrderType}, {"chase", Chase, nil}, {"MOVE_ORDER_STOP", TrailingStop, nil}, {"mOVe_OrdeR_StoP", TrailingStop, nil}, - {"optimal_limit_IoC", OptimalLimitIOC, nil}, {"Stop_market", StopMarket, nil}, {"liquidation", Liquidation, nil}, {"LiQuidation", Liquidation, nil}, @@ -781,10 +842,13 @@ func TestStringToOrderType(t *testing.T) { {"Take ProfIt", TakeProfit, nil}, {"TAKE PROFIT MARkEt", TakeProfitMarket, nil}, {"TAKE_PROFIT_MARkEt", TakeProfitMarket, nil}, + {"optimal_limit", OptimalLimit, nil}, + {"OPTIMAL_LIMIT", OptimalLimit, nil}, } for i := range cases { testData := &cases[i] t.Run(testData.in, func(t *testing.T) { + t.Parallel() out, err := StringToOrderType(testData.in) require.ErrorIs(t, err, testData.err) assert.Equal(t, testData.out, out) diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index a3ce3943..89733416 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -348,7 +348,12 @@ const ( ) // Type enforces a standard for order types across the code base -type Type uint32 +type Type uint64 + +// Is checks to see if the Type contains the Type cmp +func (t Type) Is(cmp Type) bool { + return cmp != 0 && t&cmp == cmp +} // Defined package order types const ( @@ -356,22 +361,45 @@ const ( Limit Type = 1 << iota Market Stop - StopLimit - StopMarket TakeProfit - TakeProfitMarket TrailingStop IOS AnyType Liquidation Trigger - OptimalLimitIOC - OCO // One-cancels-the-other order - ConditionalStop // One-way stop order - MarketMakerProtection // market-maker-protection used with portfolio margin mode. See https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-place-order - MarketMakerProtectionAndPostOnly // market-maker-protection and post-only mode. Used in Okx exchange orders. - TWAP // time-weighted average price. - Chase // chase order. See https://www.okx.com/docs-v5/en/#order-book-trading-algo-trading-post-place-algo-order + OCO // One-cancels-the-other order + ConditionalStop // One-way stop order + TWAP // time-weighted average price + Chase // chase limit order + OptimalLimit + MarketMakerProtection + + // Hybrid order types + StopLimit = Stop | Limit + StopMarket = Stop | Market + TakeProfitMarket = TakeProfit | Market +) + +// order-type string representations +const ( + orderStopMarket = "STOP MARKET" + orderStopLimit = "STOP LIMIT" + orderLimit = "LIMIT" + orderMarket = "MARKET" + orderStop = "STOP" + orderConditionalStop = "CONDITIONAL" + orderTWAP = "TWAP" + orderChase = "CHASE" + orderTakeProfit = "TAKE PROFIT" + orderTakeProfitMarket = "TAKE PROFIT MARKET" + orderTrailingStop = "TRAILING_STOP" + orderIOS = "IOS" + orderLiquidation = "LIQUIDATION" + orderTrigger = "TRIGGER" + orderOCO = "OCO" + orderOptimalLimit = "OPTIMAL_LIMIT" + orderMarketMakerProtection = "MMP" + orderAnyType = "ANY" ) // Side enforces a standard for order sides across the code base diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 03cc8a0f..550d95bb 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -667,44 +667,42 @@ func (d *Detail) DeriveCancel() (*Cancel, error) { // String implements the stringer interface func (t Type) String() string { switch t { - case AnyType: - return "ANY" - case Limit: - return "LIMIT" - case Market: - return "MARKET" - case Stop: - return "STOP" - case ConditionalStop: - return "CONDITIONAL" - case MarketMakerProtection: - return "MMP" - case MarketMakerProtectionAndPostOnly: - return "MMP_AND_POST_ONLY" - case TWAP: - return "TWAP" - case Chase: - return "CHASE" - case StopLimit: - return "STOP LIMIT" case StopMarket: - return "STOP MARKET" + return orderStopMarket + case StopLimit: + return orderStopLimit + case Limit: + return orderLimit + case Market: + return orderMarket + case Stop: + return orderStop + case ConditionalStop: + return orderConditionalStop + case TWAP: + return orderTWAP + case Chase: + return orderChase case TakeProfit: - return "TAKE PROFIT" + return orderTakeProfit case TakeProfitMarket: - return "TAKE PROFIT MARKET" + return orderTakeProfitMarket case TrailingStop: - return "TRAILING_STOP" + return orderTrailingStop case IOS: - return "IOS" + return orderIOS case Liquidation: - return "LIQUIDATION" + return orderLiquidation case Trigger: - return "TRIGGER" - case OptimalLimitIOC: - return "OPTIMAL_LIMIT_IOC" + return orderTrigger case OCO: - return "OCO" + return orderOCO + case OptimalLimit: + return orderOptimalLimit + case MarketMakerProtection: + return orderMarketMakerProtection + case AnyType: + return orderAnyType default: return "UNKNOWN" } @@ -1102,43 +1100,41 @@ func (s Side) MarshalJSON() ([]byte, error) { func StringToOrderType(oType string) (Type, error) { oType = strings.ToUpper(oType) switch oType { - case Limit.String(), "EXCHANGE LIMIT": + case orderLimit, "EXCHANGE LIMIT": return Limit, nil - case Market.String(), "EXCHANGE MARKET": + case orderMarket, "EXCHANGE MARKET": return Market, nil - case Stop.String(), "STOP LOSS", "STOP_LOSS", "EXCHANGE STOP": + case orderStop, "STOP LOSS", "STOP_LOSS", "EXCHANGE STOP": return Stop, nil - case StopLimit.String(), "EXCHANGE STOP LIMIT", "STOP_LIMIT": + case orderStopLimit, "EXCHANGE STOP LIMIT", "STOP_LIMIT": return StopLimit, nil - case StopMarket.String(), "STOP_MARKET": + case orderStopMarket, "STOP_MARKET": return StopMarket, nil - case TrailingStop.String(), "TRAILING STOP", "EXCHANGE TRAILING STOP", "MOVE_ORDER_STOP": + case orderTrailingStop, "TRAILING STOP", "EXCHANGE TRAILING STOP", "MOVE_ORDER_STOP": return TrailingStop, nil - case IOS.String(): + case orderIOS: return IOS, nil - case AnyType.String(): + case orderAnyType: return AnyType, nil - case Trigger.String(): + case orderTrigger: return Trigger, nil - case OptimalLimitIOC.String(): - return OptimalLimitIOC, nil - case OCO.String(): + case orderOptimalLimit: + return OptimalLimit, nil + case orderOCO: return OCO, nil - case ConditionalStop.String(): + case orderConditionalStop: return ConditionalStop, nil - case MarketMakerProtection.String(): + case orderMarketMakerProtection: return MarketMakerProtection, nil - case MarketMakerProtectionAndPostOnly.String(): - return MarketMakerProtectionAndPostOnly, nil - case TWAP.String(): + case orderTWAP: return TWAP, nil - case Chase.String(): + case orderChase: return Chase, nil - case TakeProfitMarket.String(), "TAKE_PROFIT_MARKET": + case orderTakeProfitMarket, "TAKE_PROFIT_MARKET": return TakeProfitMarket, nil - case TakeProfit.String(), "TAKE_PROFIT": + case orderTakeProfit, "TAKE_PROFIT": return TakeProfit, nil - case Liquidation.String(): + case orderLiquidation: return Liquidation, nil default: return UnknownType, fmt.Errorf("'%v' %w", oType, errUnrecognisedOrderType)