From 5412e914b5046c60ac26debaaf451981563f9a76 Mon Sep 17 00:00:00 2001 From: Samuael A <39623015+samuael@users.noreply.github.com> Date: Sun, 29 Oct 2023 21:28:08 +0300 Subject: [PATCH 01/20] Added TimeInForce type and updated related files --- .../exchange_wrapper_standards_test.go | 40 +++--- engine/order_manager.go | 8 +- exchanges/binance/binance_test.go | 2 +- exchanges/binance/binance_types.go | 2 +- exchanges/binance/binance_wrapper.go | 6 +- exchanges/bittrex/bittrex_websocket.go | 38 ++--- exchanges/bittrex/bittrex_wrapper.go | 28 ++-- exchanges/btcmarkets/btcmarkets.go | 11 +- exchanges/btcmarkets/btcmarkets_test.go | 4 +- exchanges/coinbasepro/coinbasepro.go | 2 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 6 +- exchanges/huobi/huobi_wrapper.go | 2 +- exchanges/kraken/kraken_types.go | 50 +++---- exchanges/kraken/kraken_wrapper.go | 8 +- exchanges/order/order_test.go | 127 +++++++++-------- exchanges/order/order_types.go | 66 +++++---- exchanges/order/orders.go | 130 +++++++++++------- exchanges/poloniex/poloniex_wrapper.go | 2 +- 18 files changed, 299 insertions(+), 233 deletions(-) diff --git a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go index 995c40ecce4..e19588c4c3b 100644 --- a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go +++ b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go @@ -420,29 +420,29 @@ func generateMethodArg(ctx context.Context, t *testing.T, argGenerator *MethodAr input = reflect.ValueOf(req) case argGenerator.MethodInputType.AssignableTo(orderSubmitParam): input = reflect.ValueOf(&order.Submit{ - Exchange: exchName, - Type: order.Limit, - Side: order.Buy, - Pair: argGenerator.AssetParams.Pair, - AssetType: argGenerator.AssetParams.Asset, - Price: 1337, - Amount: 1, - ClientID: "1337", - ClientOrderID: "13371337", - ImmediateOrCancel: true, + Exchange: exchName, + Type: order.Limit, + Side: order.Buy, + Pair: argGenerator.AssetParams.Pair, + AssetType: argGenerator.AssetParams.Asset, + Price: 1337, + Amount: 1, + ClientID: "1337", + ClientOrderID: "13371337", + TimeInForce: order.IOC, }) case argGenerator.MethodInputType.AssignableTo(orderModifyParam): input = reflect.ValueOf(&order.Modify{ - Exchange: exchName, - Type: order.Limit, - Side: order.Buy, - Pair: argGenerator.AssetParams.Pair, - AssetType: argGenerator.AssetParams.Asset, - Price: 1337, - Amount: 1, - ClientOrderID: "13371337", - OrderID: "1337", - ImmediateOrCancel: true, + Exchange: exchName, + Type: order.Limit, + Side: order.Buy, + Pair: argGenerator.AssetParams.Pair, + AssetType: argGenerator.AssetParams.Asset, + Price: 1337, + Amount: 1, + ClientOrderID: "13371337", + OrderID: "1337", + TimeInForce: order.IOC, }) case argGenerator.MethodInputType.AssignableTo(orderCancelParam): input = reflect.ValueOf(&order.Cancel{ diff --git a/engine/order_manager.go b/engine/order_manager.go index 8224a3d3178..3bd830d8bd1 100644 --- a/engine/order_manager.go +++ b/engine/order_manager.go @@ -404,10 +404,10 @@ 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.ImmediateOrCancel = det.ImmediateOrCancel // Used by Poloniex. + 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 // 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_test.go b/exchanges/binance/binance_test.go index 8cc074b0cc8..c6e92fb995c 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -1443,7 +1443,7 @@ func TestNewOrderTest(t *testing.T) { TradeType: BinanceRequestParamsOrderLimit, Price: 0.0025, Quantity: 100000, - TimeInForce: BinanceRequestParamsTimeGTC, + TimeInForce: order.GoodTillCancel.String(), } err := b.NewOrderTest(context.Background(), req) diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index 700a288d252..d3bd6f13bfa 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -374,7 +374,7 @@ type NewOrderRequest struct { TradeType RequestParamsOrderType // TimeInForce specifies how long the order remains in effect. // Examples are (Good Till Cancel (GTC), Immediate or Cancel (IOC) and Fill Or Kill (FOK)) - TimeInForce RequestParamsTimeForceType + TimeInForce string // Quantity is the total base qty spent or received in an order. Quantity float64 // QuoteOrderQty is the total quote qty spent or received in a MARKET order. diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index dedc56f2b2e..6168fcefa9b 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -990,15 +990,15 @@ func (b *Binance) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm } else { sideType = order.Sell.String() } - timeInForce := BinanceRequestParamsTimeGTC + timeInForce := order.GoodTillCancel.String() var requestParamsOrderType RequestParamsOrderType switch s.Type { case order.Market: timeInForce = "" requestParamsOrderType = BinanceRequestParamsOrderMarket case order.Limit: - if s.ImmediateOrCancel { - timeInForce = BinanceRequestParamsTimeIOC + if s.TimeInForce == order.IOC { + timeInForce = order.IOC.String() } requestParamsOrderType = BinanceRequestParamsOrderLimit default: diff --git a/exchanges/bittrex/bittrex_websocket.go b/exchanges/bittrex/bittrex_websocket.go index 565b455763b..4cf480ff91f 100644 --- a/exchanges/bittrex/bittrex_websocket.go +++ b/exchanges/bittrex/bittrex_websocket.go @@ -602,23 +602,29 @@ func (b *Bittrex) WsProcessUpdateOrder(data *OrderUpdateMessage) error { Err: err, } } - + timeInForce, err := order.StringToTimeInForce(data.Delta.TimeInForce) + if err != nil { + b.Websocket.DataHandler <- order.ClassificationError{ + Exchange: b.Name, + OrderID: data.Delta.ID, + Err: err, + } + } b.Websocket.DataHandler <- &order.Detail{ - ImmediateOrCancel: data.Delta.TimeInForce == string(ImmediateOrCancel), - FillOrKill: data.Delta.TimeInForce == string(GoodTilCancelled), - PostOnly: data.Delta.TimeInForce == string(PostOnlyGoodTilCancelled), - Price: data.Delta.Limit, - Amount: data.Delta.Quantity, - RemainingAmount: data.Delta.Quantity - data.Delta.FillQuantity, - ExecutedAmount: data.Delta.FillQuantity, - Exchange: b.Name, - OrderID: data.Delta.ID, - Type: orderType, - Side: orderSide, - Status: orderStatus, - AssetType: asset.Spot, - Date: data.Delta.CreatedAt, - Pair: pair, + TimeInForce: timeInForce, + PostOnly: data.Delta.TimeInForce == string(PostOnlyGoodTilCancelled), + Price: data.Delta.Limit, + Amount: data.Delta.Quantity, + RemainingAmount: data.Delta.Quantity - data.Delta.FillQuantity, + ExecutedAmount: data.Delta.FillQuantity, + Exchange: b.Name, + OrderID: data.Delta.ID, + Type: orderType, + Side: orderSide, + Status: orderStatus, + AssetType: asset.Spot, + Date: data.Delta.CreatedAt, + Pair: pair, } return nil } diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 3b2e65c77c3..0d5b303083e 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -698,9 +698,9 @@ func (b *Bittrex) GetOrderInfo(ctx context.Context, orderID string, _ currency.P // ConstructOrderDetail constructs an order detail item from the underlying data func (b *Bittrex) ConstructOrderDetail(orderData *OrderData) (*order.Detail, error) { - immediateOrCancel := false - if orderData.TimeInForce == string(ImmediateOrCancel) { - immediateOrCancel = true + timeInForce, err := order.StringToTimeInForce(orderData.TimeInForce) + if err != nil { + timeInForce = order.UnknownTIF } format, err := b.GetPairFormat(asset.Spot, false) @@ -743,17 +743,17 @@ func (b *Bittrex) ConstructOrderDetail(orderData *OrderData) (*order.Detail, err } return &order.Detail{ - ImmediateOrCancel: immediateOrCancel, - Amount: orderData.Quantity, - ExecutedAmount: orderData.FillQuantity, - RemainingAmount: orderData.Quantity - orderData.FillQuantity, - Price: orderData.Limit, - Date: orderData.CreatedAt, - OrderID: orderData.ID, - Exchange: b.Name, - Type: orderType, - Pair: orderPair, - Status: orderStatus, + TimeInForce: timeInForce, + Amount: orderData.Quantity, + ExecutedAmount: orderData.FillQuantity, + RemainingAmount: orderData.Quantity - orderData.FillQuantity, + Price: orderData.Limit, + Date: orderData.CreatedAt, + OrderID: orderData.ID, + Exchange: b.Name, + Type: orderType, + Pair: orderPair, + Status: orderStatus, }, nil } diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 7ca254cd86d..c54cf64f24f 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -376,13 +376,12 @@ 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 { - if s.ImmediateOrCancel { - return immediateOrCancel - } - if s.FillOrKill { - return fillOrKill + switch s.TimeInForce { + case order.IOC, order.FOK: + return s.TimeInForce.String() + default: + return "" // GTC (good till cancelled, default value) } - return "" // GTC (good till cancelled, default value) } // NewOrder requests a new order and returns an ID diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index 5820acf5cd4..288a60bb659 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -987,12 +987,12 @@ func TestGetTimeInForce(t *testing.T) { t.Fatal("unexpected value") } - f = b.getTimeInForce(&order.Submit{ImmediateOrCancel: true}) + 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{FillOrKill: true}) + f = b.getTimeInForce(&order.Submit{TimeInForce: order.FOK}) if f != fillOrKill { t.Fatalf("received: '%v' but expected: '%v'", f, fillOrKill) } diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index e7667fba793..06dabc4739a 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -334,7 +334,7 @@ func (c *CoinbasePro) GetHolds(ctx context.Context, accountID string) ([]Account // timeInforce - [optional] GTC, GTT, IOC, or FOK (default is GTC) // cancelAfter - [optional] min, hour, day * Requires time_in_force to be GTT // postOnly - [optional] Post only flag Invalid when time_in_force is IOC or FOK -func (c *CoinbasePro) PlaceLimitOrder(ctx context.Context, clientRef string, price, amount float64, side string, timeInforce RequestParamsTimeForceType, cancelAfter, productID, stp string, postOnly bool) (string, error) { +func (c *CoinbasePro) PlaceLimitOrder(ctx context.Context, clientRef string, price, amount float64, side, timeInforce string, cancelAfter, productID, stp string, postOnly bool) (string, error) { resp := GeneralizedOrderResponse{} req := make(map[string]interface{}) req["type"] = order.Limit.Lower() diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 4e76dfa75ec..c6ca2d84ba8 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -563,9 +563,9 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. fPair.String(), "") case order.Limit: - timeInForce := CoinbaseRequestParamsTimeGTC - if s.ImmediateOrCancel { - timeInForce = CoinbaseRequestParamsTimeIOC + timeInForce := order.GoodTillCancel.String() + if s.TimeInForce == order.IOC { + timeInForce = order.IOC.String() } orderID, err = c.PlaceLimitOrder(ctx, "", diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 3e818d0c65f..5670041bed8 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -1058,7 +1058,7 @@ 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.ImmediateOrCancel { + if s.TimeInForce == order.IOC { oType = "optimal_20_ioc" } case order.Limit: diff --git a/exchanges/kraken/kraken_types.go b/exchanges/kraken/kraken_types.go index 2f833479ff2..1885748e33c 100644 --- a/exchanges/kraken/kraken_types.go +++ b/exchanges/kraken/kraken_types.go @@ -427,7 +427,7 @@ type AddOrderOptions struct { ClosePrice float64 ClosePrice2 float64 Validate bool - TimeInForce RequestParamsTimeForceType + TimeInForce string } // CancelOrderResponse type @@ -675,25 +675,25 @@ type WsOpenOrderDescription struct { // WsAddOrderRequest request type for ws adding order type WsAddOrderRequest struct { - Event string `json:"event"` - Token string `json:"token"` - RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message. - OrderType string `json:"ordertype"` - OrderSide string `json:"type"` - Pair string `json:"pair"` - Price float64 `json:"price,string,omitempty"` // optional - Price2 float64 `json:"price2,string,omitempty"` // optional - Volume float64 `json:"volume,string,omitempty"` - Leverage float64 `json:"leverage,omitempty"` // optional - OFlags string `json:"oflags,omitempty"` // optional - StartTime string `json:"starttm,omitempty"` // optional - ExpireTime string `json:"expiretm,omitempty"` // optional - UserReferenceID string `json:"userref,omitempty"` // optional - Validate string `json:"validate,omitempty"` // optional - CloseOrderType string `json:"close[ordertype],omitempty"` // optional - ClosePrice float64 `json:"close[price],omitempty"` // optional - ClosePrice2 float64 `json:"close[price2],omitempty"` // optional - TimeInForce RequestParamsTimeForceType `json:"timeinforce,omitempty"` // optional + Event string `json:"event"` + Token string `json:"token"` + RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message. + OrderType string `json:"ordertype"` + OrderSide string `json:"type"` + Pair string `json:"pair"` + Price float64 `json:"price,string,omitempty"` // optional + Price2 float64 `json:"price2,string,omitempty"` // optional + Volume float64 `json:"volume,string,omitempty"` + Leverage float64 `json:"leverage,omitempty"` // optional + OFlags string `json:"oflags,omitempty"` // optional + StartTime string `json:"starttm,omitempty"` // optional + ExpireTime string `json:"expiretm,omitempty"` // optional + UserReferenceID string `json:"userref,omitempty"` // optional + Validate string `json:"validate,omitempty"` // optional + CloseOrderType string `json:"close[ordertype],omitempty"` // optional + ClosePrice float64 `json:"close[price],omitempty"` // optional + ClosePrice2 float64 `json:"close[price2],omitempty"` // optional + TimeInForce string `json:"timeinforce,omitempty"` // optional } // WsAddOrderResponse response data for ws order @@ -730,13 +730,3 @@ type OrderVars struct { OrderType order.Type Fee float64 } - -// RequestParamsTimeForceType Time in force -type RequestParamsTimeForceType string - -var ( - // RequestParamsTimeGTC GTC - RequestParamsTimeGTC = RequestParamsTimeForceType("GTC") - // RequestParamsTimeIOC IOC - RequestParamsTimeIOC = RequestParamsTimeForceType("IOC") -) diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 6e1fc8efa38..99f4bdfd9ba 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -819,9 +819,9 @@ func (k *Kraken) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi status := order.New switch s.AssetType { case asset.Spot: - timeInForce := RequestParamsTimeGTC - if s.ImmediateOrCancel { - timeInForce = RequestParamsTimeIOC + timeInForce := order.GoodTillCancel.String() + if s.TimeInForce == order.IOC { + timeInForce = s.TimeInForce.String() } if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() { orderID, err = k.wsAddOrder(&WsAddOrderRequest{ @@ -867,7 +867,7 @@ func (k *Kraken) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi "", s.ClientOrderID, "", - s.ImmediateOrCancel, + s.TimeInForce == order.IOC, s.Amount, s.Price, 0, diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index c533e272330..78a96745786 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -62,15 +62,14 @@ func TestSubmit_Validate(t *testing.T) { }, }, // valid pair but invalid order side { - ExpectedErr: errTimeInForceConflict, + ExpectedErr: errInvalidTimeInForce, Submit: &Submit{ - Exchange: "test", - Pair: testPair, - AssetType: asset.Spot, - Side: Ask, - Type: Market, - ImmediateOrCancel: true, - FillOrKill: true, + Exchange: "test", + Pair: testPair, + AssetType: asset.Spot, + Side: Ask, + Type: Market, + TimeInForce: TimeInForce(89), }, }, { @@ -998,24 +997,24 @@ func TestUpdateOrderFromModifyResponse(t *testing.T) { } om := ModifyResponse{ - ImmediateOrCancel: true, - PostOnly: true, - Price: 1, - Amount: 1, - TriggerPrice: 1, - RemainingAmount: 1, - Exchange: "1", - Type: 1, - Side: 1, - Status: 1, - AssetType: 1, - LastUpdated: updated, - Pair: pair, + TimeInForce: IOC, + PostOnly: true, + Price: 1, + Amount: 1, + TriggerPrice: 1, + RemainingAmount: 1, + Exchange: "1", + Type: 1, + Side: 1, + Status: 1, + AssetType: 1, + LastUpdated: updated, + Pair: pair, } od.UpdateOrderFromModifyResponse(&om) - if !od.ImmediateOrCancel { + if od.TimeInForce == UnknownTIF { t.Error("Failed to update") } if !od.PostOnly { @@ -1084,34 +1083,33 @@ func TestUpdateOrderFromDetail(t *testing.T) { } om := &Detail{ - ImmediateOrCancel: true, - HiddenOrder: true, - FillOrKill: true, - PostOnly: true, - Leverage: 1, - Price: 1, - Amount: 1, - LimitPriceUpper: 1, - LimitPriceLower: 1, - TriggerPrice: 1, - QuoteAmount: 1, - ExecutedAmount: 1, - RemainingAmount: 1, - Fee: 1, - Exchange: "1", - InternalOrderID: id, - OrderID: "1", - AccountID: "1", - ClientID: "1", - ClientOrderID: "DukeOfWombleton", - WalletAddress: "1", - Type: 1, - Side: 1, - Status: 1, - AssetType: 1, - LastUpdated: updated, - Pair: pair, - Trades: []TradeHistory{}, + TimeInForce: GoodTillCancel, + HiddenOrder: true, + PostOnly: true, + Leverage: 1, + Price: 1, + Amount: 1, + LimitPriceUpper: 1, + LimitPriceLower: 1, + TriggerPrice: 1, + QuoteAmount: 1, + ExecutedAmount: 1, + RemainingAmount: 1, + Fee: 1, + Exchange: "1", + InternalOrderID: id, + OrderID: "1", + AccountID: "1", + ClientID: "1", + ClientOrderID: "DukeOfWombleton", + WalletAddress: "1", + Type: 1, + Side: 1, + Status: 1, + AssetType: 1, + LastUpdated: updated, + Pair: pair, + Trades: []TradeHistory{}, } od = &Detail{Exchange: "test"} @@ -1128,15 +1126,12 @@ func TestUpdateOrderFromDetail(t *testing.T) { if od.InternalOrderID != id { t.Error("Failed to initialize the internal order ID") } - if !od.ImmediateOrCancel { + if od.TimeInForce != GoodTillCancel { t.Error("Failed to update") } if !od.HiddenOrder { t.Error("Failed to update") } - if !od.FillOrKill { - t.Error("Failed to update") - } if !od.PostOnly { t.Error("Failed to update") } @@ -2062,3 +2057,27 @@ func TestSideUnmarshal(t *testing.T) { var jErr *json.UnmarshalTypeError assert.ErrorAs(t, s.UnmarshalJSON([]byte(`14`)), &jErr, "non-string valid json is rejected") } + +func TestSupported(t *testing.T) { + t.Parallel() + s := Supported() + if len(supportedTIFItems) != len(s) { + t.Fatal("TestSupported mismatched lengths") + } + for i := 0; i < len(supportedTIFItems); i++ { + if s[i] != supportedTIFItems[i] { + t.Fatal("TestSupported returned an unexpected result") + } + } +} + +func TestIsValid(t *testing.T) { + t.Parallel() + if TimeInForce(50).IsValid() { + t.Fatal("TestIsValid returned an unexpected result") + } + + if !GoodTillCancel.IsValid() { + t.Fatal("TestIsValid returned an unexpected result") + } +} diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index b6136b5b1e1..050e1beda6c 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -44,9 +44,8 @@ type Submit struct { Pair currency.Pair AssetType asset.Item - // Time in force values ------ TODO: Time In Force uint8 - ImmediateOrCancel bool - FillOrKill bool + // TimeInForce holds time in force values + TimeInForce TimeInForce PostOnly bool // ReduceOnly reduces a position instead of opening an opposing @@ -96,17 +95,16 @@ type SubmitResponse struct { Pair currency.Pair AssetType asset.Item - ImmediateOrCancel bool - FillOrKill bool - PostOnly bool - ReduceOnly bool - Leverage float64 - Price float64 - Amount float64 - QuoteAmount float64 - TriggerPrice float64 - ClientID string - ClientOrderID string + TimeInForce TimeInForce + PostOnly bool + ReduceOnly bool + Leverage float64 + Price float64 + Amount float64 + QuoteAmount float64 + TriggerPrice float64 + ClientID string + ClientOrderID string LastUpdated time.Time Date time.Time @@ -137,11 +135,11 @@ type Modify struct { Pair currency.Pair // Change fields - ImmediateOrCancel bool - PostOnly bool - Price float64 - Amount float64 - TriggerPrice float64 + TimeInForce TimeInForce + PostOnly bool + Price float64 + Amount float64 + TriggerPrice float64 } // ModifyResponse is an order modifying return type @@ -157,11 +155,11 @@ type ModifyResponse struct { AssetType asset.Item // Fields that will be copied over from Modify - ImmediateOrCancel bool - PostOnly bool - Price float64 - Amount float64 - TriggerPrice float64 + TimeInForce TimeInForce + PostOnly bool + Price float64 + Amount float64 + TriggerPrice float64 // Fields that need to be handled in scope after DeriveModifyResponse() // if applicable @@ -174,9 +172,8 @@ type ModifyResponse struct { // Each exchange has their own requirements, so not all fields // are required to be populated type Detail struct { - ImmediateOrCancel bool HiddenOrder bool - FillOrKill bool + TimeInForce TimeInForce PostOnly bool ReduceOnly bool Leverage float64 @@ -371,6 +368,23 @@ const ( MissingData ) +// TimeInForce enforces a standard for time-in-force values accross the code base. +type TimeInForce uint8 + +const ( + UnknownTIF TimeInForce = 0 + GoodTillCancel TimeInForce = iota + GoodTillTime + FOK // FOK represents FillOrKill, used shorter version as the FillOrKill name is reserved for order type value. + IOC // IOC represents ImmediateOrCancel, used shorter version as the ImmediateOrCancel name is reserved for order type value. + + supportedTimeInForceFlag = UnknownTIF | GoodTillCancel | GoodTillTime | FOK | IOC +) + +var ( + supportedTIFItems = []TimeInForce{GoodTillCancel, GoodTillTime, FOK, IOC} +) + // ByPrice used for sorting orders by price type ByPrice []Detail diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 98400f66028..be81dfaf3af 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -37,7 +37,7 @@ var ( // ErrOrderNotFound is returned when no order is found ErrOrderNotFound = errors.New("order not found") - errTimeInForceConflict = errors.New("multiple time in force options applied") + errInvalidTimeInForce = errors.New("invalid time in force value provided") errUnrecognisedOrderType = errors.New("unrecognised order type") errUnrecognisedOrderStatus = errors.New("unrecognised order status") errExchangeNameUnset = errors.New("exchange name unset") @@ -82,8 +82,8 @@ func (s *Submit) Validate(opt ...validate.Checker) error { return ErrTypeIsInvalid } - if s.ImmediateOrCancel && s.FillOrKill { - return errTimeInForceConflict + if !s.TimeInForce.IsValid() { + return errInvalidTimeInForce } if s.Amount == 0 && s.QuoteAmount == 0 { @@ -124,18 +124,14 @@ func (d *Detail) UpdateOrderFromDetail(m *Detail) error { } var updated bool - if d.ImmediateOrCancel != m.ImmediateOrCancel { - d.ImmediateOrCancel = m.ImmediateOrCancel + if d.TimeInForce != m.TimeInForce { + d.TimeInForce = m.TimeInForce updated = true } if d.HiddenOrder != m.HiddenOrder { d.HiddenOrder = m.HiddenOrder updated = true } - if d.FillOrKill != m.FillOrKill { - d.FillOrKill = m.FillOrKill - updated = true - } if m.Price > 0 && m.Price != d.Price { d.Price = m.Price updated = true @@ -291,8 +287,8 @@ func (d *Detail) UpdateOrderFromModifyResponse(m *ModifyResponse) { d.OrderID = m.OrderID updated = true } - if d.ImmediateOrCancel != m.ImmediateOrCancel { - d.ImmediateOrCancel = m.ImmediateOrCancel + if d.TimeInForce != m.TimeInForce && m.TimeInForce != UnknownTIF { + d.TimeInForce = m.TimeInForce updated = true } if m.Price > 0 && m.Price != d.Price { @@ -467,18 +463,17 @@ func (s *Submit) DeriveSubmitResponse(orderID string) (*SubmitResponse, error) { Pair: s.Pair, AssetType: s.AssetType, - ImmediateOrCancel: s.ImmediateOrCancel, - FillOrKill: s.FillOrKill, - PostOnly: s.PostOnly, - ReduceOnly: s.ReduceOnly, - Leverage: s.Leverage, - Price: s.Price, - Amount: s.Amount, - QuoteAmount: s.QuoteAmount, - TriggerPrice: s.TriggerPrice, - ClientID: s.ClientID, - ClientOrderID: s.ClientOrderID, - MarginType: s.MarginType, + TimeInForce: s.TimeInForce, + PostOnly: s.PostOnly, + ReduceOnly: s.ReduceOnly, + Leverage: s.Leverage, + Price: s.Price, + Amount: s.Amount, + QuoteAmount: s.QuoteAmount, + TriggerPrice: s.TriggerPrice, + ClientID: s.ClientID, + ClientOrderID: s.ClientOrderID, + MarginType: s.MarginType, LastUpdated: time.Now(), Date: time.Now(), @@ -560,17 +555,16 @@ func (s *SubmitResponse) DeriveDetail(internal uuid.UUID) (*Detail, error) { Pair: s.Pair, AssetType: s.AssetType, - ImmediateOrCancel: s.ImmediateOrCancel, - FillOrKill: s.FillOrKill, - PostOnly: s.PostOnly, - ReduceOnly: s.ReduceOnly, - Leverage: s.Leverage, - Price: s.Price, - Amount: s.Amount, - QuoteAmount: s.QuoteAmount, - TriggerPrice: s.TriggerPrice, - ClientID: s.ClientID, - ClientOrderID: s.ClientOrderID, + TimeInForce: s.TimeInForce, + PostOnly: s.PostOnly, + ReduceOnly: s.ReduceOnly, + Leverage: s.Leverage, + Price: s.Price, + Amount: s.Amount, + QuoteAmount: s.QuoteAmount, + TriggerPrice: s.TriggerPrice, + ClientID: s.ClientID, + ClientOrderID: s.ClientOrderID, InternalOrderID: internal, @@ -620,18 +614,18 @@ func (m *Modify) DeriveModifyResponse() (*ModifyResponse, error) { return nil, errOrderDetailIsNil } return &ModifyResponse{ - Exchange: m.Exchange, - OrderID: m.OrderID, - ClientOrderID: m.ClientOrderID, - Type: m.Type, - Side: m.Side, - AssetType: m.AssetType, - Pair: m.Pair, - ImmediateOrCancel: m.ImmediateOrCancel, - PostOnly: m.PostOnly, - Price: m.Price, - Amount: m.Amount, - TriggerPrice: m.TriggerPrice, + Exchange: m.Exchange, + OrderID: m.OrderID, + ClientOrderID: m.ClientOrderID, + Type: m.Type, + Side: m.Side, + AssetType: m.AssetType, + Pair: m.Pair, + TimeInForce: m.TimeInForce, + PostOnly: m.PostOnly, + Price: m.Price, + Amount: m.Amount, + TriggerPrice: m.TriggerPrice, }, nil } @@ -698,6 +692,22 @@ func (t Type) String() string { } } +// String implements the stringer interface. +func (t TimeInForce) String() string { + switch t { + case IOC: + return "IOC" + case GoodTillCancel: + return "GTC" + case GoodTillTime: + return "GTT" + case FOK: + return "FOK" + default: + return "UNKNOWN" + } +} + // Lower returns the type lower case string func (t Type) Lower() string { return strings.ToLower(t.String()) @@ -1159,6 +1169,34 @@ func StringToOrderStatus(status string) (Status, error) { } } +// StringToTimeInForce converts time in force string value to TimeInForce instance. +func StringToTimeInForce(timeInForce string) (TimeInForce, error) { + timeInForce = strings.ToUpper(timeInForce) + switch timeInForce { + case "IOC", "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL": + return IOC, nil + case "GTC", "GOODTILLCANCEL", "GOOD_TIL_CANCELLED": + return GoodTillCancel, nil + case "GTT", "GOODTILLTIME": + return GoodTillTime, nil + case "FOK", "FILLORKILL", "FILL_OR_KILL": + return FOK, nil + default: + return UnknownTIF, errInvalidTimeInForce + } +} + +// Supported returns a list of supported time in force types +func Supported() []TimeInForce { + return supportedTIFItems +} + +// IsValid returns whether or not the supplied time in force value is valid or +// not +func (a TimeInForce) IsValid() bool { + return a == UnknownTIF || supportedTimeInForceFlag&a == a +} + func (o *ClassificationError) Error() string { if o.OrderID != "" { return fmt.Sprintf("Exchange %s: OrderID: %s classification error: %v", diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index ee0efc0c139..6d80746fd47 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -657,7 +657,7 @@ func (p *Poloniex) ModifyOrder(ctx context.Context, action *order.Modify) (*orde action.Price, action.Amount, action.PostOnly, - action.ImmediateOrCancel) + action.TimeInForce == order.IOC) if err != nil { return nil, err } From 0bbd6fbc7ee051e1bcf90c2a831639f8ac8d5f1d Mon Sep 17 00:00:00 2001 From: Samuael A <39623015+samuael@users.noreply.github.com> Date: Sun, 29 Oct 2023 21:39:18 +0300 Subject: [PATCH 02/20] Linter issue fix and minor coinbasepro type update --- exchanges/binance/binance.go | 2 +- exchanges/coinbasepro/coinbasepro.go | 2 +- exchanges/coinbasepro/coinbasepro_test.go | 4 ++-- exchanges/coinbasepro/coinbasepro_types.go | 11 ----------- exchanges/coinbasepro/coinbasepro_wrapper.go | 4 ++-- exchanges/kraken/kraken.go | 2 +- exchanges/order/order_types.go | 3 ++- exchanges/order/orders.go | 4 ++-- 8 files changed, 11 insertions(+), 21 deletions(-) diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index d3a40cd80bf..ec65fa9abb3 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -629,7 +629,7 @@ func (b *Binance) newOrder(ctx context.Context, api string, o *NewOrderRequest, params.Set("price", strconv.FormatFloat(o.Price, 'f', -1, 64)) } if o.TimeInForce != "" { - params.Set("timeInForce", string(o.TimeInForce)) + params.Set("timeInForce", o.TimeInForce) } if o.NewClientOrderID != "" { diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 06dabc4739a..6ae5e1b04b5 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -334,7 +334,7 @@ func (c *CoinbasePro) GetHolds(ctx context.Context, accountID string) ([]Account // timeInforce - [optional] GTC, GTT, IOC, or FOK (default is GTC) // cancelAfter - [optional] min, hour, day * Requires time_in_force to be GTT // postOnly - [optional] Post only flag Invalid when time_in_force is IOC or FOK -func (c *CoinbasePro) PlaceLimitOrder(ctx context.Context, clientRef string, price, amount float64, side, timeInforce string, cancelAfter, productID, stp string, postOnly bool) (string, error) { +func (c *CoinbasePro) PlaceLimitOrder(ctx context.Context, clientRef, side, timeInforce, cancelAfter, productID, stp string, price, amount float64, postOnly bool) (string, error) { resp := GeneralizedOrderResponse{} req := make(map[string]interface{}) req["type"] = order.Limit.Lower() diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index af5c2ab37ef..ddb17686ce1 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -196,8 +196,8 @@ func TestAuthRequests(t *testing.T) { t.Error("Expecting error") } orderResponse, err := c.PlaceLimitOrder(context.Background(), - "", 0.001, 0.001, - order.Buy.Lower(), "", "", testPair.String(), "", false) + "", + order.Buy.Lower(), "", "", testPair.String(), "", 0.001, 0.001, false) if orderResponse != "" { t.Error("Expecting no data returned") } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 2a8765edba7..cb7d44629ec 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -489,17 +489,6 @@ type wsStatus struct { Type string `json:"type"` } -// RequestParamsTimeForceType Time in force -type RequestParamsTimeForceType string - -var ( - // CoinbaseRequestParamsTimeGTC GTC - CoinbaseRequestParamsTimeGTC = RequestParamsTimeForceType("GTC") - - // CoinbaseRequestParamsTimeIOC IOC - CoinbaseRequestParamsTimeIOC = RequestParamsTimeForceType("IOC") -) - // TransferHistory returns wallet transfer history type TransferHistory struct { ID string `json:"id"` diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index c6ca2d84ba8..1af57467eb7 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -569,13 +569,13 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. } orderID, err = c.PlaceLimitOrder(ctx, "", - s.Price, - s.Amount, s.Side.Lower(), timeInForce, "", fPair.String(), "", + s.Price, + s.Amount, false) default: err = fmt.Errorf("%w %v", order.ErrUnsupportedOrderType, s.Type) diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 1fc2318aa50..54b22d69550 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -920,7 +920,7 @@ func (k *Kraken) AddOrder(ctx context.Context, symbol currency.Pair, side, order } if args.TimeInForce != "" { - params.Set("timeinforce", string(args.TimeInForce)) + params.Set("timeinforce", args.TimeInForce) } if err := k.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, krakenOrderPlace, params, &response); err != nil { diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 050e1beda6c..708db7cef6d 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -368,9 +368,10 @@ const ( MissingData ) -// TimeInForce enforces a standard for time-in-force values accross the code base. +// TimeInForce enforces a standard for time-in-force values across the code base. type TimeInForce uint8 +// TimeInForce types const ( UnknownTIF TimeInForce = 0 GoodTillCancel TimeInForce = iota diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index be81dfaf3af..6a02cc76ec0 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -1193,8 +1193,8 @@ func Supported() []TimeInForce { // IsValid returns whether or not the supplied time in force value is valid or // not -func (a TimeInForce) IsValid() bool { - return a == UnknownTIF || supportedTimeInForceFlag&a == a +func (t TimeInForce) IsValid() bool { + return t == UnknownTIF || supportedTimeInForceFlag&t == t } func (o *ClassificationError) Error() string { From b786e75dd7787c4fbff6c727ed66ee65b91702ab Mon Sep 17 00:00:00 2001 From: Samuael A <39623015+samuael@users.noreply.github.com> Date: Sun, 29 Oct 2023 22:04:33 +0300 Subject: [PATCH 03/20] Bitrex consts update --- exchanges/bittrex/bittrex_types.go | 10 +++++----- exchanges/bittrex/bittrex_websocket.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/exchanges/bittrex/bittrex_types.go b/exchanges/bittrex/bittrex_types.go index da217efdcea..73e8349733f 100644 --- a/exchanges/bittrex/bittrex_types.go +++ b/exchanges/bittrex/bittrex_types.go @@ -19,11 +19,11 @@ type TimeInForce string // All order status types const ( - GoodTilCancelled TimeInForce = "GOOD_TIL_CANCELLED" - ImmediateOrCancel TimeInForce = "IMMEDIATE_OR_CANCEL" - FillOrKill TimeInForce = "FILL_OR_KILL" - PostOnlyGoodTilCancelled TimeInForce = "POST_ONLY_GOOD_TIL_CANCELLED" - BuyNow TimeInForce = "BUY_NOW" + GoodTilCancelled = "GOOD_TIL_CANCELLED" + ImmediateOrCancel = "IMMEDIATE_OR_CANCEL" + FillOrKill = "FILL_OR_KILL" + PostOnlyGoodTilCancelled = "POST_ONLY_GOOD_TIL_CANCELLED" + BuyNow = "BUY_NOW" ) // OrderData holds order data diff --git a/exchanges/bittrex/bittrex_websocket.go b/exchanges/bittrex/bittrex_websocket.go index 4cf480ff91f..a62cfd139d4 100644 --- a/exchanges/bittrex/bittrex_websocket.go +++ b/exchanges/bittrex/bittrex_websocket.go @@ -612,7 +612,7 @@ func (b *Bittrex) WsProcessUpdateOrder(data *OrderUpdateMessage) error { } b.Websocket.DataHandler <- &order.Detail{ TimeInForce: timeInForce, - PostOnly: data.Delta.TimeInForce == string(PostOnlyGoodTilCancelled), + PostOnly: data.Delta.TimeInForce == PostOnlyGoodTilCancelled, Price: data.Delta.Limit, Amount: data.Delta.Quantity, RemainingAmount: data.Delta.Quantity - data.Delta.FillQuantity, From afa9695340bbb12655a74bbe5a94fd502c712131 Mon Sep 17 00:00:00 2001 From: Samuael A <39623015+samuael@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:28:03 +0300 Subject: [PATCH 04/20] added unit test and minor changes in bittrex --- exchanges/binance/binance_test.go | 2 +- exchanges/binance/binance_wrapper.go | 2 +- exchanges/bittrex/bittrex.go | 4 +- exchanges/bittrex/bittrex_types.go | 16 ++------ exchanges/bittrex/bittrex_websocket.go | 4 +- exchanges/bittrex/bittrex_wrapper.go | 37 +++++++++++++++--- exchanges/coinbasepro/coinbasepro_wrapper.go | 2 +- exchanges/kraken/kraken_wrapper.go | 2 +- exchanges/order/order_test.go | 41 ++++++++++++-------- exchanges/order/order_types.go | 17 ++++---- exchanges/order/orders.go | 29 +++++++------- 11 files changed, 92 insertions(+), 64 deletions(-) diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index c6e92fb995c..4f475f3ad82 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -1443,7 +1443,7 @@ func TestNewOrderTest(t *testing.T) { TradeType: BinanceRequestParamsOrderLimit, Price: 0.0025, Quantity: 100000, - TimeInForce: order.GoodTillCancel.String(), + TimeInForce: order.GTC.String(), } err := b.NewOrderTest(context.Background(), req) diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 6168fcefa9b..30b70414474 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -990,7 +990,7 @@ func (b *Binance) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm } else { sideType = order.Sell.String() } - timeInForce := order.GoodTillCancel.String() + timeInForce := order.GTC.String() var requestParamsOrderType RequestParamsOrderType switch s.Type { case order.Market: diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index 3cc0f93c6b1..12fec172259 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -136,7 +136,7 @@ func (b *Bittrex) GetMarketHistory(ctx context.Context, currency string) ([]Trad } // Order places an order -func (b *Bittrex) Order(ctx context.Context, marketName, side, orderType string, timeInForce TimeInForce, price, amount, ceiling float64) (OrderData, error) { +func (b *Bittrex) Order(ctx context.Context, marketName, side, orderType, timeInForce string, price, amount, ceiling float64) (OrderData, error) { req := make(map[string]interface{}) req["marketSymbol"] = marketName req["direction"] = side @@ -151,7 +151,7 @@ func (b *Bittrex) Order(ctx context.Context, marketName, side, orderType string, if timeInForce != "" { req["timeInForce"] = timeInForce } else { - req["timeInForce"] = GoodTilCancelled + req["timeInForce"] = goodTilCancelled } var resp OrderData return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, submitOrder, nil, req, &resp, nil) diff --git a/exchanges/bittrex/bittrex_types.go b/exchanges/bittrex/bittrex_types.go index 73e8349733f..40e3a085d60 100644 --- a/exchanges/bittrex/bittrex_types.go +++ b/exchanges/bittrex/bittrex_types.go @@ -9,23 +9,15 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/stream" ) +const ( + goodTilCancelled = "GOOD_TIL_CANCELLED" +) + // CancelOrderRequest holds request data for CancelOrder type CancelOrderRequest struct { OrderID int64 `json:"orderId,string"` } -// TimeInForce defines timeInForce types -type TimeInForce string - -// All order status types -const ( - GoodTilCancelled = "GOOD_TIL_CANCELLED" - ImmediateOrCancel = "IMMEDIATE_OR_CANCEL" - FillOrKill = "FILL_OR_KILL" - PostOnlyGoodTilCancelled = "POST_ONLY_GOOD_TIL_CANCELLED" - BuyNow = "BUY_NOW" -) - // OrderData holds order data type OrderData struct { ID string `json:"id"` diff --git a/exchanges/bittrex/bittrex_websocket.go b/exchanges/bittrex/bittrex_websocket.go index a62cfd139d4..b0cb9f0bcad 100644 --- a/exchanges/bittrex/bittrex_websocket.go +++ b/exchanges/bittrex/bittrex_websocket.go @@ -602,7 +602,7 @@ func (b *Bittrex) WsProcessUpdateOrder(data *OrderUpdateMessage) error { Err: err, } } - timeInForce, err := order.StringToTimeInForce(data.Delta.TimeInForce) + timeInForce, err := timeInForceFromString(data.Delta.TimeInForce) if err != nil { b.Websocket.DataHandler <- order.ClassificationError{ Exchange: b.Name, @@ -612,7 +612,7 @@ func (b *Bittrex) WsProcessUpdateOrder(data *OrderUpdateMessage) error { } b.Websocket.DataHandler <- &order.Detail{ TimeInForce: timeInForce, - PostOnly: data.Delta.TimeInForce == PostOnlyGoodTilCancelled, + PostOnly: data.Delta.TimeInForce == order.PostOnlyGTC.String(), Price: data.Delta.Limit, Amount: data.Delta.Quantity, RemainingAmount: data.Delta.Quantity - data.Delta.FillQuantity, diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 0d5b303083e..2e62854e2cd 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -627,7 +627,7 @@ func (b *Bittrex) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm formattedPair.String(), s.Side.String(), s.Type.String(), - GoodTilCancelled, + timeInForceToString(s.TimeInForce), s.Price, s.Amount, 0.0) @@ -698,10 +698,7 @@ func (b *Bittrex) GetOrderInfo(ctx context.Context, orderID string, _ currency.P // ConstructOrderDetail constructs an order detail item from the underlying data func (b *Bittrex) ConstructOrderDetail(orderData *OrderData) (*order.Detail, error) { - timeInForce, err := order.StringToTimeInForce(orderData.TimeInForce) - if err != nil { - timeInForce = order.UnknownTIF - } + timeInForce, _ := timeInForceFromString(orderData.TimeInForce) format, err := b.GetPairFormat(asset.Spot, false) if err != nil { @@ -1095,3 +1092,33 @@ func (b *Bittrex) GetHistoricCandlesExtended(_ context.Context, _ currency.Pair, func (b *Bittrex) GetFuturesContractDetails(context.Context, asset.Item) ([]futures.Contract, error) { return nil, common.ErrFunctionNotSupported } + +func timeInForceFromString(s string) (order.TimeInForce, error) { + switch s { + case "GOOD_TIL_CANCELLED": + return order.GTC, nil + case "IMMEDIATE_OR_CANCEL": + return order.IOC, nil + case "FILL_OR_KILL": + return order.FOK, nil + case "POST_ONLY_GOOD_TIL_CANCELLED": + return order.PostOnlyGTC, nil + default: + return order.UnknownTIF, order.ErrInvalidTimeInForce + } +} + +// timeInForceToString returns string given TimeInForce instance +func timeInForceToString(t order.TimeInForce) string { + switch t { + case order.IOC: + return "IMMEDIATE_OR_CANCEL" + case order.FOK: + return "FILL_OR_KILL" + case order.PostOnlyGTC: + return order.PostOnlyGTC.String() + default: + // The exchange Uses GTC as a default TimeInForce value + return goodTilCancelled + } +} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 1af57467eb7..708baa00247 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -563,7 +563,7 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. fPair.String(), "") case order.Limit: - timeInForce := order.GoodTillCancel.String() + timeInForce := order.GTC.String() if s.TimeInForce == order.IOC { timeInForce = order.IOC.String() } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 99f4bdfd9ba..fa710d1566d 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -819,7 +819,7 @@ func (k *Kraken) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi status := order.New switch s.AssetType { case asset.Spot: - timeInForce := order.GoodTillCancel.String() + timeInForce := order.GTC.String() if s.TimeInForce == order.IOC { timeInForce = s.TimeInForce.String() } diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 78a96745786..e20dd3744d8 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -62,7 +62,7 @@ func TestSubmit_Validate(t *testing.T) { }, }, // valid pair but invalid order side { - ExpectedErr: errInvalidTimeInForce, + ExpectedErr: ErrInvalidTimeInForce, Submit: &Submit{ Exchange: "test", Pair: testPair, @@ -1083,7 +1083,7 @@ func TestUpdateOrderFromDetail(t *testing.T) { } om := &Detail{ - TimeInForce: GoodTillCancel, + TimeInForce: GTC, HiddenOrder: true, PostOnly: true, Leverage: 1, @@ -1126,7 +1126,7 @@ func TestUpdateOrderFromDetail(t *testing.T) { if od.InternalOrderID != id { t.Error("Failed to initialize the internal order ID") } - if od.TimeInForce != GoodTillCancel { + if od.TimeInForce != GTC { t.Error("Failed to update") } if !od.HiddenOrder { @@ -2058,26 +2058,33 @@ func TestSideUnmarshal(t *testing.T) { assert.ErrorAs(t, s.UnmarshalJSON([]byte(`14`)), &jErr, "non-string valid json is rejected") } -func TestSupported(t *testing.T) { - t.Parallel() - s := Supported() - if len(supportedTIFItems) != len(s) { - t.Fatal("TestSupported mismatched lengths") - } - for i := 0; i < len(supportedTIFItems); i++ { - if s[i] != supportedTIFItems[i] { - t.Fatal("TestSupported returned an unexpected result") - } - } -} - func TestIsValid(t *testing.T) { t.Parallel() if TimeInForce(50).IsValid() { t.Fatal("TestIsValid returned an unexpected result") } - if !GoodTillCancel.IsValid() { + if !GTC.IsValid() { t.Fatal("TestIsValid returned an unexpected result") } } + +func TestStringToTimeInForce(t *testing.T) { + t.Parallel() + _, err := StringToTimeInForce("Unknown") + if !errors.Is(err, ErrInvalidTimeInForce) { + t.Fatalf("expected %v, got %v", ErrInvalidTimeInForce, err) + } + _, err = StringToTimeInForce("GoodTillCancel") + if err != nil { + t.Fatal(err) + } + _, err = StringToTimeInForce("") + if !errors.Is(err, ErrInvalidTimeInForce) { + t.Fatalf("expected %v, got %v", ErrInvalidTimeInForce, err) + } + _, err = StringToTimeInForce("IOC") + if err != nil { + t.Fatal(err) + } +} diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 708db7cef6d..4c873d6c747 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -373,17 +373,18 @@ type TimeInForce uint8 // TimeInForce types const ( - UnknownTIF TimeInForce = 0 - GoodTillCancel TimeInForce = iota - GoodTillTime - FOK // FOK represents FillOrKill, used shorter version as the FillOrKill name is reserved for order type value. - IOC // IOC represents ImmediateOrCancel, used shorter version as the ImmediateOrCancel name is reserved for order type value. - - supportedTimeInForceFlag = UnknownTIF | GoodTillCancel | GoodTillTime | FOK | IOC + UnknownTIF TimeInForce = 0 + GTC TimeInForce = iota // GTC represents GoodTillCancel + GTT // GTT represents GoodTillTime + FOK // FOK represents FillOrKill + IOC // IOC represents ImmediateOrCancel + PostOnlyGTC // PostOnlyGCT represents PostOnlyGoodTilCancelled + + supportedTimeInForceFlag = UnknownTIF | GTC | GTT | FOK | IOC ) var ( - supportedTIFItems = []TimeInForce{GoodTillCancel, GoodTillTime, FOK, IOC} + supportedTIFItems = []TimeInForce{GTC, GTT, FOK, IOC} ) // ByPrice used for sorting orders by price diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 6a02cc76ec0..965e360dea5 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -36,8 +36,9 @@ var ( ErrUnableToPlaceOrder = errors.New("order not placed") // ErrOrderNotFound is returned when no order is found ErrOrderNotFound = errors.New("order not found") + // ErrInvalidTimeInForce is returned when an invalid time-in-force value is provided + ErrInvalidTimeInForce = errors.New("invalid time in force value provided") - errInvalidTimeInForce = errors.New("invalid time in force value provided") errUnrecognisedOrderType = errors.New("unrecognised order type") errUnrecognisedOrderStatus = errors.New("unrecognised order status") errExchangeNameUnset = errors.New("exchange name unset") @@ -83,7 +84,7 @@ func (s *Submit) Validate(opt ...validate.Checker) error { } if !s.TimeInForce.IsValid() { - return errInvalidTimeInForce + return ErrInvalidTimeInForce } if s.Amount == 0 && s.QuoteAmount == 0 { @@ -697,12 +698,15 @@ func (t TimeInForce) String() string { switch t { case IOC: return "IOC" - case GoodTillCancel: + case GTC: return "GTC" - case GoodTillTime: + case GTT: return "GTT" case FOK: return "FOK" + case PostOnlyGTC: + // Added in Bittrex exchange to represent PostOnly and GTC + return "POST_ONLY_GOOD_TIL_CANCELLED" default: return "UNKNOWN" } @@ -1175,22 +1179,19 @@ func StringToTimeInForce(timeInForce string) (TimeInForce, error) { switch timeInForce { case "IOC", "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL": return IOC, nil - case "GTC", "GOODTILLCANCEL", "GOOD_TIL_CANCELLED": - return GoodTillCancel, nil - case "GTT", "GOODTILLTIME": - return GoodTillTime, nil + case "GTC", "GOODTILLCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED": + return GTC, nil + case "GTT", "GOODTILLTIME", "GOOD_TIL_TIME": + return GTT, nil case "FOK", "FILLORKILL", "FILL_OR_KILL": return FOK, nil + case "POST_ONLY_GOOD_TIL_CANCELLED", "POST_ONLY_GOOD_TILL_CANCELLED": + return PostOnlyGTC, nil default: - return UnknownTIF, errInvalidTimeInForce + return UnknownTIF, fmt.Errorf("%w, tif=%s", ErrInvalidTimeInForce, timeInForce) } } -// Supported returns a list of supported time in force types -func Supported() []TimeInForce { - return supportedTIFItems -} - // IsValid returns whether or not the supplied time in force value is valid or // not func (t TimeInForce) IsValid() bool { From da4b3cb580a2baf4ffd3d056e9559ed0dabdb280 Mon Sep 17 00:00:00 2001 From: Samuael A <39623015+samuael@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:08:16 +0300 Subject: [PATCH 05/20] Unit tests update --- exchanges/order/order_test.go | 66 +++++++++++++++++++++++++++++++++- exchanges/order/order_types.go | 4 +-- exchanges/order/orders.go | 10 +++--- 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 70e6fde39e3..218e46dc870 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -2064,9 +2064,27 @@ func TestIsValid(t *testing.T) { t.Fatal("TestIsValid returned an unexpected result") } + if !IOC.IsValid() { + t.Fatal("TestIsValid returned an unexpected result") + } + if !GTT.IsValid() { + t.Fatal("TestIsValid returned an unexpected result") + } if !GTC.IsValid() { t.Fatal("TestIsValid returned an unexpected result") } + if !FOK.IsValid() { + t.Fatal("TestIsValid returned an unexpected result") + } + if !PostOnlyGTC.IsValid() { + t.Fatal("TestIsValid returned an unexpected result") + } + if !UnknownTIF.IsValid() { + t.Fatal("TestIsValid returned an unexpected result") + } + if TimeInForce(22).IsValid() { + t.Fatal("TestIsValid returned an unexpected result") + } } func TestStringToTimeInForce(t *testing.T) { @@ -2075,10 +2093,39 @@ func TestStringToTimeInForce(t *testing.T) { if !errors.Is(err, ErrInvalidTimeInForce) { t.Fatalf("expected %v, got %v", ErrInvalidTimeInForce, err) } - _, err = StringToTimeInForce("GoodTillCancel") + tif1, err := StringToTimeInForce("GoodTillCancel") + if err != nil { + t.Fatal(err) + } + + tif, err := StringToTimeInForce("GOOD_TILL_CANCELED") if err != nil { t.Fatal(err) + } else if tif1 != tif { + t.Fatalf("unexpected result") } + + tif, err = StringToTimeInForce("FILLORKILL") + if err != nil { + t.Error(err) + } else if tif != FOK { + t.Fatalf("expected %v, got %v", FOK, tif) + } + + tif, err = StringToTimeInForce("POST_ONLY_GOOD_TIL_CANCELLED") + if err != nil { + t.Fatal(err) + } else if tif != PostOnlyGTC { + t.Fatalf("expected %v, got %v", PostOnlyGTC, tif) + } + + tif, err = StringToTimeInForce("immedIate_Or_Cancel") + if err != nil { + t.Fatal(err) + } else if tif != IOC { + t.Fatalf("expected %v, got %v", IOC, tif) + } + _, err = StringToTimeInForce("") if !errors.Is(err, ErrInvalidTimeInForce) { t.Fatalf("expected %v, got %v", ErrInvalidTimeInForce, err) @@ -2088,3 +2135,20 @@ func TestStringToTimeInForce(t *testing.T) { t.Fatal(err) } } + +func TestString(t *testing.T) { + t.Parallel() + valMap := map[string]TimeInForce{ + "IOC": IOC, + "GTC": GTC, + "GTT": GTT, + "FOK": FOK, + "POST_ONLY_GOOD_TIL_CANCELLED": PostOnlyGTC, + "UNKNOWN": UnknownTIF, + } + for x := range valMap { + if result := valMap[x].String(); x != result { + t.Fatalf("expected %v, got %v", x, result) + } + } +} diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 4c873d6c747..9364f35ad1e 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -380,11 +380,11 @@ const ( IOC // IOC represents ImmediateOrCancel PostOnlyGTC // PostOnlyGCT represents PostOnlyGoodTilCancelled - supportedTimeInForceFlag = UnknownTIF | GTC | GTT | FOK | IOC + supportedTimeInForceFlag = UnknownTIF | GTC | GTT | FOK | IOC | PostOnlyGTC ) var ( - supportedTIFItems = []TimeInForce{GTC, GTT, FOK, IOC} + supportedTIFItems = []TimeInForce{GTC, GTT, FOK, IOC, PostOnlyGTC} ) // ByPrice used for sorting orders by price diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 965e360dea5..717d11020c8 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -1177,15 +1177,15 @@ func StringToOrderStatus(status string) (Status, error) { func StringToTimeInForce(timeInForce string) (TimeInForce, error) { timeInForce = strings.ToUpper(timeInForce) switch timeInForce { - case "IOC", "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL": + case "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL", IOC.String(): return IOC, nil - case "GTC", "GOODTILLCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED": + case "GOODTILLCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED", "GOOD_TILL_CANCELED", GTC.String(): return GTC, nil - case "GTT", "GOODTILLTIME", "GOOD_TIL_TIME": + case "GOODTILLTIME", "GOOD_TIL_TIME", GTT.String(): return GTT, nil - case "FOK", "FILLORKILL", "FILL_OR_KILL": + case "FILLORKILL", "FILL_OR_KILL", FOK.String(): return FOK, nil - case "POST_ONLY_GOOD_TIL_CANCELLED", "POST_ONLY_GOOD_TILL_CANCELLED": + case "POST_ONLY_GOOD_TILL_CANCELLED", PostOnlyGTC.String(): return PostOnlyGTC, nil default: return UnknownTIF, fmt.Errorf("%w, tif=%s", ErrInvalidTimeInForce, timeInForce) From e11beefc3491cd557e1d1a6bce3d065cfcfcd0ea Mon Sep 17 00:00:00 2001 From: Samuael A <39623015+samuael@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:08:44 +0300 Subject: [PATCH 06/20] Fix minor linter issues --- exchanges/kucoin/kucoin_wrapper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index d9919ffee68..a4309fb0e19 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -754,9 +754,9 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm timeInForce := "" if s.Type == order.Limit { switch { - case s.FillOrKill: + case s.TimeInForce == order.FOK: timeInForce = "FOK" - case s.ImmediateOrCancel: + case s.TimeInForce == order.IOC: timeInForce = "IOC" case s.PostOnly: default: From 9add8480eaad5d6de6104a5edfb6272da1f74b9a Mon Sep 17 00:00:00 2001 From: Samuael A <39623015+samuael@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:55:04 +0300 Subject: [PATCH 07/20] Update TestStringToTimeInForce unit test --- exchanges/order/order_test.go | 14 ++++++++++++++ exchanges/order/order_types.go | 4 ---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 218e46dc870..fea56d50735 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -2105,6 +2105,20 @@ func TestStringToTimeInForce(t *testing.T) { t.Fatalf("unexpected result") } + tif, err = StringToTimeInForce("GTT") + if err != nil { + t.Fatal(err) + } else if tif != GTT { + t.Fatalf("expected %v, got %v", GTT, tif) + } + + tif, err = StringToTimeInForce("GOOD_TIL_TIME") + if err != nil { + t.Fatal(err) + } else if tif != GTT { + t.Fatalf("expected %v, got %v", GTT, tif) + } + tif, err = StringToTimeInForce("FILLORKILL") if err != nil { t.Error(err) diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 9364f35ad1e..b2cc4d879b9 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -383,10 +383,6 @@ const ( supportedTimeInForceFlag = UnknownTIF | GTC | GTT | FOK | IOC | PostOnlyGTC ) -var ( - supportedTIFItems = []TimeInForce{GTC, GTT, FOK, IOC, PostOnlyGTC} -) - // ByPrice used for sorting orders by price type ByPrice []Detail From d82cc0c50bcbcebc9721902a2c3a3c3df0b81dd1 Mon Sep 17 00:00:00 2001 From: Samuael A <39623015+samuael@users.noreply.github.com> Date: Mon, 18 Mar 2024 20:44:35 +0300 Subject: [PATCH 08/20] fix conflict with gateio timeInForce --- exchanges/gateio/gateio_test.go | 2 +- exchanges/gateio/gateio_wrapper.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index ce39ae91b55..e2a16a2bd71 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -3555,7 +3555,7 @@ func TestGetTimeInForce(t *testing.T) { require.NoError(t, err) assert.Equal(t, "gtc", ret) - ret, err = getTimeInForce(&order.Submit{Type: order.Market, FillOrKill: true}) + ret, err = getTimeInForce(&order.Submit{Type: order.Market, TimeInForce: order.FOK}) require.NoError(t, err) assert.Equal(t, "fok", ret) } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 1e67f41fa7b..83a27415466 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -2561,7 +2561,7 @@ var errPostOnlyOrderTypeUnsupported = errors.New("post only is only supported fo // IOC func getTimeInForce(s *order.Submit) (string, error) { timeInForce := "gtc" // limit order taker/maker - if s.Type == order.Market || s.ImmediateOrCancel { + if s.Type == order.Market || s.TimeInForce == order.IOC { timeInForce = "ioc" // market taker only } if s.PostOnly { @@ -2570,7 +2570,7 @@ func getTimeInForce(s *order.Submit) (string, error) { } timeInForce = "poc" // limit order maker only } - if s.FillOrKill { + if s.TimeInForce == order.IOC { timeInForce = "fok" // market order entire fill or kill } return timeInForce, nil From f63547d6b9812c878ba5d8693f5ad2d9c65a866a Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:06:40 +0300 Subject: [PATCH 09/20] Update order tests --- exchanges/order/order_test.go | 836 +++++++++------------------------- 1 file changed, 225 insertions(+), 611 deletions(-) diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index e21ebe41a6d..f8b3b6d5aff 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -255,102 +255,57 @@ func TestSubmit_DeriveSubmitResponse(t *testing.T) { t.Parallel() var s *Submit _, err := s.DeriveSubmitResponse("") - if !errors.Is(err, errOrderSubmitIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, errOrderSubmitIsNil) - } + require.ErrorIs(t, err, errOrderSubmitIsNil) s = &Submit{} _, err = s.DeriveSubmitResponse("") - if !errors.Is(err, ErrOrderIDNotSet) { - t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderIDNotSet) - } + require.ErrorIs(t, err, ErrOrderIDNotSet) resp, err := s.DeriveSubmitResponse("1337") - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } - - if resp.OrderID != "1337" { - t.Fatal("unexpected value") - } - - if resp.Status != New { - t.Fatal("unexpected value") - } - - if resp.Date.IsZero() { - t.Fatal("unexpected value") - } - - if resp.LastUpdated.IsZero() { - t.Fatal("unexpected value") - } + require.NoError(t, err) + require.Equal(t, resp.OrderID, "1337") + require.Equal(t, resp.Status, New) + require.False(t, resp.Date.IsZero()) + assert.False(t, resp.LastUpdated.IsZero()) } func TestSubmitResponse_DeriveDetail(t *testing.T) { t.Parallel() var s *SubmitResponse _, err := s.DeriveDetail(uuid.Nil) - if !errors.Is(err, errOrderSubmitResponseIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, errOrderSubmitResponseIsNil) - } + require.ErrorIs(t, err, errOrderSubmitResponseIsNil) id, err := uuid.NewV4() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) s = &SubmitResponse{} deets, err := s.DeriveDetail(id) - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } - - if deets.InternalOrderID != id { - t.Fatal("unexpected value") - } + require.NoError(t, err) + assert.Equal(t, id, deets.InternalOrderID) } func TestOrderSides(t *testing.T) { t.Parallel() var os = Buy - if os.String() != "BUY" { - t.Errorf("unexpected string %s", os.String()) - } - - if os.Lower() != "buy" { - t.Errorf("unexpected string %s", os.Lower()) - } - - if os.Title() != "Buy" { - t.Errorf("unexpected string %s", os.Title()) - } + assert.Equal(t, os.String(), "BUY") + assert.Equal(t, os.Lower(), "buy") + assert.Equal(t, os.Title(), "Buy") } func TestTitle(t *testing.T) { t.Parallel() orderType := Limit - if orderType.Title() != "Limit" { - t.Errorf("received '%v' expected 'Limit'", orderType.Title()) - } + require.Equal(t, orderType.Title(), "Limit") } func TestOrderTypes(t *testing.T) { t.Parallel() var orderType Type - if orderType.String() != "UNKNOWN" { - t.Errorf("unexpected string %s", orderType.String()) - } - - if orderType.Lower() != "unknown" { - t.Errorf("unexpected string %s", orderType.Lower()) - } - - if orderType.Title() != "Unknown" { - t.Errorf("unexpected string %s", orderType.Title()) - } + assert.Equal(t, orderType.String(), "UNKNOWN") + assert.Equal(t, orderType.Lower(), "unknown") + assert.Equal(t, orderType.Title(), "Unknown") } func TestInferCostsAndTimes(t *testing.T) { @@ -451,19 +406,13 @@ func TestFilterOrdersByType(t *testing.T) { } FilterOrdersByType(&orders, AnyType) - if len(orders) != 3 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) - } + assert.Equalf(t, len(orders), 3, "Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) FilterOrdersByType(&orders, Limit) - if len(orders) != 2 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } + assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) FilterOrdersByType(&orders, Stop) - if len(orders) != 1 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) - } + assert.Equalf(t, len(orders), 1, "Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) } var filterOrdersByTypeBenchmark = &[]Detail{ @@ -503,19 +452,13 @@ func TestFilterOrdersBySide(t *testing.T) { } FilterOrdersBySide(&orders, AnySide) - if len(orders) != 3 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) - } + assert.Equalf(t, len(orders), 3, "Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) FilterOrdersBySide(&orders, Buy) - if len(orders) != 2 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } + assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) FilterOrdersBySide(&orders, Sell) - if len(orders) != 1 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) - } + assert.Equalf(t, len(orders), 1, "Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) } var filterOrdersBySideBenchmark = &[]Detail{ @@ -557,50 +500,29 @@ func TestFilterOrdersByTimeRange(t *testing.T) { } err := FilterOrdersByTimeRange(&orders, time.Unix(0, 0), time.Unix(0, 0)) - if err != nil { - t.Fatal(err) - } - if len(orders) != 3 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) - } + require.NoError(t, err) + assert.Equalf(t, len(orders), 3, "Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) err = FilterOrdersByTimeRange(&orders, time.Unix(100, 0), time.Unix(111, 0)) - if err != nil { - t.Fatal(err) - } - if len(orders) != 3 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) - } + require.NoError(t, err) + assert.Equalf(t, len(orders), 3, "Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) err = FilterOrdersByTimeRange(&orders, time.Unix(101, 0), time.Unix(111, 0)) - if err != nil { - t.Fatal(err) - } - if len(orders) != 2 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) - } + require.NoError(t, err) + assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) err = FilterOrdersByTimeRange(&orders, time.Unix(200, 0), time.Unix(300, 0)) - if err != nil { - t.Fatal(err) - } - if len(orders) != 0 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) - } + require.NoError(t, err) + assert.Equalf(t, len(orders), 0, "Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) + orders = append(orders, Detail{}) // test for event no timestamp is set on an order, best to include it err = FilterOrdersByTimeRange(&orders, time.Unix(200, 0), time.Unix(300, 0)) - if err != nil { - t.Fatal(err) - } - if len(orders) != 1 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } + require.NoError(t, err) + assert.Equalf(t, len(orders), 1, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) err = FilterOrdersByTimeRange(&orders, time.Unix(300, 0), time.Unix(50, 0)) - if !errors.Is(err, common.ErrStartAfterEnd) { - t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrStartAfterEnd) - } + require.ErrorIs(t, err, common.ErrStartAfterEnd) } var filterOrdersByTimeRangeBenchmark = &[]Detail{ @@ -649,39 +571,28 @@ func TestFilterOrdersByPairs(t *testing.T) { currency.NewPair(currency.LTC, currency.EUR), currency.NewPair(currency.DOGE, currency.RUB)} FilterOrdersByPairs(&orders, currencies) - if len(orders) != 4 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) - } + assert.Equalf(t, len(orders), 4, "Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD), currency.NewPair(currency.LTC, currency.EUR)} FilterOrdersByPairs(&orders, currencies) - if len(orders) != 3 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) - } + assert.Equalf(t, len(orders), 3, "Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD)} FilterOrdersByPairs(&orders, currencies) - if len(orders) != 2 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } + assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) currencies = []currency.Pair{currency.NewPair(currency.USD, currency.BTC)} FilterOrdersByPairs(&orders, currencies) - if len(orders) != 2 { - t.Errorf("Reverse Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } + assert.Equalf(t, len(orders), 2, "Reverse Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) currencies = []currency.Pair{} FilterOrdersByPairs(&orders, currencies) - if len(orders) != 2 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } + assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + currencies = append(currencies, currency.EMPTYPAIR) FilterOrdersByPairs(&orders, currencies) - if len(orders) != 2 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } + assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) } var filterOrdersByPairsBenchmark = &[]Detail{ @@ -722,14 +633,10 @@ func TestSortOrdersByPrice(t *testing.T) { } SortOrdersByPrice(&orders, false) - if orders[0].Price != 0 { - t.Errorf("Expected: '%v', received: '%v'", 0, orders[0].Price) - } + assert.Equalf(t, orders[0].Price, 0., "Expected: '%v', received: '%v'", 0, orders[0].Price) SortOrdersByPrice(&orders, true) - if orders[0].Price != 100 { - t.Errorf("Expected: '%v', received: '%v'", 100, orders[0].Price) - } + assert.Equalf(t, orders[0].Price, 100., "Expected: '%v', received: '%v'", 100, orders[0].Price) } func TestSortOrdersByDate(t *testing.T) { @@ -746,18 +653,10 @@ func TestSortOrdersByDate(t *testing.T) { } SortOrdersByDate(&orders, false) - if orders[0].Date.Unix() != time.Unix(0, 0).Unix() { - t.Errorf("Expected: '%v', received: '%v'", - time.Unix(0, 0).Unix(), - orders[0].Date.Unix()) - } + assert.Equal(t, orders[0].Date.Unix(), time.Unix(0, 0).Unix()) SortOrdersByDate(&orders, true) - if orders[0].Date.Unix() != time.Unix(2, 0).Unix() { - t.Errorf("Expected: '%v', received: '%v'", - time.Unix(2, 0).Unix(), - orders[0].Date.Unix()) - } + assert.Equal(t, orders[0].Date, time.Unix(2, 0)) } func TestSortOrdersByCurrency(t *testing.T) { @@ -891,12 +790,8 @@ func TestStringToOrderSide(t *testing.T) { testData := &cases[i] t.Run(testData.in, func(t *testing.T) { out, err := StringToOrderSide(testData.in) - if !errors.Is(err, testData.err) { - t.Fatalf("received: '%v' but expected: '%v'", err, testData.err) - } - if out != testData.out { - t.Errorf("Unexpected output %v. Expected %v", out, testData.out) - } + require.ErrorIs(t, err, testData.err) + require.Equal(t, out, testData.out) }) } } @@ -954,12 +849,8 @@ func TestStringToOrderType(t *testing.T) { testData := &cases[i] t.Run(testData.in, func(t *testing.T) { out, err := StringToOrderType(testData.in) - if !errors.Is(err, testData.err) { - t.Fatalf("received: '%v' but expected: '%v'", err, testData.err) - } - if out != testData.out { - t.Errorf("Unexpected output %v. Expected %v", out, testData.out) - } + require.ErrorIs(t, err, testData.err) + assert.Equal(t, testData.out, out) }) } } @@ -1031,12 +922,8 @@ func TestStringToOrderStatus(t *testing.T) { testData := &stringsToOrderStatus[i] t.Run(testData.in, func(t *testing.T) { out, err := StringToOrderStatus(testData.in) - if !errors.Is(err, testData.err) { - t.Fatalf("received: '%v' but expected: '%v'", err, testData.err) - } - if out != testData.out { - t.Errorf("Unexpected output %v. Expected %v", out, testData.out) - } + require.ErrorIs(t, err, testData.err) + assert.Equal(t, testData.out, out) }) } } @@ -1056,9 +943,7 @@ func TestUpdateOrderFromModifyResponse(t *testing.T) { updated := time.Now() pair, err := currency.NewPairFromString("BTCUSD") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) om := ModifyResponse{ TimeInForce: IOC, @@ -1077,52 +962,21 @@ func TestUpdateOrderFromModifyResponse(t *testing.T) { } od.UpdateOrderFromModifyResponse(&om) - - if od.TimeInForce == UnknownTIF { - t.Error("Failed to update") - } - if !od.PostOnly { - t.Error("Failed to update") - } - if od.Price != 1 { - t.Error("Failed to update") - } - if od.Amount != 1 { - t.Error("Failed to update") - } - if od.TriggerPrice != 1 { - t.Error("Failed to update") - } - if od.RemainingAmount != 1 { - t.Error("Failed to update") - } - if od.Exchange != "" { - t.Error("Should not be able to update exchange via modify") - } - if od.OrderID != "1" { - t.Error("Failed to update") - } - if od.Type != 1 { - t.Error("Failed to update") - } - if od.Side != 1 { - t.Error("Failed to update") - } - if od.Status != 1 { - t.Error("Failed to update") - } - if od.AssetType != 1 { - t.Error("Failed to update") - } - if od.LastUpdated != updated { - t.Error("Failed to update") - } - if od.Pair.String() != "BTCUSD" { - t.Error("Failed to update") - } - if od.Trades != nil { - t.Error("Failed to update") - } + assert.NotEqual(t, od.TimeInForce, UnknownTIF) + assert.True(t, od.PostOnly) + assert.Equal(t, 1., od.Price) + assert.Equal(t, 1., od.Amount) + assert.Equal(t, 1., od.TriggerPrice) + assert.Equal(t, 1., od.RemainingAmount) + assert.Equal(t, "", od.Exchange, "Should not be able to update exchange via modify") + assert.Equal(t, "1", od.OrderID) + assert.Equal(t, Type(1), od.Type) + assert.Equal(t, Side(1), od.Side) + assert.Equal(t, Status(1), od.Status) + assert.Equal(t, asset.Item(1), od.AssetType) + assert.Equal(t, od.LastUpdated, updated) + assert.Equal(t, "BTCUSD", od.Pair.String()) + assert.Nil(t, od.Trades) } func TestUpdateOrderFromDetail(t *testing.T) { @@ -1131,20 +985,14 @@ func TestUpdateOrderFromDetail(t *testing.T) { updated := time.Now() pair, err := currency.NewPairFromString("BTCUSD") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) id, err := uuid.NewV4() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var od *Detail err = od.UpdateOrderFromDetail(nil) - if !errors.Is(err, ErrOrderDetailIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderDetailIsNil) - } + require.ErrorIs(t, err, ErrOrderDetailIsNil) om := &Detail{ TimeInForce: GTC, @@ -1179,101 +1027,42 @@ func TestUpdateOrderFromDetail(t *testing.T) { od = &Detail{Exchange: "test"} err = od.UpdateOrderFromDetail(nil) - if !errors.Is(err, ErrOrderDetailIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderDetailIsNil) - } + require.ErrorIs(t, err, ErrOrderDetailIsNil) err = od.UpdateOrderFromDetail(om) - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } - if od.InternalOrderID != id { - t.Error("Failed to initialize the internal order ID") - } - if od.TimeInForce != GTC { - t.Error("Failed to update") - } - if !od.HiddenOrder { - t.Error("Failed to update") - } - if !od.PostOnly { - t.Error("Failed to update") - } - if od.Leverage != 1 { - t.Error("Failed to update") - } - if od.Price != 1 { - t.Error("Failed to update") - } - if od.Amount != 1 { - t.Error("Failed to update") - } - if od.LimitPriceLower != 1 { - t.Error("Failed to update") - } - if od.LimitPriceUpper != 1 { - t.Error("Failed to update") - } - if od.TriggerPrice != 1 { - t.Error("Failed to update") - } - if od.QuoteAmount != 1 { - t.Error("Failed to update") - } - if od.ExecutedAmount != 1 { - t.Error("Failed to update") - } - if od.RemainingAmount != 1 { - t.Error("Failed to update") - } - if od.Fee != 1 { - t.Error("Failed to update") - } - if od.Exchange != "test" { - t.Error("Should not be able to update exchange via modify") - } - if od.OrderID != "1" { - t.Error("Failed to update") - } - if od.ClientID != "1" { - t.Error("Failed to update") - } - if od.ClientOrderID != "DukeOfWombleton" { - t.Error("Failed to update") - } - if od.WalletAddress != "1" { - t.Error("Failed to update") - } - if od.Type != 1 { - t.Error("Failed to update") - } - if od.Side != 1 { - t.Error("Failed to update") - } - if od.Status != 1 { - t.Error("Failed to update") - } - if od.AssetType != 1 { - t.Error("Failed to update") - } - if od.LastUpdated != updated { - t.Error("Failed to update") - } - if od.Pair.String() != "BTCUSD" { - t.Error("Failed to update") - } - if od.Trades != nil { - t.Error("Failed to update") - } + require.NoError(t, err) + + assert.Equal(t, od.InternalOrderID, id) + assert.Equal(t, od.TimeInForce, GTC) + 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) + assert.Equal(t, 1., od.LimitPriceLower) + assert.Equal(t, 1., od.LimitPriceUpper) + assert.Equal(t, 1., od.TriggerPrice) + assert.Equal(t, 1., od.QuoteAmount) + assert.Equal(t, 1., od.ExecutedAmount) + assert.Equal(t, 1., od.RemainingAmount) + assert.Equal(t, 1., od.Fee) + assert.Equal(t, "test", od.Exchange, "Should not be able to update exchange via modify") + assert.Equal(t, "1", od.OrderID) + assert.Equal(t, "1", od.ClientID) + assert.Equal(t, "DukeOfWombleton", od.ClientOrderID) + assert.Equal(t, "1", od.WalletAddress) + assert.Equal(t, Type(1), od.Type) + assert.Equal(t, Side(1), od.Side) + assert.Equal(t, Status(1), od.Status) + assert.Equal(t, asset.Item(1), od.AssetType) + assert.Equal(t, updated, od.LastUpdated) + assert.Equal(t, "BTCUSD", od.Pair.String()) + assert.Nil(t, od.Trades) om.Trades = append(om.Trades, TradeHistory{TID: "1"}, TradeHistory{TID: "2"}) err = od.UpdateOrderFromDetail(om) - if err != nil { - t.Fatal(err) - } - if len(od.Trades) != 2 { - t.Error("Failed to add trades") - } + require.NoError(t, err) + assert.Len(t, od.Trades, 2) om.Trades[0].Exchange = leet om.Trades[0].Price = 1337 om.Trades[0].Fee = 1337 @@ -1284,53 +1073,27 @@ func TestUpdateOrderFromDetail(t *testing.T) { om.Trades[0].Type = UnknownType om.Trades[0].Amount = 1337 err = od.UpdateOrderFromDetail(om) - if err != nil { - t.Fatal(err) - } - if od.Trades[0].Exchange == leet { - t.Error("Should not be able to update exchange from update") - } - if od.Trades[0].Price != 1337 { - t.Error("Failed to update trades") - } - if od.Trades[0].Fee != 1337 { - t.Error("Failed to update trades") - } - if !od.Trades[0].IsMaker { - t.Error("Failed to update trades") - } - if od.Trades[0].Timestamp != updated { - t.Error("Failed to update trades") - } - if od.Trades[0].Description != leet { - t.Error("Failed to update trades") - } - if od.Trades[0].Side != UnknownSide { - t.Error("Failed to update trades") - } - if od.Trades[0].Type != UnknownType { - t.Error("Failed to update trades") - } - if od.Trades[0].Amount != 1337 { - t.Error("Failed to update trades") - } + require.NoError(t, err) + assert.NotEqual(t, leet, od.Trades[0].Exchange, "Should not be able to update exchange from update") + assert.Equal(t, 1337., od.Trades[0].Price) + assert.Equal(t, 1337., od.Trades[0].Fee) + assert.True(t, od.Trades[0].IsMaker) + assert.Equal(t, updated, od.Trades[0].Timestamp) + assert.Equal(t, leet, od.Trades[0].Description) + assert.Equal(t, UnknownSide, od.Trades[0].Side) + assert.Equal(t, UnknownType, od.Trades[0].Type) + assert.Equal(t, 1337., od.Trades[0].Amount) id, err = uuid.NewV4() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) om = &Detail{ InternalOrderID: id, } err = od.UpdateOrderFromDetail(om) - if err != nil { - t.Fatal(err) - } - if od.InternalOrderID == id { - t.Error("Should not be able to update the internal order ID after initialization") - } + require.NoError(t, err) + assert.NotEqual(t, id, od.InternalOrderID, "Should not be able to update the internal order ID after initialization") } func TestClassificationError_Error(t *testing.T) { @@ -1346,15 +1109,11 @@ func TestClassificationError_Error(t *testing.T) { func TestValidationOnOrderTypes(t *testing.T) { var cancelMe *Cancel - if cancelMe.Validate() != ErrCancelOrderIsNil { - t.Fatal("unexpected error") - } + require.ErrorIs(t, cancelMe.Validate(), ErrCancelOrderIsNil) cancelMe = new(Cancel) err := cancelMe.Validate() - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } + assert.NoError(t, err) err = cancelMe.Validate(cancelMe.PairAssetRequired()) if err == nil || err.Error() != ErrPairIsEmpty.Error() { @@ -1369,113 +1128,68 @@ func TestValidationOnOrderTypes(t *testing.T) { cancelMe.AssetType = asset.Spot err = cancelMe.Validate(cancelMe.PairAssetRequired()) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } - - if cancelMe.Validate(cancelMe.StandardCancel()) == nil { - t.Fatal("expected error") - } + assert.NoError(t, err) + require.NotNil(t, cancelMe.Validate(cancelMe.StandardCancel())) - if cancelMe.Validate(validate.Check(func() error { + require.NoError(t, cancelMe.Validate(validate.Check(func() error { return nil - })) != nil { - t.Fatal("should return nil") - } + }))) cancelMe.OrderID = "1337" - if cancelMe.Validate(cancelMe.StandardCancel()) != nil { - t.Fatal("should return nil") - } + require.NoError(t, cancelMe.Validate(cancelMe.StandardCancel())) var getOrders *MultiOrderRequest err = getOrders.Validate() - if !errors.Is(err, ErrGetOrdersRequestIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, ErrGetOrdersRequestIsNil) - } + require.ErrorIs(t, err, ErrGetOrdersRequestIsNil) getOrders = new(MultiOrderRequest) err = getOrders.Validate() - if !errors.Is(err, asset.ErrNotSupported) { - t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported) - } + require.ErrorIs(t, err, asset.ErrNotSupported) getOrders.AssetType = asset.Spot err = getOrders.Validate() - if !errors.Is(err, ErrSideIsInvalid) { - t.Fatalf("received: '%v' but expected: '%v'", err, ErrSideIsInvalid) - } + require.ErrorIs(t, err, ErrSideIsInvalid) getOrders.Side = AnySide err = getOrders.Validate() - if !errors.Is(err, errUnrecognisedOrderType) { - t.Fatalf("received: '%v' but expected: '%v'", err, errUnrecognisedOrderType) - } + require.ErrorIs(t, err, errUnrecognisedOrderType) var errTestError = errors.New("test error") getOrders.Type = AnyType err = getOrders.Validate(validate.Check(func() error { return errTestError })) - if !errors.Is(err, errTestError) { - t.Fatalf("received: '%v' but expected: '%v'", err, errTestError) - } + require.ErrorIs(t, err, errTestError) err = getOrders.Validate(validate.Check(func() error { return nil })) - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } + require.NoError(t, err) var modifyOrder *Modify - if modifyOrder.Validate() != ErrModifyOrderIsNil { - t.Fatal("unexpected error") - } + require.ErrorIs(t, modifyOrder.Validate(), ErrModifyOrderIsNil) modifyOrder = new(Modify) - if modifyOrder.Validate() != ErrPairIsEmpty { - t.Fatal("unexpected error") - } + require.ErrorIs(t, modifyOrder.Validate(), ErrPairIsEmpty) p, err := currency.NewPairFromString("BTC-USD") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) modifyOrder.Pair = p - if modifyOrder.Validate() != ErrAssetNotSet { - t.Fatal("unexpected error") - } + require.ErrorIs(t, modifyOrder.Validate(), ErrAssetNotSet) modifyOrder.AssetType = asset.Spot - if modifyOrder.Validate() != ErrOrderIDNotSet { - t.Fatal("unexpected error") - } + require.ErrorIs(t, modifyOrder.Validate(), ErrOrderIDNotSet) modifyOrder.ClientOrderID = "1337" - if modifyOrder.Validate() != nil { - t.Fatal("should not error") - } - - if modifyOrder.Validate(validate.Check(func() error { - return errors.New("this should error") - })) == nil { - t.Fatal("expected error") - } - - if modifyOrder.Validate(validate.Check(func() error { - return nil - })) != nil { - t.Fatal("unexpected error") - } + require.NoError(t, modifyOrder.Validate()) + require.NotNil(t, modifyOrder.Validate(validate.Check(func() error { return errors.New("this should error") }))) + require.NoError(t, modifyOrder.Validate(validate.Check(func() error { return nil }))) } func TestMatchFilter(t *testing.T) { t.Parallel() id, err := uuid.NewV4() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) filters := map[int]*Filter{ 0: {}, 1: {Exchange: "Binance"}, @@ -1519,9 +1233,7 @@ func TestMatchFilter(t *testing.T) { // empty filter tests emptyFilter := filters[0] for _, o := range orders { - if !o.MatchFilter(emptyFilter) { - t.Error("empty filter should match everything") - } + assert.True(t, o.MatchFilter(emptyFilter), "empty filter should match everything") } tests := map[int]struct { @@ -1572,9 +1284,7 @@ func TestMatchFilter(t *testing.T) { for num, tt := range tests { t.Run(strconv.Itoa(num), func(t *testing.T) { t.Parallel() - if tt.o.MatchFilter(tt.f) != tt.expectedResult { - t.Errorf("tests[%v] failed", num) - } + assert.Equalf(t, tt.expectedResult, tt.o.MatchFilter(tt.f), "tests[%v] failed", num) }) } } @@ -1630,9 +1340,7 @@ func TestIsActive(t *testing.T) { } // specific tests for num, tt := range statusTests { - if tt.o.IsActive() != tt.expectedResult { - t.Fatalf("statusTests[%v] failed", num) - } + require.Equalf(t, tt.expectedResult, tt.o.IsActive(), "statusTests[%v] failed", num) } } @@ -1854,9 +1562,8 @@ func TestDetail_CopyPointerOrderSlice(t *testing.T) { func TestDeriveModify(t *testing.T) { t.Parallel() var o *Detail - if _, err := o.DeriveModify(); !errors.Is(err, errOrderDetailIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, errOrderDetailIsNil) - } + _, err := o.DeriveModify() + require.ErrorIs(t, err, errOrderDetailIsNil) pair := currency.NewPair(currency.BTC, currency.AUD) @@ -1871,13 +1578,8 @@ func TestDeriveModify(t *testing.T) { } mod, err := o.DeriveModify() - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } - - if mod == nil { - t.Fatal("should not be nil") - } + require.NoError(t, err) + require.NotNil(t, mod) if mod.Exchange != "wow" || mod.OrderID != "wow2" || @@ -1893,9 +1595,8 @@ func TestDeriveModify(t *testing.T) { func TestDeriveModifyResponse(t *testing.T) { t.Parallel() var mod *Modify - if _, err := mod.DeriveModifyResponse(); !errors.Is(err, errOrderDetailIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, errOrderDetailIsNil) - } + _, err := mod.DeriveModifyResponse() + require.ErrorIs(t, err, errOrderDetailIsNil) pair := currency.NewPair(currency.BTC, currency.AUD) @@ -1910,13 +1611,8 @@ func TestDeriveModifyResponse(t *testing.T) { } modresp, err := mod.DeriveModifyResponse() - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } - - if modresp == nil { - t.Fatal("should not be nil") - } + require.NoError(t, err) + require.NotNil(t, modresp) if modresp.Exchange != "wow" || modresp.OrderID != "wow2" || @@ -1932,9 +1628,8 @@ func TestDeriveModifyResponse(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) - } + _, err := o.DeriveCancel() + require.ErrorIs(t, err, errOrderDetailIsNil) pair := currency.NewPair(currency.BTC, currency.AUD) @@ -1951,9 +1646,8 @@ func TestDeriveCancel(t *testing.T) { AssetType: asset.Futures, } cancel, err := o.DeriveCancel() - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } + require.NoError(t, err) + if cancel.Exchange != "wow" || cancel.OrderID != "wow1" || cancel.AccountID != "wow2" || @@ -1994,14 +1688,10 @@ func TestGetOrdersRequest_Filter(t *testing.T) { } shinyAndClean := request.Filter("test", orders) - if len(shinyAndClean) != 16 { - t.Fatalf("received: '%v' but expected: '%v'", len(shinyAndClean), 16) - } + require.Len(t, shinyAndClean, 16) for x := range shinyAndClean { - if strconv.FormatInt(int64(x), 10) != shinyAndClean[x].OrderID { - t.Fatalf("received: '%v' but expected: '%v'", shinyAndClean[x].OrderID, int64(x)) - } + require.Equal(t, strconv.FormatInt(int64(x), 10), shinyAndClean[x].OrderID) } request.Pairs = []currency.Pair{btcltc} @@ -2011,29 +1701,18 @@ func TestGetOrdersRequest_Filter(t *testing.T) { request.StartTime = time.Unix(1337, 0) shinyAndClean = request.Filter("test", orders) - - if len(shinyAndClean) != 8 { - t.Fatalf("received: '%v' but expected: '%v'", len(shinyAndClean), 8) - } + require.Len(t, shinyAndClean, 8) for x := range shinyAndClean { - if strconv.FormatInt(int64(x)+8, 10) != shinyAndClean[x].OrderID { - t.Fatalf("received: '%v' but expected: '%v'", shinyAndClean[x].OrderID, int64(x)+8) - } + require.Equal(t, strconv.FormatInt(int64(x)+8, 10), shinyAndClean[x].OrderID) } } func TestIsValidOrderSubmissionSide(t *testing.T) { t.Parallel() - if IsValidOrderSubmissionSide(UnknownSide) { - t.Error("expected false") - } - if !IsValidOrderSubmissionSide(Buy) { - t.Error("expected true") - } - if IsValidOrderSubmissionSide(CouldNotBuy) { - t.Error("expected false") - } + assert.False(t, IsValidOrderSubmissionSide(UnknownSide)) + assert.True(t, IsValidOrderSubmissionSide(Buy)) + assert.False(t, IsValidOrderSubmissionSide(CouldNotBuy)) } func TestAdjustBaseAmount(t *testing.T) { @@ -2041,35 +1720,21 @@ func TestAdjustBaseAmount(t *testing.T) { var s *SubmitResponse err := s.AdjustBaseAmount(0) - if !errors.Is(err, errOrderSubmitResponseIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, errOrderSubmitResponseIsNil) - } + require.ErrorIs(t, err, errOrderSubmitResponseIsNil) s = &SubmitResponse{} err = s.AdjustBaseAmount(0) - if !errors.Is(err, errAmountIsZero) { - t.Fatalf("received: '%v' but expected: '%v'", err, errAmountIsZero) - } + require.ErrorIs(t, err, errAmountIsZero) s.Amount = 1.7777777777 err = s.AdjustBaseAmount(1.7777777777) - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } - - if s.Amount != 1.7777777777 { - t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 1.7777777777) - } + require.NoError(t, err) + require.Equal(t, 1.7777777777, s.Amount) s.Amount = 1.7777777777 err = s.AdjustBaseAmount(1.777) - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } - - if s.Amount != 1.777 { - t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 1.777) - } + require.NoError(t, err) + assert.Equal(t, 1.777, s.Amount) } func TestAdjustQuoteAmount(t *testing.T) { @@ -2077,35 +1742,21 @@ func TestAdjustQuoteAmount(t *testing.T) { var s *SubmitResponse err := s.AdjustQuoteAmount(0) - if !errors.Is(err, errOrderSubmitResponseIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, errOrderSubmitResponseIsNil) - } + require.ErrorIs(t, err, errOrderSubmitResponseIsNil) s = &SubmitResponse{} err = s.AdjustQuoteAmount(0) - if !errors.Is(err, errAmountIsZero) { - t.Fatalf("received: '%v' but expected: '%v'", err, errAmountIsZero) - } + require.ErrorIs(t, err, errAmountIsZero) s.QuoteAmount = 5.222222222222 err = s.AdjustQuoteAmount(5.222222222222) - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } - - if s.QuoteAmount != 5.222222222222 { - t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 5.222222222222) - } + require.NoError(t, err) + require.Equal(t, 5.222222222222, s.QuoteAmount) s.QuoteAmount = 5.222222222222 err = s.AdjustQuoteAmount(5.22222222) - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) - } - - if s.QuoteAmount != 5.22222222 { - t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 5.22222222) - } + require.NoError(t, err) + assert.Equal(t, 5.22222222, s.QuoteAmount) } func TestSideUnmarshal(t *testing.T) { @@ -2120,109 +1771,71 @@ func TestSideUnmarshal(t *testing.T) { func TestIsValid(t *testing.T) { t.Parallel() - if TimeInForce(50).IsValid() { - t.Fatal("TestIsValid returned an unexpected result") - } - - if !IOC.IsValid() { - t.Fatal("TestIsValid returned an unexpected result") - } - if !GTT.IsValid() { - t.Fatal("TestIsValid returned an unexpected result") - } - if !GTC.IsValid() { - t.Fatal("TestIsValid returned an unexpected result") - } - if !FOK.IsValid() { - t.Fatal("TestIsValid returned an unexpected result") - } - if !PostOnlyGTC.IsValid() { - t.Fatal("TestIsValid returned an unexpected result") - } - if !UnknownTIF.IsValid() { - t.Fatal("TestIsValid returned an unexpected result") - } - if TimeInForce(22).IsValid() { - t.Fatal("TestIsValid returned an unexpected result") + var timeInForceValidityMap = map[TimeInForce]bool{ + TimeInForce(1): false, + IOC: true, + GTT: true, + GTC: true, + GTD: true, + FOK: true, + PostOnlyGTC: true, + UnsetTIF: true, + UnknownTIF: false, + } + var tif TimeInForce + for tif = range timeInForceValidityMap { + assert.Equalf(t, timeInForceValidityMap[tif], tif.IsValid(), "got %v, expected %v for %v with id %d", tif.IsValid(), timeInForceValidityMap[tif], tif, tif) } } func TestStringToTimeInForce(t *testing.T) { t.Parallel() - _, err := StringToTimeInForce("Unknown") - if !errors.Is(err, ErrInvalidTimeInForce) { - t.Fatalf("expected %v, got %v", ErrInvalidTimeInForce, err) - } - tif1, err := StringToTimeInForce("GoodTillCancel") - if err != nil { - t.Fatal(err) - } - - tif, err := StringToTimeInForce("GOOD_TILL_CANCELED") - if err != nil { - t.Fatal(err) - } else if tif1 != tif { - t.Fatalf("unexpected result") - } - - tif, err = StringToTimeInForce("GTT") - if err != nil { - t.Fatal(err) - } else if tif != GTT { - t.Fatalf("expected %v, got %v", GTT, tif) - } - - tif, err = StringToTimeInForce("GOOD_TIL_TIME") - if err != nil { - t.Fatal(err) - } else if tif != GTT { - t.Fatalf("expected %v, got %v", GTT, tif) - } - - tif, err = StringToTimeInForce("FILLORKILL") - if err != nil { - t.Error(err) - } else if tif != FOK { - t.Fatalf("expected %v, got %v", FOK, tif) - } - - tif, err = StringToTimeInForce("POST_ONLY_GOOD_TIL_CANCELLED") - if err != nil { - t.Fatal(err) - } else if tif != PostOnlyGTC { - t.Fatalf("expected %v, got %v", PostOnlyGTC, tif) - } - tif, err = StringToTimeInForce("immedIate_Or_Cancel") - if err != nil { - t.Fatal(err) - } else if tif != IOC { - t.Fatalf("expected %v, got %v", IOC, tif) - } - - _, err = StringToTimeInForce("") - if !errors.Is(err, ErrInvalidTimeInForce) { - t.Fatalf("expected %v, got %v", ErrInvalidTimeInForce, err) + var timeInForceStringToValueMap = map[string]struct { + TIF TimeInForce + Error error + }{ + "Unknown": {TIF: UnknownTIF, Error: ErrInvalidTimeInForce}, + "GoodTillCancel": {TIF: GTC}, + "GOOD_TILL_CANCELED": {TIF: GTC}, + "GTT": {TIF: GTT}, + "GOOD_TIL_TIME": {TIF: GTT}, + "FILLORKILL": {TIF: FOK}, + "POST_ONLY_GOOD_TIL_CANCELLED": {TIF: PostOnlyGTC}, + "immedIate_Or_Cancel": {TIF: IOC}, + "": {TIF: UnsetTIF}, + "IOC": {TIF: IOC}, + "immediate_or_cancel": {TIF: IOC}, + "IMMEDIATE_OR_CANCEL": {TIF: IOC}, + "IMMEDIATEORCANCEL": {TIF: IOC}, + "GOOD_TILL_CANCELLED": {TIF: GTC}, + "good_till_day": {TIF: GTD}, + "GOOD_TILL_DAY": {TIF: GTD}, + "GOODtillday": {TIF: GTD}, + "abcdfeg": {TIF: UnknownTIF, Error: ErrInvalidTimeInForce}, + } + + for tk := range timeInForceStringToValueMap { + result, err := StringToTimeInForce(tk) + assert.ErrorIsf(t, err, timeInForceStringToValueMap[tk].Error, "got %v, expected %v", err, timeInForceStringToValueMap[tk].Error) + assert.Equalf(t, result, timeInForceStringToValueMap[tk].TIF, "got %v, expected %v", result, timeInForceStringToValueMap[tk].TIF) } - tif, err = StringToTimeInForce("IOC") - assert.NoError(t, err) - assert.Equal(t, tif, IOC) } func TestString(t *testing.T) { t.Parallel() - valMap := map[string]TimeInForce{ - "IOC": IOC, - "GTC": GTC, - "GTT": GTT, - "FOK": FOK, - "POST_ONLY_GOOD_TIL_CANCELLED": PostOnlyGTC, - "UNKNOWN": UnknownTIF, + valMap := map[TimeInForce]string{ + IOC: "IOC", + GTC: "GTC", + GTT: "GTT", + FOK: "FOK", + PostOnlyGTC: "POST_ONLY_GOOD_TIL_CANCELLED", + UnknownTIF: "UNKNOWN", + UnsetTIF: "", } for x := range valMap { - if result := valMap[x].String(); x != result { - t.Fatalf("expected %v, got %v", x, result) - } + result := x.String() + assert.Equalf(t, result, valMap[x], "expected %v, got %v", x, result) } } func TestSideMarshalJSON(t *testing.T) { @@ -2230,6 +1843,7 @@ func TestSideMarshalJSON(t *testing.T) { b, err := Buy.MarshalJSON() assert.NoError(t, err) assert.Equal(t, `"BUY"`, string(b)) + b, err = UnknownSide.MarshalJSON() assert.NoError(t, err) assert.Equal(t, `"UNKNOWN"`, string(b)) From 28cb5f7324c13aaee55f823a0cca87a5803da447 Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:01:27 +0300 Subject: [PATCH 10/20] Complete updating the order unit tests --- exchanges/order/limits_test.go | 225 ++++++++------------------------ exchanges/order/order_test.go | 229 ++++++++++----------------------- 2 files changed, 125 insertions(+), 329 deletions(-) diff --git a/exchanges/order/limits_test.go b/exchanges/order/limits_test.go index 072c9c5ac4c..3b11f2aa2cd 100644 --- a/exchanges/order/limits_test.go +++ b/exchanges/order/limits_test.go @@ -1,10 +1,11 @@ package order import ( - "errors" "testing" "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) @@ -19,9 +20,7 @@ func TestLoadLimits(t *testing.T) { t.Parallel() e := ExecutionLimits{} err := e.LoadLimits(nil) - if !errors.Is(err, errCannotLoadLimit) { - t.Fatalf("expected error %v but received %v", errCannotLoadLimit, err) - } + assert.ErrorIs(t, err, errCannotLoadLimit) invalidAsset := []MinMaxLevel{ { @@ -33,11 +32,7 @@ func TestLoadLimits(t *testing.T) { }, } err = e.LoadLimits(invalidAsset) - if !errors.Is(err, asset.ErrNotSupported) { - t.Fatalf("expected error %v but received %v", - asset.ErrNotSupported, - err) - } + require.ErrorIs(t, err, asset.ErrNotSupported) invalidPairLoading := []MinMaxLevel{ { @@ -50,9 +45,7 @@ func TestLoadLimits(t *testing.T) { } err = e.LoadLimits(invalidPairLoading) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Fatalf("expected error %v but received %v", currency.ErrCurrencyPairEmpty, err) - } + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) newLimits := []MinMaxLevel{ { @@ -66,9 +59,7 @@ func TestLoadLimits(t *testing.T) { } err = e.LoadLimits(newLimits) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + require.NoError(t, err) badLimit := []MinMaxLevel{ { @@ -82,9 +73,7 @@ func TestLoadLimits(t *testing.T) { } err = e.LoadLimits(badLimit) - if !errors.Is(err, errInvalidPriceLevels) { - t.Fatalf("expected error %v but received %v", errInvalidPriceLevels, err) - } + require.ErrorIs(t, err, errInvalidPriceLevels) badLimit = []MinMaxLevel{ { @@ -98,9 +87,7 @@ func TestLoadLimits(t *testing.T) { } err = e.LoadLimits(badLimit) - if !errors.Is(err, errInvalidAmountLevels) { - t.Fatalf("expected error %v but received %v", errInvalidPriceLevels, err) - } + require.ErrorIs(t, err, errInvalidAmountLevels) goodLimit := []MinMaxLevel{ { @@ -110,9 +97,7 @@ func TestLoadLimits(t *testing.T) { } err = e.LoadLimits(goodLimit) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + require.NoError(t, err) noCompare := []MinMaxLevel{ { @@ -123,9 +108,7 @@ func TestLoadLimits(t *testing.T) { } err = e.LoadLimits(noCompare) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + require.NoError(t, err) noCompare = []MinMaxLevel{ { @@ -136,18 +119,14 @@ func TestLoadLimits(t *testing.T) { } err = e.LoadLimits(noCompare) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + assert.NoError(t, err) } func TestGetOrderExecutionLimits(t *testing.T) { t.Parallel() e := ExecutionLimits{} _, err := e.GetOrderExecutionLimits(asset.Spot, btcusd) - if !errors.Is(err, ErrExchangeLimitNotLoaded) { - t.Fatalf("expected error %v but received %v", ErrExchangeLimitNotLoaded, err) - } + require.ErrorIs(t, err, ErrExchangeLimitNotLoaded) newLimits := []MinMaxLevel{ { @@ -161,45 +140,31 @@ func TestGetOrderExecutionLimits(t *testing.T) { } err = e.LoadLimits(newLimits) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", errCannotLoadLimit, err) - } + require.NoError(t, err) _, err = e.GetOrderExecutionLimits(asset.Futures, ltcusd) - if !errors.Is(err, ErrCannotValidateAsset) { - t.Fatalf("expected error %v but received %v", ErrCannotValidateAsset, err) - } + require.ErrorIs(t, err, ErrCannotValidateAsset) _, err = e.GetOrderExecutionLimits(asset.Spot, ltcusd) - if !errors.Is(err, errExchangeLimitBase) { - t.Fatalf("expected error %v but received %v", errExchangeLimitBase, err) - } + require.ErrorIs(t, err, errExchangeLimitBase) _, err = e.GetOrderExecutionLimits(asset.Spot, btcltc) - if !errors.Is(err, errExchangeLimitQuote) { - t.Fatalf("expected error %v but received %v", errExchangeLimitQuote, err) - } + require.ErrorIs(t, err, errExchangeLimitQuote) tt, err := e.GetOrderExecutionLimits(asset.Spot, btcusd) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + require.NoError(t, err) - if tt.MaximumBaseAmount != newLimits[0].MaximumBaseAmount || + assert.False(t, tt.MaximumBaseAmount != newLimits[0].MaximumBaseAmount || tt.MinimumBaseAmount != newLimits[0].MinimumBaseAmount || tt.MaxPrice != newLimits[0].MaxPrice || - tt.MinPrice != newLimits[0].MinPrice { - t.Fatal("unexpected values") - } + tt.MinPrice != newLimits[0].MinPrice) } func TestCheckLimit(t *testing.T) { t.Parallel() e := ExecutionLimits{} err := e.CheckOrderExecutionLimits(asset.Spot, btcusd, 1337, 1337, Limit) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + require.NoError(t, err) newLimits := []MinMaxLevel{ { @@ -213,97 +178,63 @@ func TestCheckLimit(t *testing.T) { } err = e.LoadLimits(newLimits) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", errCannotLoadLimit, err) - } + require.NoError(t, err) err = e.CheckOrderExecutionLimits(asset.Futures, ltcusd, 1337, 1337, Limit) - if !errors.Is(err, ErrCannotValidateAsset) { - t.Fatalf("expected error %v but received %v", ErrCannotValidateAsset, err) - } + require.ErrorIs(t, err, ErrCannotValidateAsset) err = e.CheckOrderExecutionLimits(asset.Spot, ltcusd, 1337, 1337, Limit) - if !errors.Is(err, ErrCannotValidateBaseCurrency) { - t.Fatalf("expected error %v but received %v", ErrCannotValidateBaseCurrency, err) - } + require.ErrorIs(t, err, ErrCannotValidateBaseCurrency) err = e.CheckOrderExecutionLimits(asset.Spot, btcltc, 1337, 1337, Limit) - if !errors.Is(err, ErrCannotValidateQuoteCurrency) { - t.Fatalf("expected error %v but received %v", ErrCannotValidateQuoteCurrency, err) - } + require.ErrorIs(t, err, ErrCannotValidateQuoteCurrency) err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 1337, 9, Limit) - if !errors.Is(err, ErrPriceBelowMin) { - t.Fatalf("expected error %v but received %v", ErrPriceBelowMin, err) - } + require.ErrorIs(t, err, ErrPriceBelowMin) err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 1000001, 9, Limit) - if !errors.Is(err, ErrPriceExceedsMax) { - t.Fatalf("expected error %v but received %v", ErrPriceExceedsMax, err) - } + require.ErrorIs(t, err, ErrPriceExceedsMax) err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, .5, Limit) - if !errors.Is(err, ErrAmountBelowMin) { - t.Fatalf("expected error %v but received %v", ErrAmountBelowMin, err) - } + require.ErrorIs(t, err, ErrAmountBelowMin) err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, 11, Limit) - if !errors.Is(err, ErrAmountExceedsMax) { - t.Fatalf("expected error %v but received %v", ErrAmountExceedsMax, err) - } + require.ErrorIs(t, err, ErrAmountExceedsMax) err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, 7, Limit) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + require.NoError(t, err) err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, 7, Market) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + assert.NoError(t, err) } func TestConforms(t *testing.T) { t.Parallel() var tt MinMaxLevel err := tt.Conforms(0, 0, Limit) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) tt = MinMaxLevel{ MinNotional: 100, } err = tt.Conforms(1, 1, Limit) - if !errors.Is(err, ErrNotionalValue) { - t.Fatalf("expected error %v but received %v", ErrNotionalValue, err) - } + require.ErrorIs(t, err, ErrNotionalValue) err = tt.Conforms(200, .5, Limit) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + require.NoError(t, err) tt.PriceStepIncrementSize = 0.001 err = tt.Conforms(200.0001, .5, Limit) - if !errors.Is(err, ErrPriceExceedsStep) { - t.Fatalf("expected error %v but received %v", ErrPriceExceedsStep, err) - } + require.ErrorIs(t, err, ErrPriceExceedsStep) err = tt.Conforms(200.004, .5, Limit) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + require.NoError(t, err) tt.AmountStepIncrementSize = 0.001 err = tt.Conforms(200, .0002, Limit) - if !errors.Is(err, ErrAmountExceedsStep) { - t.Fatalf("expected error %v but received %v", ErrAmountExceedsStep, err) - } + require.ErrorIs(t, err, ErrAmountExceedsStep) err = tt.Conforms(200000, .003, Limit) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received %v", nil, err) - } + require.NoError(t, err) tt.MinimumBaseAmount = 1 tt.MaximumBaseAmount = 10 @@ -311,115 +242,73 @@ func TestConforms(t *testing.T) { tt.MarketMaxQty = 9.9 err = tt.Conforms(200000, 1, Market) - if !errors.Is(err, ErrMarketAmountBelowMin) { - t.Fatalf("expected error %v but received: %v", ErrMarketAmountBelowMin, err) - } + require.ErrorIs(t, err, ErrMarketAmountBelowMin) err = tt.Conforms(200000, 10, Market) - if !errors.Is(err, ErrMarketAmountExceedsMax) { - t.Fatalf("expected error %v but received: %v", ErrMarketAmountExceedsMax, err) - } + require.ErrorIs(t, err, ErrMarketAmountExceedsMax) tt.MarketStepIncrementSize = 10 err = tt.Conforms(200000, 9.1, Market) - if !errors.Is(err, ErrMarketAmountExceedsStep) { - t.Fatalf("expected error %v but received: %v", ErrMarketAmountExceedsStep, err) - } + require.ErrorIs(t, err, ErrMarketAmountExceedsStep) tt.MarketStepIncrementSize = 1 err = tt.Conforms(200000, 9.1, Market) - if !errors.Is(err, nil) { - t.Fatalf("expected error %v but received: %v", nil, err) - } + assert.NoError(t, err) } func TestConformToDecimalAmount(t *testing.T) { t.Parallel() var tt MinMaxLevel - if !tt.ConformToDecimalAmount(decimal.NewFromFloat(1.001)).Equal(decimal.NewFromFloat(1.001)) { - t.Fatal("value should not be changed") - } + require.True(t, tt.ConformToDecimalAmount(decimal.NewFromFloat(1.001)).Equal(decimal.NewFromFloat(1.001))) tt = MinMaxLevel{} val := tt.ConformToDecimalAmount(decimal.NewFromInt(1)) - if !val.Equal(decimal.NewFromInt(1)) { // If there is no step amount set this should not change - // the inputted amount - t.Fatal("unexpected amount") - } + assert.True(t, val.Equal(decimal.NewFromInt(1))) // If there is no step amount set this should not change tt.AmountStepIncrementSize = 0.001 val = tt.ConformToDecimalAmount(decimal.NewFromFloat(1.001)) - if !val.Equal(decimal.NewFromFloat(1.001)) { - t.Error("unexpected amount", val) - } + assert.True(t, val.Equal(decimal.NewFromFloat(1.001))) val = tt.ConformToDecimalAmount(decimal.NewFromFloat(0.0001)) - if !val.IsZero() { - t.Error("unexpected amount", val) - } + assert.True(t, val.IsZero()) val = tt.ConformToDecimalAmount(decimal.NewFromFloat(0.7777)) - if !val.Equal(decimal.NewFromFloat(0.777)) { - t.Error("unexpected amount", val) - } + assert.True(t, val.Equal(decimal.NewFromFloat(0.777))) tt.AmountStepIncrementSize = 100 val = tt.ConformToDecimalAmount(decimal.NewFromInt(100)) - if !val.Equal(decimal.NewFromInt(100)) { - t.Fatal("unexpected amount", val) - } + assert.True(t, val.Equal(decimal.NewFromInt(100))) val = tt.ConformToDecimalAmount(decimal.NewFromInt(200)) - if !val.Equal(decimal.NewFromInt(200)) { - t.Fatal("unexpected amount", val) - } + assert.True(t, val.Equal(decimal.NewFromInt(200))) val = tt.ConformToDecimalAmount(decimal.NewFromInt(150)) - if !val.Equal(decimal.NewFromInt(100)) { - t.Fatal("unexpected amount", val) - } + assert.True(t, val.Equal(decimal.NewFromInt(100))) } func TestConformToAmount(t *testing.T) { t.Parallel() var tt MinMaxLevel - if tt.ConformToAmount(1.001) != 1.001 { - t.Fatal("value should not be changed") - } + require.Equal(t, 1.001, tt.ConformToAmount(1.001)) tt = MinMaxLevel{} val := tt.ConformToAmount(1) - if val != 1 { // If there is no step amount set this should not change - // the inputted amount - t.Fatal("unexpected amount") - } + assert.Equal(t, 1., val) // If there is no step amount set this should not change tt.AmountStepIncrementSize = 0.001 val = tt.ConformToAmount(1.001) - if val != 1.001 { - t.Error("unexpected amount", val) - } + assert.Equal(t, 1.001, val) val = tt.ConformToAmount(0.0001) - if val != 0 { - t.Error("unexpected amount", val) - } + assert.Zero(t, val) val = tt.ConformToAmount(0.7777) - if val != 0.777 { - t.Error("unexpected amount", val) - } + assert.Equal(t, 0.777, val) tt.AmountStepIncrementSize = 100 val = tt.ConformToAmount(100) - if val != 100 { - t.Fatal("unexpected amount", val) - } + assert.Equal(t, 100., val) val = tt.ConformToAmount(200) - if val != 200 { - t.Fatal("unexpected amount", val) - } + require.Equal(t, 200., val) val = tt.ConformToAmount(150) - if val != 100 { - t.Fatal("unexpected amount", val) - } + assert.Equal(t, 100., val) } diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index f8b3b6d5aff..0214fd793c0 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -263,8 +263,8 @@ func TestSubmit_DeriveSubmitResponse(t *testing.T) { resp, err := s.DeriveSubmitResponse("1337") require.NoError(t, err) - require.Equal(t, resp.OrderID, "1337") - require.Equal(t, resp.Status, New) + require.Equal(t, "1337", resp.OrderID) + require.Equal(t, New, resp.Status) require.False(t, resp.Date.IsZero()) assert.False(t, resp.LastUpdated.IsZero()) } @@ -286,31 +286,28 @@ func TestSubmitResponse_DeriveDetail(t *testing.T) { func TestOrderSides(t *testing.T) { t.Parallel() - var os = Buy - assert.Equal(t, os.String(), "BUY") - assert.Equal(t, os.Lower(), "buy") - assert.Equal(t, os.Title(), "Buy") + assert.Equal(t, "BUY", os.String()) + assert.Equal(t, "buy", os.Lower()) + assert.Equal(t, "Buy", os.Title()) } func TestTitle(t *testing.T) { t.Parallel() orderType := Limit - require.Equal(t, orderType.Title(), "Limit") + require.Equal(t, "Limit", orderType.Title()) } func TestOrderTypes(t *testing.T) { t.Parallel() - var orderType Type - assert.Equal(t, orderType.String(), "UNKNOWN") - assert.Equal(t, orderType.Lower(), "unknown") - assert.Equal(t, orderType.Title(), "Unknown") + assert.Equal(t, "UNKNOWN", orderType.String()) + assert.Equal(t, "unknown", orderType.Lower()) + assert.Equal(t, "Unknown", orderType.Title()) } func TestInferCostsAndTimes(t *testing.T) { t.Parallel() - var detail Detail detail.InferCostsAndTimes() if detail.Amount != detail.ExecutedAmount+detail.RemainingAmount { @@ -354,42 +351,22 @@ func TestInferCostsAndTimes(t *testing.T) { detail.Amount = 1 detail.RemainingAmount = 1 detail.InferCostsAndTimes() - if detail.Amount != detail.ExecutedAmount+detail.RemainingAmount { - t.Errorf( - "Order detail amounts not equals. Expected 0, received %f", - detail.Amount-(detail.ExecutedAmount+detail.RemainingAmount), - ) - } + assert.Equal(t, detail.ExecutedAmount+detail.RemainingAmount, detail.Amount) detail.RemainingAmount = 0 detail.Amount = 1 detail.ExecutedAmount = 1 detail.Price = 2 detail.InferCostsAndTimes() - if detail.AverageExecutedPrice != 2 { - t.Errorf( - "Unexpected AverageExecutedPrice. Expected 2, received %f", - detail.AverageExecutedPrice, - ) - } + assert.Equal(t, 2., detail.AverageExecutedPrice) detail = Detail{Amount: 1, ExecutedAmount: 2, Cost: 3, Price: 0} detail.InferCostsAndTimes() - if detail.AverageExecutedPrice != 1.5 { - t.Errorf( - "Unexpected AverageExecutedPrice. Expected 1.5, received %f", - detail.AverageExecutedPrice, - ) - } + assert.Equal(t, 1.5, detail.AverageExecutedPrice) detail = Detail{Amount: 1, ExecutedAmount: 2, AverageExecutedPrice: 3} detail.InferCostsAndTimes() - if detail.Cost != 6 { - t.Errorf( - "Unexpected Cost. Expected 6, received %f", - detail.Cost, - ) - } + assert.Equal(t, 6., detail.Cost) } func TestFilterOrdersByType(t *testing.T) { @@ -406,13 +383,13 @@ func TestFilterOrdersByType(t *testing.T) { } FilterOrdersByType(&orders, AnyType) - assert.Equalf(t, len(orders), 3, "Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) + assert.Lenf(t, orders, 3, "Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) FilterOrdersByType(&orders, Limit) - assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + assert.Lenf(t, orders, 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) FilterOrdersByType(&orders, Stop) - assert.Equalf(t, len(orders), 1, "Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) + assert.Lenf(t, orders, 1, "Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) } var filterOrdersByTypeBenchmark = &[]Detail{ @@ -452,13 +429,13 @@ func TestFilterOrdersBySide(t *testing.T) { } FilterOrdersBySide(&orders, AnySide) - assert.Equalf(t, len(orders), 3, "Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + assert.Lenf(t, orders, 3, "Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) FilterOrdersBySide(&orders, Buy) - assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + assert.Lenf(t, orders, 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) FilterOrdersBySide(&orders, Sell) - assert.Equalf(t, len(orders), 1, "Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) + assert.Lenf(t, orders, 1, "Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) } var filterOrdersBySideBenchmark = &[]Detail{ @@ -501,25 +478,25 @@ func TestFilterOrdersByTimeRange(t *testing.T) { err := FilterOrdersByTimeRange(&orders, time.Unix(0, 0), time.Unix(0, 0)) require.NoError(t, err) - assert.Equalf(t, len(orders), 3, "Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + assert.Lenf(t, orders, 3, "Orders failed to be filtered. Expected %d, received %d", 3, len(orders)) err = FilterOrdersByTimeRange(&orders, time.Unix(100, 0), time.Unix(111, 0)) require.NoError(t, err) - assert.Equalf(t, len(orders), 3, "Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + assert.Lenf(t, orders, 3, "Orders failed to be filtered. Expected %d, received %d", 3, len(orders)) err = FilterOrdersByTimeRange(&orders, time.Unix(101, 0), time.Unix(111, 0)) require.NoError(t, err) - assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) + assert.Lenf(t, orders, 2, "Orders failed to be filtered. Expected %d, received %d", 2, len(orders)) err = FilterOrdersByTimeRange(&orders, time.Unix(200, 0), time.Unix(300, 0)) require.NoError(t, err) - assert.Equalf(t, len(orders), 0, "Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) + assert.Emptyf(t, orders, "Orders failed to be filtered. Expected 0, received %d", len(orders)) orders = append(orders, Detail{}) // test for event no timestamp is set on an order, best to include it err = FilterOrdersByTimeRange(&orders, time.Unix(200, 0), time.Unix(300, 0)) require.NoError(t, err) - assert.Equalf(t, len(orders), 1, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + assert.Lenf(t, orders, 1, "Orders failed to be filtered. Expected %d, received %d", 1, len(orders)) err = FilterOrdersByTimeRange(&orders, time.Unix(300, 0), time.Unix(50, 0)) require.ErrorIs(t, err, common.ErrStartAfterEnd) @@ -545,9 +522,7 @@ var filterOrdersByTimeRangeBenchmark = &[]Detail{ func BenchmarkFilterOrdersByTimeRange(b *testing.B) { for x := 0; x < b.N; x++ { err := FilterOrdersByTimeRange(filterOrdersByTimeRangeBenchmark, time.Unix(50, 0), time.Unix(150, 0)) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } } @@ -571,28 +546,28 @@ func TestFilterOrdersByPairs(t *testing.T) { currency.NewPair(currency.LTC, currency.EUR), currency.NewPair(currency.DOGE, currency.RUB)} FilterOrdersByPairs(&orders, currencies) - assert.Equalf(t, len(orders), 4, "Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + assert.Lenf(t, orders, 4, "Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD), currency.NewPair(currency.LTC, currency.EUR)} FilterOrdersByPairs(&orders, currencies) - assert.Equalf(t, len(orders), 3, "Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) + assert.Lenf(t, orders, 3, "Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD)} FilterOrdersByPairs(&orders, currencies) - assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + assert.Lenf(t, orders, 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) currencies = []currency.Pair{currency.NewPair(currency.USD, currency.BTC)} FilterOrdersByPairs(&orders, currencies) - assert.Equalf(t, len(orders), 2, "Reverse Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + assert.Lenf(t, orders, 2, "Reverse Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) currencies = []currency.Pair{} FilterOrdersByPairs(&orders, currencies) - assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + assert.Lenf(t, orders, 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) currencies = append(currencies, currency.EMPTYPAIR) FilterOrdersByPairs(&orders, currencies) - assert.Equalf(t, len(orders), 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + assert.Lenf(t, orders, 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) } var filterOrdersByPairsBenchmark = &[]Detail{ @@ -633,10 +608,10 @@ func TestSortOrdersByPrice(t *testing.T) { } SortOrdersByPrice(&orders, false) - assert.Equalf(t, orders[0].Price, 0., "Expected: '%v', received: '%v'", 0, orders[0].Price) + assert.Equalf(t, 0., orders[0].Price, "Expected: '%v', received: '%v'", 0, orders[0].Price) SortOrdersByPrice(&orders, true) - assert.Equalf(t, orders[0].Price, 100., "Expected: '%v', received: '%v'", 100, orders[0].Price) + assert.Equalf(t, 100., orders[0].Price, "Expected: '%v', received: '%v'", 100, orders[0].Price) } func TestSortOrdersByDate(t *testing.T) { @@ -687,18 +662,10 @@ func TestSortOrdersByCurrency(t *testing.T) { } SortOrdersByCurrency(&orders, false) - if orders[0].Pair.String() != currency.BTC.String()+"-"+currency.RUB.String() { - t.Errorf("Expected: '%v', received: '%v'", - currency.BTC.String()+"-"+currency.RUB.String(), - orders[0].Pair.String()) - } + assert.Equal(t, currency.BTC.String()+"-"+currency.RUB.String(), orders[0].Pair.String()) SortOrdersByCurrency(&orders, true) - if orders[0].Pair.String() != currency.LTC.String()+"-"+currency.EUR.String() { - t.Errorf("Expected: '%v', received: '%v'", - currency.LTC.String()+"-"+currency.EUR.String(), - orders[0].Pair.String()) - } + assert.Equal(t, currency.LTC.String()+"-"+currency.EUR.String(), orders[0].Pair.String()) } func TestSortOrdersByOrderSide(t *testing.T) { @@ -717,18 +684,10 @@ func TestSortOrdersByOrderSide(t *testing.T) { } SortOrdersBySide(&orders, false) - if !strings.EqualFold(orders[0].Side.String(), Buy.String()) { - t.Errorf("Expected: '%v', received: '%v'", - Buy, - orders[0].Side) - } + assert.Truef(t, strings.EqualFold(orders[0].Side.String(), Buy.String()), "Expected: '%v', received: '%v'", Buy, orders[0].Side) SortOrdersBySide(&orders, true) - if !strings.EqualFold(orders[0].Side.String(), Sell.String()) { - t.Errorf("Expected: '%v', received: '%v'", - Sell, - orders[0].Side) - } + assert.Truef(t, strings.EqualFold(orders[0].Side.String(), Sell.String()), "Expected: '%v', received: '%v'", Sell, orders[0].Side) } func TestSortOrdersByOrderType(t *testing.T) { @@ -747,18 +706,10 @@ func TestSortOrdersByOrderType(t *testing.T) { } SortOrdersByType(&orders, false) - if !strings.EqualFold(orders[0].Type.String(), ImmediateOrCancel.String()) { - t.Errorf("Expected: '%v', received: '%v'", - ImmediateOrCancel, - orders[0].Type) - } + assert.Truef(t, strings.EqualFold(orders[0].Type.String(), ImmediateOrCancel.String()), "Expected: '%v', received: '%v'", ImmediateOrCancel, orders[0].Type) SortOrdersByType(&orders, true) - if !strings.EqualFold(orders[0].Type.String(), TrailingStop.String()) { - t.Errorf("Expected: '%v', received: '%v'", - TrailingStop, - orders[0].Type) - } + assert.Truef(t, strings.EqualFold(orders[0].Type.String(), TrailingStop.String()), "Expected: '%v', received: '%v'", TrailingStop, orders[0].Type) } func TestStringToOrderSide(t *testing.T) { @@ -962,7 +913,7 @@ func TestUpdateOrderFromModifyResponse(t *testing.T) { } od.UpdateOrderFromModifyResponse(&om) - assert.NotEqual(t, od.TimeInForce, UnknownTIF) + assert.NotEqual(t, UnknownTIF, od.TimeInForce) assert.True(t, od.PostOnly) assert.Equal(t, 1., od.Price) assert.Equal(t, 1., od.Amount) @@ -1033,7 +984,7 @@ func TestUpdateOrderFromDetail(t *testing.T) { require.NoError(t, err) assert.Equal(t, od.InternalOrderID, id) - assert.Equal(t, od.TimeInForce, GTC) + assert.Equal(t, GTC, od.TimeInForce) require.True(t, od.HiddenOrder) assert.True(t, od.PostOnly) assert.Equal(t, 1., od.Leverage) @@ -1098,13 +1049,9 @@ func TestUpdateOrderFromDetail(t *testing.T) { func TestClassificationError_Error(t *testing.T) { class := ClassificationError{OrderID: "1337", Exchange: "test", Err: errors.New("test error")} - if class.Error() != "Exchange test: OrderID: 1337 classification error: test error" { - t.Fatal("unexpected output") - } + require.Equal(t, "Exchange test: OrderID: 1337 classification error: test error", class.Error()) class.OrderID = "" - if class.Error() != "Exchange test: classification error: test error" { - t.Fatal("unexpected output") - } + assert.Equal(t, "Exchange test: classification error: test error", class.Error()) } func TestValidationOnOrderTypes(t *testing.T) { @@ -1116,20 +1063,16 @@ func TestValidationOnOrderTypes(t *testing.T) { assert.NoError(t, err) err = cancelMe.Validate(cancelMe.PairAssetRequired()) - if err == nil || err.Error() != ErrPairIsEmpty.Error() { - t.Errorf("received '%v' expected '%v'", err, ErrPairIsEmpty) - } + assert.Falsef(t, err == nil || err.Error() != ErrPairIsEmpty.Error(), "received '%v' expected '%v'", err, ErrPairIsEmpty) cancelMe.Pair = currency.NewPair(currency.BTC, currency.USDT) err = cancelMe.Validate(cancelMe.PairAssetRequired()) - if err == nil || err.Error() != ErrAssetNotSet.Error() { - t.Errorf("received '%v' expected '%v'", err, ErrAssetNotSet) - } + assert.Falsef(t, err == nil || err.Error() != ErrAssetNotSet.Error(), "received '%v' expected '%v'", err, ErrAssetNotSet) cancelMe.AssetType = asset.Spot err = cancelMe.Validate(cancelMe.PairAssetRequired()) assert.NoError(t, err) - require.NotNil(t, cancelMe.Validate(cancelMe.StandardCancel())) + require.Error(t, cancelMe.Validate(cancelMe.StandardCancel())) require.NoError(t, cancelMe.Validate(validate.Check(func() error { return nil @@ -1182,7 +1125,7 @@ func TestValidationOnOrderTypes(t *testing.T) { modifyOrder.ClientOrderID = "1337" require.NoError(t, modifyOrder.Validate()) - require.NotNil(t, modifyOrder.Validate(validate.Check(func() error { return errors.New("this should error") }))) + require.Error(t, modifyOrder.Validate(validate.Check(func() error { return errors.New("this should error") }))) require.NoError(t, modifyOrder.Validate(validate.Check(func() error { return nil }))) } @@ -1308,9 +1251,7 @@ func TestIsActive(t *testing.T) { } // specific tests for num, tt := range amountTests { - if tt.o.IsActive() != tt.expectedResult { - t.Errorf("amountTests[%v] failed", num) - } + assert.Equalf(t, tt.expectedResult, tt.o.IsActive(), "amountTests[%v] failed", num) } statusTests := map[int]struct { @@ -1350,9 +1291,7 @@ var activeBenchmark = Detail{Status: Pending, Amount: 1} // 1000000000 1.188 ns/op 0 B/op 0 allocs/op // CURRENT func BenchmarkIsActive(b *testing.B) { for x := 0; x < b.N; x++ { - if !activeBenchmark.IsActive() { - b.Fatal("expected true") - } + require.True(b, activeBenchmark.IsActive()) } } @@ -1375,9 +1314,7 @@ func TestIsInactive(t *testing.T) { } // specific tests for num, tt := range amountTests { - if tt.o.IsInactive() != tt.expectedResult { - t.Errorf("amountTests[%v] failed", num) - } + assert.Equalf(t, tt.expectedResult, tt.o.IsInactive(), "amountTests[%v] failed", num) } statusTests := map[int]struct { @@ -1407,9 +1344,7 @@ func TestIsInactive(t *testing.T) { } // specific tests for num, tt := range statusTests { - if tt.o.IsInactive() != tt.expectedResult { - t.Errorf("statusTests[%v] failed", num) - } + assert.Equalf(t, tt.expectedResult, tt.o.IsInactive(), "statusTests[%v] failed", num) } } @@ -1418,9 +1353,7 @@ var inactiveBenchmark = Detail{Status: Closed, Amount: 1} // 1000000000 1.043 ns/op 0 B/op 0 allocs/op // CURRENT func BenchmarkIsInactive(b *testing.B) { for x := 0; x < b.N; x++ { - if !inactiveBenchmark.IsInactive() { - b.Fatal("expected true") - } + assert.True(b, inactiveBenchmark.IsInactive()) } } @@ -1453,31 +1386,23 @@ func TestIsOrderPlaced(t *testing.T) { for num, tt := range statusTests { t.Run(fmt.Sprintf("TEST CASE: %d", num), func(t *testing.T) { t.Parallel() - if tt.o.WasOrderPlaced() != tt.expectedResult { - t.Errorf("statusTests[%v] failed", num) - } + assert.Equalf(t, tt.expectedResult, tt.o.WasOrderPlaced(), "statusTests[%v] failed", num) }) } } func TestGenerateInternalOrderID(t *testing.T) { id, err := uuid.NewV4() - if err != nil { - t.Errorf("unable to create uuid: %s", err) - } + assert.NoError(t, err) od := Detail{ InternalOrderID: id, } od.GenerateInternalOrderID() - if od.InternalOrderID != id { - t.Error("Should not be able to generate a new internal order ID") - } + assert.Equal(t, id, od.InternalOrderID, "Should not be able to generate a new internal order ID") od = Detail{} od.GenerateInternalOrderID() - if od.InternalOrderID.IsNil() { - t.Error("unable to generate internal order ID") - } + assert.False(t, od.InternalOrderID.IsNil(), "unable to generate internal order ID") } func TestDetail_Copy(t *testing.T) { @@ -1495,13 +1420,9 @@ func TestDetail_Copy(t *testing.T) { } for i := range d { r := d[i].Copy() - if !reflect.DeepEqual(d[i], r) { - t.Errorf("[%d] Copy does not contain same elements, expected: %v\ngot:%v", i, d[i], r) - } + assert.True(t, reflect.DeepEqual(d[i], r), "[%d] Copy does not contain same elements, expected: %v\ngot:%v", i, d[i], r) if len(d[i].Trades) > 0 { - if &d[i].Trades[0] == &r.Trades[0] { - t.Errorf("[%d]Trades point to the same data elements", i) - } + assert.Equalf(t, &d[i].Trades[0], &r.Trades[0], "[%d]Trades point to the same data elements", i) } } } @@ -1521,13 +1442,9 @@ func TestDetail_CopyToPointer(t *testing.T) { } for i := range d { r := d[i].CopyToPointer() - if !reflect.DeepEqual(d[i], *r) { - t.Errorf("[%d] Copy does not contain same elements, expected: %v\ngot:%v", i, d[i], r) - } + assert.Truef(t, reflect.DeepEqual(d[i], *r), "[%d] Copy does not contain same elements, expected: %v\ngot:%v", i, d[i], r) if len(d[i].Trades) > 0 { - if &d[i].Trades[0] == &r.Trades[0] { - t.Errorf("[%d]Trades point to the same data elements", i) - } + assert.Equalf(t, &d[i].Trades[0], &r.Trades[0], "[%d]Trades point to the same data elements", i) } } } @@ -1548,13 +1465,9 @@ func TestDetail_CopyPointerOrderSlice(t *testing.T) { sliceCopy := CopyPointerOrderSlice(d) for i := range sliceCopy { - if !reflect.DeepEqual(*sliceCopy[i], *d[i]) { - t.Errorf("[%d] Copy does not contain same elements, expected: %v\ngot:%v", i, sliceCopy[i], d[i]) - } + assert.Truef(t, reflect.DeepEqual(*sliceCopy[i], *d[i]), "[%d] Copy does not contain same elements, expected: %v\ngot:%v", i, sliceCopy[i], d[i]) if len(sliceCopy[i].Trades) > 0 { - if &sliceCopy[i].Trades[0] == &d[i].Trades[0] { - t.Errorf("[%d]Trades point to the same data elements", i) - } + assert.Equalf(t, &sliceCopy[i].Trades[0], &d[i].Trades[0], "[%d]Trades point to the same data elements", i) } } } @@ -1581,15 +1494,13 @@ func TestDeriveModify(t *testing.T) { require.NoError(t, err) require.NotNil(t, mod) - if mod.Exchange != "wow" || + assert.False(t, mod.Exchange != "wow" || mod.OrderID != "wow2" || mod.ClientOrderID != "wow3" || mod.Type != Market || mod.Side != Long || mod.AssetType != asset.Futures || - !mod.Pair.Equal(pair) { - t.Fatal("unexpected values") - } + !mod.Pair.Equal(pair)) } func TestDeriveModifyResponse(t *testing.T) { @@ -1614,15 +1525,13 @@ func TestDeriveModifyResponse(t *testing.T) { require.NoError(t, err) require.NotNil(t, modresp) - if modresp.Exchange != "wow" || + assert.False(t, modresp.Exchange != "wow" || modresp.OrderID != "wow2" || modresp.ClientOrderID != "wow3" || modresp.Type != Market || modresp.Side != Long || modresp.AssetType != asset.Futures || - !modresp.Pair.Equal(pair) { - t.Fatal("unexpected values") - } + !modresp.Pair.Equal(pair)) } func TestDeriveCancel(t *testing.T) { @@ -1648,7 +1557,7 @@ func TestDeriveCancel(t *testing.T) { cancel, err := o.DeriveCancel() require.NoError(t, err) - if cancel.Exchange != "wow" || + assert.False(t, cancel.Exchange != "wow" || cancel.OrderID != "wow1" || cancel.AccountID != "wow2" || cancel.ClientID != "wow3" || @@ -1657,9 +1566,7 @@ func TestDeriveCancel(t *testing.T) { cancel.Type != Market || cancel.Side != Long || !cancel.Pair.Equal(pair) || - cancel.AssetType != asset.Futures { - t.Fatalf("unexpected values %+v", cancel) - } + cancel.AssetType != asset.Futures) } func TestGetOrdersRequest_Filter(t *testing.T) { From 745977bd0e53f82f2a7d8f810a9f5010e87fb8cb Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Fri, 21 Feb 2025 00:16:06 +0300 Subject: [PATCH 11/20] update kucoin and deribit wrapper to match the time in force change --- exchanges/deribit/deribit_wrapper.go | 13 +++++- exchanges/kucoin/kucoin_wrapper.go | 64 +++++++++++++++++++++++++++- exchanges/order/order_types.go | 2 - 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index a71bcdc2b15..55076c389ab 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -787,10 +787,21 @@ func (d *Deribit) GetOrderInfo(ctx context.Context, orderID string, _ currency.P return nil, fmt.Errorf("%v: orderStatus %s not supported", d.Name, orderInfo.OrderState) } } + var tif order.TimeInForce + switch orderInfo.TimeInForce { + case "good_til_cancelled": + tif = order.GTC + case "good_til_day": + tif = order.GTD + case "fill_or_kill": + tif = order.FOK + case "immediate_or_cancel": + tif = order.IOC + } return &order.Detail{ AssetType: assetType, Exchange: d.Name, - PostOnly: orderInfo.PostOnly, + TimeInForce: tif, Price: orderInfo.Price, Amount: orderInfo.Amount, ExecutedAmount: orderInfo.FilledAmount, diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index 7e42bef7b39..a5e7f235181 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -668,7 +668,7 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm Leverage: s.Leverage, VisibleSize: 0, ReduceOnly: s.ReduceOnly, - PostOnly: s.PostOnly, + PostOnly: s.TimeInForce == order.PostOnlyGTC, Hidden: s.Hidden, Stop: stopOrderBoundary, StopPrice: s.TriggerPrice, @@ -721,7 +721,7 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm s.Pair.String(), oType.Lower(), "", stopType, "", SpotTradeType, timeInForce, s.Amount, s.Price, stopPrice, 0, - 0, 0, s.PostOnly, s.Hidden, s.Iceberg) + 0, 0, s.TimeInForce == order.PostOnlyGTC || s.Type == order.PostOnly, s.Hidden, s.Iceberg) if err != nil { return nil, err } @@ -1020,6 +1020,17 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc } else { oStatus = order.Closed } + var tif order.TimeInForce + switch orderDetail.TimeInForce { + case "GTT": + tif = order.GTT + case "IOC": + tif = order.IOC + case "FOK": + tif = order.FOK + default: + tif = order.GTC + } return &order.Detail{ Exchange: ku.Name, OrderID: orderDetail.ID, @@ -1033,6 +1044,7 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc Price: orderDetail.Price, Date: orderDetail.CreatedAt.Time(), HiddenOrder: orderDetail.Hidden, + TimeInForce: tif, PostOnly: orderDetail.PostOnly, ReduceOnly: orderDetail.ReduceOnly, Leverage: orderDetail.Leverage, @@ -1090,6 +1102,17 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc } else { remainingSize = orderDetail.Size.Float64() - orderDetail.DealSize.Float64() } + var tif order.TimeInForce + switch orderDetail.TimeInForce { + case "GTT": + tif = order.GTT + case "IOC": + tif = order.IOC + case "FOK": + tif = order.FOK + default: + tif = order.GTC + } return &order.Detail{ Exchange: ku.Name, OrderID: orderDetail.ID, @@ -1104,6 +1127,7 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc Price: orderDetail.Price.Float64(), Date: orderDetail.CreatedAt.Time(), HiddenOrder: orderDetail.Hidden, + TimeInForce: tif, PostOnly: orderDetail.PostOnly, AverageExecutedPrice: orderDetail.Price.Float64(), FeeAsset: currency.NewCode(orderDetail.FeeCurrency), @@ -1262,6 +1286,17 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M if err != nil { return nil, err } + var tif order.TimeInForce + switch futuresOrders.Items[x].TimeInForce { + case "GTT": + tif = order.GTT + case "IOC": + tif = order.IOC + case "FOK": + tif = order.FOK + default: + tif = order.GTC + } orders = append(orders, order.Detail{ OrderID: futuresOrders.Items[x].ID, ClientOrderID: futuresOrders.Items[x].ClientOid, @@ -1276,6 +1311,7 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M Side: side, Type: oType, Pair: dPair, + TimeInForce: tif, PostOnly: futuresOrders.Items[x].PostOnly, ReduceOnly: futuresOrders.Items[x].ReduceOnly, Status: status, @@ -1360,6 +1396,17 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M if err != nil { return nil, err } + var tif order.TimeInForce + switch response.Items[a].TimeInForce { + case "GTT": + tif = order.GTT + case "IOC": + tif = order.IOC + case "FOK": + tif = order.FOK + default: + tif = order.GTC + } orders = append(orders, order.Detail{ OrderID: response.Items[a].ID, ClientOrderID: response.Items[a].ClientOID, @@ -1372,6 +1419,7 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M Side: side, Type: order.Stop, Pair: dPair, + TimeInForce: tif, PostOnly: response.Items[a].PostOnly, Status: status, AssetType: getOrdersRequest.AssetType, @@ -1597,6 +1645,17 @@ func (ku *Kucoin) GetOrderHistory(ctx context.Context, getOrdersRequest *order.M if err != nil { return nil, err } + var tif order.TimeInForce + switch response.Items[a].TimeInForce { + case "GTT": + tif = order.GTT + case "IOC": + tif = order.IOC + case "FOK": + tif = order.FOK + default: + tif = order.GTC + } orders = append(orders, order.Detail{ OrderID: response.Items[a].ID, ClientOrderID: response.Items[a].ClientOID, @@ -1610,6 +1669,7 @@ func (ku *Kucoin) GetOrderHistory(ctx context.Context, getOrdersRequest *order.M Side: side, Type: order.Stop, Pair: dPair, + TimeInForce: tif, PostOnly: response.Items[a].PostOnly, Status: status, AssetType: getOrdersRequest.AssetType, diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index c68049209be..3704c840e3e 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -118,8 +118,6 @@ type SubmitResponse struct { TriggerPrice float64 ClientID string ClientOrderID string - ImmediateOrCancel bool - FillOrKill bool AverageExecutedPrice float64 LastUpdated time.Time From b05fe7fd378ec552e319f04dac14ac80e2747543 Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Fri, 21 Feb 2025 01:18:21 +0300 Subject: [PATCH 12/20] fix time-in-force related test errors --- exchanges/gateio/gateio_test.go | 6 +++--- exchanges/gateio/gateio_wrapper.go | 29 +++++++++++++++++------------ exchanges/kucoin/kucoin_wrapper.go | 4 ++-- exchanges/order/order_test.go | 3 +++ exchanges/order/order_types.go | 5 +++-- exchanges/order/orders.go | 5 +++++ 6 files changed, 33 insertions(+), 19 deletions(-) diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index e55a667ee0d..844e0418c6c 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -3417,14 +3417,14 @@ func TestGetFutureOrderSize(t *testing.T) { func TestGetTimeInForce(t *testing.T) { t.Parallel() - _, err := getTimeInForce(&order.Submit{Type: order.Market, PostOnly: true}) - assert.ErrorIs(t, err, errPostOnlyOrderTypeUnsupported) + _, err := getTimeInForce(&order.Submit{Type: order.Market, TimeInForce: order.PostOnlyGTC}) + 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, PostOnly: true}) + ret, err = getTimeInForce(&order.Submit{Type: order.Limit, TimeInForce: order.POC}) require.NoError(t, err) assert.Equal(t, "poc", ret) diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index bfb22abe5cf..35845bda045 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -2539,23 +2539,28 @@ func getFutureOrderSize(s *order.Submit) (float64, error) { } } -var errPostOnlyOrderTypeUnsupported = errors.New("post only is only supported for limit orders") - // getTimeInForce returns the time in force for a given order. If Market order // IOC func getTimeInForce(s *order.Submit) (string, error) { - timeInForce := "gtc" // limit order taker/maker - if s.Type == order.Market || s.TimeInForce == order.IOC { + var timeInForce string + switch s.TimeInForce { + case order.IOC: timeInForce = "ioc" // market taker only - } - if s.PostOnly { - if s.Type != order.Limit { - return "", fmt.Errorf("%w not for %v", errPostOnlyOrderTypeUnsupported, s.Type) + case order.FOK: + timeInForce = "fok" + case order.POC: + timeInForce = "poc" + case order.GTC: + timeInForce = "gtc" + case order.UnsetTIF: + switch s.Type { + case order.Market: + timeInForce = "ioc" + case order.Limit: + timeInForce = "gtc" } - timeInForce = "poc" // limit order maker only - } - if s.TimeInForce == order.IOC { - timeInForce = "fok" // market order entire fill or kill + default: + return timeInForce, fmt.Errorf("%w: time-in-force value of %s", order.ErrInvalidTimeInForce, timeInForce) } return timeInForce, nil } diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index a5e7f235181..37f8e3d37cf 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -668,7 +668,7 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm Leverage: s.Leverage, VisibleSize: 0, ReduceOnly: s.ReduceOnly, - PostOnly: s.TimeInForce == order.PostOnlyGTC, + PostOnly: s.PostOnly, Hidden: s.Hidden, Stop: stopOrderBoundary, StopPrice: s.TriggerPrice, @@ -721,7 +721,7 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm s.Pair.String(), oType.Lower(), "", stopType, "", SpotTradeType, timeInForce, s.Amount, s.Price, stopPrice, 0, - 0, 0, s.TimeInForce == order.PostOnlyGTC || s.Type == order.PostOnly, s.Hidden, s.Iceberg) + 0, 0, s.PostOnly, s.Hidden, s.Iceberg) if err != nil { return nil, err } diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 64169c77f8d..0ba7fd7741c 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -1735,6 +1735,8 @@ func TestStringToTimeInForce(t *testing.T) { "GOOD_TILL_DAY": {TIF: GTD}, "GOODtillday": {TIF: GTD}, "abcdfeg": {TIF: UnknownTIF, Error: ErrInvalidTimeInForce}, + "PoC": {TIF: POC}, + "PendingORCANCEL": {TIF: POC}, } for tk := range timeInForceStringToValueMap { @@ -1752,6 +1754,7 @@ func TestString(t *testing.T) { GTT: "GTT", FOK: "FOK", PostOnlyGTC: "POST_ONLY_GOOD_TIL_CANCELLED", + POC: "POC", UnknownTIF: "UNKNOWN", UnsetTIF: "", } diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 3704c840e3e..5d43ab695bf 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -407,7 +407,7 @@ const ( ) // TimeInForce enforces a standard for time-in-force values across the code base. -type TimeInForce uint8 +type TimeInForce uint16 // TimeInForce types const ( @@ -417,10 +417,11 @@ const ( GTT // GTT represents GoodTillTime FOK // FOK represents FillOrKill IOC // IOC represents ImmediateOrCancel + POC // POC represents PendingOrCancel PostOnlyGTC // PostOnlyGCT represents PostOnlyGoodTilCancelled UnknownTIF - supportedTimeInForceFlag = GTC | GTD | GTT | FOK | IOC | PostOnlyGTC + supportedTimeInForceFlag = GTC | GTD | GTT | FOK | IOC | POC | PostOnlyGTC ) // ByPrice used for sorting orders by price diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 21048d95a3d..f269316b01e 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -747,6 +747,9 @@ func (t TimeInForce) String() string { return "GTT" case FOK: return "FOK" + case POC: + // Added in GateIO exchange to represent Pending or Cancel + return "POC" case PostOnlyGTC: // Added in Bittrex exchange to represent PostOnly and GTC return "POST_ONLY_GOOD_TIL_CANCELLED" @@ -1267,6 +1270,8 @@ func StringToTimeInForce(timeInForce string) (TimeInForce, error) { return FOK, nil case "POST_ONLY_GOOD_TILL_CANCELLED", PostOnlyGTC.String(): return PostOnlyGTC, nil + case "POC", "PENDINGORCANCEL": + return POC, nil case "": return UnsetTIF, nil default: From 2085156c90e70d31072ffc62a632c3b057d04950 Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Fri, 21 Feb 2025 01:55:14 +0300 Subject: [PATCH 13/20] linter issue fix --- exchanges/kucoin/kucoin.go | 2 +- exchanges/kucoin/kucoin_wrapper.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exchanges/kucoin/kucoin.go b/exchanges/kucoin/kucoin.go index 78a4c06fcb3..9d493ee3f77 100644 --- a/exchanges/kucoin/kucoin.go +++ b/exchanges/kucoin/kucoin.go @@ -1125,7 +1125,7 @@ func (ku *Kucoin) PostStopOrder(ctx context.Context, clientOID, side, symbol, or if timeInForce != "" { arg["timeInForce"] = timeInForce } - if cancelAfter > 0 && timeInForce == "GTT" { + if cancelAfter > 0 && timeInForce == order.GTT.String() { arg["cancelAfter"] = strconv.FormatFloat(cancelAfter, 'f', -1, 64) } arg["postOnly"] = postOnly diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index 37f8e3d37cf..bbec4abdb56 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -693,12 +693,12 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm if oType == order.Limit { switch { case s.TimeInForce == order.FOK: - timeInForce = "FOK" + timeInForce = order.FOK.String() case s.TimeInForce == order.IOC: - timeInForce = "IOC" + timeInForce = order.IOC.String() case s.PostOnly: default: - timeInForce = "GTC" + timeInForce = order.GTC.String() } } var stopType string From 889c7cc2f2fc3a5aad7b6610687e09285ae9a0a8 Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:44:34 +0300 Subject: [PATCH 14/20] time in force constants, functions and unit tests update --- exchanges/binance/binance_test.go | 2 +- exchanges/binance/binance_wrapper.go | 2 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 2 +- exchanges/deribit/deribit_wrapper.go | 10 +-- exchanges/gateio/gateio_test.go | 2 +- exchanges/gateio/gateio_wrapper.go | 4 +- exchanges/kraken/kraken_wrapper.go | 2 +- exchanges/kucoin/kucoin.go | 2 +- exchanges/kucoin/kucoin_wrapper.go | 80 +++++--------------- exchanges/order/order_test.go | 59 +++++++++------ exchanges/order/order_types.go | 17 ++--- exchanges/order/orders.go | 34 ++++----- exchanges/poloniex/poloniex_wrapper.go | 2 +- 13 files changed, 93 insertions(+), 125 deletions(-) diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 32661597786..fb38f20541e 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -1420,7 +1420,7 @@ func TestNewOrderTest(t *testing.T) { TradeType: BinanceRequestParamsOrderLimit, Price: 0.0025, Quantity: 100000, - TimeInForce: order.GTC.String(), + TimeInForce: order.GoodTillCancel.String(), } err := b.NewOrderTest(context.Background(), req) diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index de68d6f2a60..dcb7793576e 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -882,7 +882,7 @@ func (b *Binance) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm } else { sideType = order.Sell.String() } - timeInForce := order.GTC.String() + timeInForce := order.GoodTillCancel.String() var requestParamsOrderType RequestParamsOrderType switch s.Type { case order.Market: diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index c53746acf02..452830963bf 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -429,7 +429,7 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. fPair.String(), "") case order.Limit: - timeInForce := order.GTC.String() + timeInForce := order.GoodTillCancel.String() if s.TimeInForce == order.IOC { timeInForce = order.IOC.String() } diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index 55076c389ab..e8fa1d582e6 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -593,10 +593,10 @@ func (d *Deribit) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm } var timeInForce string switch s.TimeInForce { - case order.GTC: + case order.GoodTillCancel: timeInForce = "good_til_cancelled" - case order.GTD: - timeInForce = strings.ToLower(order.GTD.String()) + case order.GoodTillDay: + timeInForce = "good_till_day" case order.FOK: timeInForce = "fill_or_kill" case order.IOC: @@ -790,9 +790,9 @@ func (d *Deribit) GetOrderInfo(ctx context.Context, orderID string, _ currency.P var tif order.TimeInForce switch orderInfo.TimeInForce { case "good_til_cancelled": - tif = order.GTC + tif = order.GoodTillCancel case "good_til_day": - tif = order.GTD + tif = order.GoodTillDay case "fill_or_kill": tif = order.FOK case "immediate_or_cancel": diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index bde763ac458..dee849a5b32 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -3424,7 +3424,7 @@ func TestGetTimeInForce(t *testing.T) { require.NoError(t, err) assert.Equal(t, "ioc", ret) - ret, err = getTimeInForce(&order.Submit{Type: order.Limit, TimeInForce: order.POC}) + ret, err = getTimeInForce(&order.Submit{Type: order.Limit, TimeInForce: order.PostOnlyGTC}) require.NoError(t, err) assert.Equal(t, "poc", ret) diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 35845bda045..2b89f743306 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -2548,9 +2548,9 @@ func getTimeInForce(s *order.Submit) (string, error) { timeInForce = "ioc" // market taker only case order.FOK: timeInForce = "fok" - case order.POC: + case order.PostOnlyGTC: timeInForce = "poc" - case order.GTC: + case order.GoodTillCancel: timeInForce = "gtc" case order.UnsetTIF: switch s.Type { diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 055d97fb3c2..3a1c31624a2 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -716,7 +716,7 @@ func (k *Kraken) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi status := order.New switch s.AssetType { case asset.Spot: - timeInForce := order.GTC.String() + timeInForce := order.GoodTillCancel.String() if s.TimeInForce == order.IOC { timeInForce = s.TimeInForce.String() } diff --git a/exchanges/kucoin/kucoin.go b/exchanges/kucoin/kucoin.go index 9d493ee3f77..646a78e63da 100644 --- a/exchanges/kucoin/kucoin.go +++ b/exchanges/kucoin/kucoin.go @@ -1125,7 +1125,7 @@ func (ku *Kucoin) PostStopOrder(ctx context.Context, clientOID, side, symbol, or if timeInForce != "" { arg["timeInForce"] = timeInForce } - if cancelAfter > 0 && timeInForce == order.GTT.String() { + if cancelAfter > 0 && timeInForce == order.GoodTillTime.String() { arg["cancelAfter"] = strconv.FormatFloat(cancelAfter, 'f', -1, 64) } arg["postOnly"] = postOnly diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index bbec4abdb56..f4a19169311 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -698,7 +698,7 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm timeInForce = order.IOC.String() case s.PostOnly: default: - timeInForce = order.GTC.String() + timeInForce = order.GoodTillCancel.String() } } var stopType string @@ -1020,17 +1020,6 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc } else { oStatus = order.Closed } - var tif order.TimeInForce - switch orderDetail.TimeInForce { - case "GTT": - tif = order.GTT - case "IOC": - tif = order.IOC - case "FOK": - tif = order.FOK - default: - tif = order.GTC - } return &order.Detail{ Exchange: ku.Name, OrderID: orderDetail.ID, @@ -1044,7 +1033,7 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc Price: orderDetail.Price, Date: orderDetail.CreatedAt.Time(), HiddenOrder: orderDetail.Hidden, - TimeInForce: tif, + TimeInForce: StringToTimeInForce(orderDetail.TimeInForce), PostOnly: orderDetail.PostOnly, ReduceOnly: orderDetail.ReduceOnly, Leverage: orderDetail.Leverage, @@ -1102,17 +1091,6 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc } else { remainingSize = orderDetail.Size.Float64() - orderDetail.DealSize.Float64() } - var tif order.TimeInForce - switch orderDetail.TimeInForce { - case "GTT": - tif = order.GTT - case "IOC": - tif = order.IOC - case "FOK": - tif = order.FOK - default: - tif = order.GTC - } return &order.Detail{ Exchange: ku.Name, OrderID: orderDetail.ID, @@ -1127,7 +1105,7 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc Price: orderDetail.Price.Float64(), Date: orderDetail.CreatedAt.Time(), HiddenOrder: orderDetail.Hidden, - TimeInForce: tif, + TimeInForce: StringToTimeInForce(orderDetail.TimeInForce), PostOnly: orderDetail.PostOnly, AverageExecutedPrice: orderDetail.Price.Float64(), FeeAsset: currency.NewCode(orderDetail.FeeCurrency), @@ -1286,17 +1264,6 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M if err != nil { return nil, err } - var tif order.TimeInForce - switch futuresOrders.Items[x].TimeInForce { - case "GTT": - tif = order.GTT - case "IOC": - tif = order.IOC - case "FOK": - tif = order.FOK - default: - tif = order.GTC - } orders = append(orders, order.Detail{ OrderID: futuresOrders.Items[x].ID, ClientOrderID: futuresOrders.Items[x].ClientOid, @@ -1311,7 +1278,7 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M Side: side, Type: oType, Pair: dPair, - TimeInForce: tif, + TimeInForce: StringToTimeInForce(futuresOrders.Items[x].TimeInForce), PostOnly: futuresOrders.Items[x].PostOnly, ReduceOnly: futuresOrders.Items[x].ReduceOnly, Status: status, @@ -1396,17 +1363,6 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M if err != nil { return nil, err } - var tif order.TimeInForce - switch response.Items[a].TimeInForce { - case "GTT": - tif = order.GTT - case "IOC": - tif = order.IOC - case "FOK": - tif = order.FOK - default: - tif = order.GTC - } orders = append(orders, order.Detail{ OrderID: response.Items[a].ID, ClientOrderID: response.Items[a].ClientOID, @@ -1419,7 +1375,7 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M Side: side, Type: order.Stop, Pair: dPair, - TimeInForce: tif, + TimeInForce: StringToTimeInForce(response.Items[a].TimeInForce), PostOnly: response.Items[a].PostOnly, Status: status, AssetType: getOrdersRequest.AssetType, @@ -1645,17 +1601,6 @@ func (ku *Kucoin) GetOrderHistory(ctx context.Context, getOrdersRequest *order.M if err != nil { return nil, err } - var tif order.TimeInForce - switch response.Items[a].TimeInForce { - case "GTT": - tif = order.GTT - case "IOC": - tif = order.IOC - case "FOK": - tif = order.FOK - default: - tif = order.GTC - } orders = append(orders, order.Detail{ OrderID: response.Items[a].ID, ClientOrderID: response.Items[a].ClientOID, @@ -1669,7 +1614,7 @@ func (ku *Kucoin) GetOrderHistory(ctx context.Context, getOrdersRequest *order.M Side: side, Type: order.Stop, Pair: dPair, - TimeInForce: tif, + TimeInForce: StringToTimeInForce(response.Items[a].TimeInForce), PostOnly: response.Items[a].PostOnly, Status: status, AssetType: getOrdersRequest.AssetType, @@ -2515,3 +2460,16 @@ func (ku *Kucoin) GetCurrencyTradeURL(_ context.Context, a asset.Item, cp curren return "", fmt.Errorf("%w %v", asset.ErrNotSupported, a) } } + +// StringToTimeInForce returns an order.TimeInForder instance from string +func StringToTimeInForce(tif string) order.TimeInForce { + switch tif { + case "GTT": + return order.GoodTillTime + case "IOC": + return order.IOC + case "FOK": + return order.FOK + } + return order.GoodTillCancel +} diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 0ba7fd7741c..afd13abbc65 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -961,7 +961,7 @@ func TestUpdateOrderFromDetail(t *testing.T) { require.ErrorIs(t, err, ErrOrderDetailIsNil) om := &Detail{ - TimeInForce: GTC, + TimeInForce: GoodTillCancel, HiddenOrder: true, PostOnly: true, Leverage: 1, @@ -999,7 +999,7 @@ func TestUpdateOrderFromDetail(t *testing.T) { require.NoError(t, err) assert.Equal(t, od.InternalOrderID, id) - assert.Equal(t, GTC, od.TimeInForce) + assert.Equal(t, GoodTillCancel, od.TimeInForce) require.True(t, od.HiddenOrder) assert.True(t, od.PostOnly) assert.Equal(t, 1., od.Leverage) @@ -1306,7 +1306,9 @@ var activeBenchmark = Detail{Status: Pending, Amount: 1} // 1000000000 1.188 ns/op 0 B/op 0 allocs/op // CURRENT func BenchmarkIsActive(b *testing.B) { for x := 0; x < b.N; x++ { - require.True(b, activeBenchmark.IsActive()) + if !activeBenchmark.IsActive() { + b.Fatal("expected true") + } } } @@ -1696,9 +1698,9 @@ func TestIsValid(t *testing.T) { var timeInForceValidityMap = map[TimeInForce]bool{ TimeInForce(1): false, IOC: true, - GTT: true, - GTC: true, - GTD: true, + GoodTillTime: true, + GoodTillCancel: true, + GoodTillDay: true, FOK: true, PostOnlyGTC: true, UnsetTIF: true, @@ -1718,10 +1720,10 @@ func TestStringToTimeInForce(t *testing.T) { Error error }{ "Unknown": {TIF: UnknownTIF, Error: ErrInvalidTimeInForce}, - "GoodTillCancel": {TIF: GTC}, - "GOOD_TILL_CANCELED": {TIF: GTC}, - "GTT": {TIF: GTT}, - "GOOD_TIL_TIME": {TIF: GTT}, + "GoodTillCancel": {TIF: GoodTillCancel}, + "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}, @@ -1730,13 +1732,14 @@ func TestStringToTimeInForce(t *testing.T) { "immediate_or_cancel": {TIF: IOC}, "IMMEDIATE_OR_CANCEL": {TIF: IOC}, "IMMEDIATEORCANCEL": {TIF: IOC}, - "GOOD_TILL_CANCELLED": {TIF: GTC}, - "good_till_day": {TIF: GTD}, - "GOOD_TILL_DAY": {TIF: GTD}, - "GOODtillday": {TIF: GTD}, + "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: POC}, - "PendingORCANCEL": {TIF: POC}, + "PoC": {TIF: IOC}, + "PendingORCANCEL": {TIF: IOC}, } for tk := range timeInForceStringToValueMap { @@ -1749,20 +1752,28 @@ func TestStringToTimeInForce(t *testing.T) { func TestString(t *testing.T) { t.Parallel() valMap := map[TimeInForce]string{ - IOC: "IOC", - GTC: "GTC", - GTT: "GTT", - FOK: "FOK", - PostOnlyGTC: "POST_ONLY_GOOD_TIL_CANCELLED", - POC: "POC", - UnknownTIF: "UNKNOWN", - UnsetTIF: "", + IOC: "IOC", + GoodTillCancel: "GTC", + GoodTillTime: "GTT", + GoodTillDay: "GTD", + FOK: "FOK", + PostOnlyGTC: "POST_ONLY_GOOD_TIL_CANCELLED", + UnknownTIF: "UNKNOWN", + UnsetTIF: "", } for x := range valMap { result := x.String() assert.Equalf(t, result, valMap[x], "expected %v, got %v", x, result) } } + +func TestIsIOC(t *testing.T) { + t.Parallel() + require.True(t, IOC.IsIOC()) + require.False(t, FOK.IsIOC()) + require.False(t, TimeInForce(0).IsIOC()) +} + func TestSideMarshalJSON(t *testing.T) { t.Parallel() b, err := Buy.MarshalJSON() diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 5d43ab695bf..df5d4b2632c 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -411,17 +411,16 @@ type TimeInForce uint16 // TimeInForce types const ( - UnsetTIF TimeInForce = 0 - GTC TimeInForce = 1 << iota // GTC represents GoodTillCancel - GTD // GTD represents GoodTillDay - GTT // GTT represents GoodTillTime - FOK // FOK represents FillOrKill - IOC // IOC represents ImmediateOrCancel - POC // POC represents PendingOrCancel - PostOnlyGTC // PostOnlyGCT represents PostOnlyGoodTilCancelled + UnsetTIF TimeInForce = 0 + GoodTillCancel TimeInForce = 1 << iota + GoodTillDay + GoodTillTime + FOK // FOK represents FillOrKill + IOC // IOC represents ImmediateOrCancel + PostOnlyGTC // PostOnlyGCT represents PostOnlyGoodTilCancelled UnknownTIF - supportedTimeInForceFlag = GTC | GTD | GTT | FOK | IOC | POC | PostOnlyGTC + supportedTimeInForceFlag = GoodTillCancel | GoodTillDay | GoodTillTime | FOK | IOC | PostOnlyGTC ) // ByPrice used for sorting orders by price diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index f269316b01e..ad1d25295aa 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -739,17 +739,14 @@ func (t TimeInForce) String() string { switch t { case IOC: return "IOC" - case GTC: + case GoodTillCancel: return "GTC" - case GTD: - return "GOOD_TILL_DAY" - case GTT: + case GoodTillDay: + return "GTD" + case GoodTillTime: return "GTT" case FOK: return "FOK" - case POC: - // Added in GateIO exchange to represent Pending or Cancel - return "POC" case PostOnlyGTC: // Added in Bittrex exchange to represent PostOnly and GTC return "POST_ONLY_GOOD_TIL_CANCELLED" @@ -760,6 +757,11 @@ 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 +} + // Lower returns the type lower case string func (t Type) Lower() string { return strings.ToLower(t.String()) @@ -1258,24 +1260,22 @@ 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(): + case "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL", IOC.String(), "POC", "PENDINGORCANCEL": return IOC, nil - case "GOODTILLCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED", "GOOD_TILL_CANCELED", GTC.String(): - return GTC, nil - case "GOODTILLDAY", "GTD", GTD.String(): - return GTD, nil - case "GOODTILLTIME", "GOOD_TIL_TIME", GTT.String(): - return GTT, 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 "POC", "PENDINGORCANCEL": - return POC, nil case "": return UnsetTIF, nil default: - return UnknownTIF, fmt.Errorf("%w, tif=%s", ErrInvalidTimeInForce, timeInForce) + return UnknownTIF, fmt.Errorf("%w: tif=%s", ErrInvalidTimeInForce, timeInForce) } } diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 54418ed20af..d1feec35286 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -549,7 +549,7 @@ func (p *Poloniex) ModifyOrder(ctx context.Context, action *order.Modify) (*orde action.Price, action.Amount, action.PostOnly, - action.TimeInForce == order.IOC) + action.TimeInForce.IsIOC()) if err != nil { return nil, err } From 83b65211d4d70f0dd78f09dfe9eabb027d3843a1 Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Sat, 1 Mar 2025 00:09:44 +0300 Subject: [PATCH 15/20] 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 | 23 +++-- 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 | 50 ++++++----- exchanges/okx/okx_test.go | 61 ++++++++------ exchanges/okx/okx_wrapper.go | 47 +++++------ exchanges/order/order_test.go | 84 ++++++++----------- exchanges/order/order_types.go | 32 +++---- exchanges/order/orders.go | 45 +++------- 26 files changed, 270 insertions(+), 285 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..29ccec6d91b 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,15 @@ 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 +939,7 @@ func (d *Deribit) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M Side: orderSide, Type: orderType, Status: orderStatus, + TimeInForce: tif, }) } } @@ -988,10 +994,14 @@ 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 +1013,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..1b4c0408da1 100644 --- a/exchanges/okx/helpers.go +++ b/exchanges/okx/helpers.go @@ -12,45 +12,55 @@ 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..7c13b92a34d 100644 --- a/exchanges/okx/okx_test.go +++ b/exchanges/okx/okx_test.go @@ -6285,28 +6285,32 @@ 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 +6575,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..82deb57d5ba 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,16 +695,11 @@ func TestSortOrdersByOrderType(t *testing.T) { Type: Market, }, { Type: Limit, - }, { - Type: ImmediateOrCancel, }, { Type: TrailingStop, }, } - SortOrdersByType(&orders, false) - assert.Truef(t, strings.EqualFold(orders[0].Type.String(), ImmediateOrCancel.String()), "Expected: '%v', received: '%v'", ImmediateOrCancel, orders[0].Type) - SortOrdersByType(&orders, true) assert.Truef(t, strings.EqualFold(orders[0].Type.String(), TrailingStop.String()), "Expected: '%v', received: '%v'", TrailingStop, orders[0].Type) } @@ -769,10 +761,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 +770,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 +897,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 +913,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 +946,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 +983,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 +1680,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 +1708,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: ImmediateOrCancel}, + "PendingORCANCEL": {TIF: ImmediateOrCancel}, } for tk := range timeInForceStringToValueMap { @@ -1752,14 +1736,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 +1753,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..a7bdba913ef 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -186,7 +186,6 @@ type ModifyResponse struct { // Fields that will be copied over from Modify TimeInForce TimeInForce - PostOnly bool Price float64 Amount float64 TriggerPrice float64 @@ -203,7 +202,6 @@ type ModifyResponse struct { type Detail struct { HiddenOrder bool TimeInForce TimeInForce - PostOnly bool ReduceOnly bool Leverage float64 Price float64 @@ -273,6 +271,7 @@ type Cancel struct { AssetType asset.Item Pair currency.Pair MarginType margin.Type + TimeInForce TimeInForce } // CancelAllResponse returns the status from attempting to @@ -308,12 +307,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 +358,12 @@ const ( UnknownType Type = 0 Limit Type = 1 << iota Market - PostOnly - ImmediateOrCancel Stop StopLimit StopMarket TakeProfit TakeProfitMarket TrailingStop - FillOrKill IOS AnyType Liquidation @@ -409,18 +406,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..e2f0b6d73c3 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, @@ -656,7 +647,6 @@ func (m *Modify) DeriveModifyResponse() (*ModifyResponse, error) { AssetType: m.AssetType, Pair: m.Pair, TimeInForce: m.TimeInForce, - PostOnly: m.PostOnly, Price: m.Price, Amount: m.Amount, TriggerPrice: m.TriggerPrice, @@ -691,10 +681,6 @@ func (t Type) String() string { return "LIMIT" case Market: return "MARKET" - case PostOnly: - return "POST_ONLY" - case ImmediateOrCancel: - return "IMMEDIATE_OR_CANCEL" case Stop: return "STOP" case ConditionalStop: @@ -717,8 +703,6 @@ func (t Type) String() string { return "TAKE PROFIT MARKET" case TrailingStop: return "TRAILING_STOP" - case FillOrKill: - return "FOK" case IOS: return "IOS" case Liquidation: @@ -737,7 +721,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 +729,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 +742,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 +1142,6 @@ 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 Stop.String(), "STOP LOSS", "STOP_LOSS", "EXCHANGE STOP": return Stop, nil case StopLimit.String(), "EXCHANGE STOP LIMIT", "STOP_LIMIT": @@ -1169,12 +1150,8 @@ 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 IOS.String(): return IOS, nil - case PostOnly.String(): - return PostOnly, nil case AnyType.String(): return AnyType, nil case Trigger.String(): @@ -1260,18 +1237,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: From 9bef751401212bcba0a175d8f60600cc43b545f1 Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Tue, 4 Mar 2025 01:27:00 +0300 Subject: [PATCH 16/20] Update time-in-force, related functions, and unit tests --- exchanges/binance/binance_test.go | 2 +- exchanges/binance/binance_types.go | 14 ------ exchanges/binance/binance_wrapper.go | 10 ++-- exchanges/binance/cfutures_types.go | 2 +- exchanges/binanceus/binanceus_test.go | 4 +- exchanges/binanceus/binanceus_types.go | 16 +----- exchanges/binanceus/binanceus_wrapper.go | 4 +- exchanges/btcmarkets/btcmarkets.go | 6 +-- exchanges/bybit/bybit_test.go | 1 - exchanges/coinbasepro/coinbasepro_wrapper.go | 2 +- exchanges/deribit/deribit_wrapper.go | 37 ++++++++------ exchanges/gateio/gateio.go | 4 +- exchanges/gateio/gateio_test.go | 39 ++++++++------ exchanges/gateio/gateio_wrapper.go | 53 +++++++++++--------- exchanges/huobi/huobi_wrapper.go | 2 +- exchanges/kucoin/kucoin_wrapper.go | 7 ++- exchanges/order/order_test.go | 36 ++++++++----- exchanges/order/order_types.go | 7 ++- exchanges/order/orders.go | 27 ++++------ exchanges/poloniex/poloniex_wrapper.go | 4 +- 20 files changed, 133 insertions(+), 144 deletions(-) diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index fb38f20541e..23f1e427c7e 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -866,7 +866,7 @@ func TestFuturesNewOrder(t *testing.T) { Symbol: currency.NewPairWithDelimiter("BTCUSD", "PERP", "_"), Side: "BUY", OrderType: "LIMIT", - TimeInForce: BinanceRequestParamsTimeGTC, + TimeInForce: order.GoodTillCancel.String(), Quantity: 1, Price: 1, }, diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index a269e6a0f74..97ec9c9b3a8 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -486,20 +486,6 @@ type MarginAccountAsset struct { NetAsset float64 `json:"netAsset,string"` } -// RequestParamsTimeForceType Time in force -type RequestParamsTimeForceType string - -var ( - // BinanceRequestParamsTimeGTC GTC - BinanceRequestParamsTimeGTC = RequestParamsTimeForceType("GTC") - - // BinanceRequestParamsTimeIOC IOC - BinanceRequestParamsTimeIOC = RequestParamsTimeForceType("IOC") - - // BinanceRequestParamsTimeFOK FOK - BinanceRequestParamsTimeFOK = RequestParamsTimeForceType("FOK") -) - // RequestParamsOrderType trade order type type RequestParamsOrderType string diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 983fbb970cf..e565ff03286 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -889,7 +889,7 @@ func (b *Binance) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm timeInForce = "" requestParamsOrderType = BinanceRequestParamsOrderMarket case order.Limit: - if s.TimeInForce == order.ImmediateOrCancel { + if s.TimeInForce.Is(order.ImmediateOrCancel) { timeInForce = order.ImmediateOrCancel.String() } requestParamsOrderType = BinanceRequestParamsOrderLimit @@ -936,15 +936,11 @@ func (b *Binance) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm return nil, errors.New("invalid side") } - var ( - oType string - timeInForce RequestParamsTimeForceType - ) - + var oType, timeInForce string switch s.Type { case order.Limit: oType = cfuturesLimit - timeInForce = BinanceRequestParamsTimeGTC + timeInForce = order.GoodTillTime.String() case order.Market: oType = cfuturesMarket case order.Stop: diff --git a/exchanges/binance/cfutures_types.go b/exchanges/binance/cfutures_types.go index 6c983f134be..d0431a80153 100644 --- a/exchanges/binance/cfutures_types.go +++ b/exchanges/binance/cfutures_types.go @@ -213,7 +213,7 @@ type FuturesNewOrderRequest struct { Side string PositionSide string OrderType string - TimeInForce RequestParamsTimeForceType + TimeInForce string NewClientOrderID string ClosePosition string WorkingType string diff --git a/exchanges/binanceus/binanceus_test.go b/exchanges/binanceus/binanceus_test.go index 6e97d64df3e..938933d5b89 100644 --- a/exchanges/binanceus/binanceus_test.go +++ b/exchanges/binanceus/binanceus_test.go @@ -781,7 +781,7 @@ func TestNewOrderTest(t *testing.T) { TradeType: BinanceRequestParamsOrderLimit, Price: 0.0025, Quantity: 100000, - TimeInForce: BinanceRequestParamsTimeGTC, + TimeInForce: order.GoodTillCancel.String(), } _, err := bi.NewOrderTest(context.Background(), req) if err != nil { @@ -809,7 +809,7 @@ func TestNewOrder(t *testing.T) { TradeType: BinanceRequestParamsOrderLimit, Price: 0.0025, Quantity: 100000, - TimeInForce: BinanceRequestParamsTimeGTC, + TimeInForce: order.GoodTillCancel.String(), } if _, err := bi.NewOrder(context.Background(), req); err != nil && !strings.Contains(err.Error(), "Account has insufficient balance for requested action") { t.Error("Binanceus NewOrder() error", err) diff --git a/exchanges/binanceus/binanceus_types.go b/exchanges/binanceus/binanceus_types.go index 07fb569a262..a1f6cd51027 100644 --- a/exchanges/binanceus/binanceus_types.go +++ b/exchanges/binanceus/binanceus_types.go @@ -404,26 +404,12 @@ type OrderRateLimit struct { // RequestParamsOrderType trade order type type RequestParamsOrderType string -// RequestParamsTimeForceType Time in force -type RequestParamsTimeForceType string - -var ( - // BinanceRequestParamsTimeGTC GTC - BinanceRequestParamsTimeGTC = RequestParamsTimeForceType("GTC") - - // BinanceRequestParamsTimeIOC IOC - BinanceRequestParamsTimeIOC = RequestParamsTimeForceType("IOC") - - // BinanceRequestParamsTimeFOK FOK - BinanceRequestParamsTimeFOK = RequestParamsTimeForceType("FOK") -) - // NewOrderRequest request type type NewOrderRequest struct { Symbol currency.Pair Side string TradeType RequestParamsOrderType - TimeInForce RequestParamsTimeForceType + TimeInForce string Quantity float64 QuoteOrderQty float64 Price float64 diff --git a/exchanges/binanceus/binanceus_wrapper.go b/exchanges/binanceus/binanceus_wrapper.go index 5092578abcd..b7df0192647 100644 --- a/exchanges/binanceus/binanceus_wrapper.go +++ b/exchanges/binanceus/binanceus_wrapper.go @@ -488,7 +488,7 @@ func (bi *Binanceus) GetHistoricTrades(ctx context.Context, p currency.Pair, ass // SubmitOrder submits a new order func (bi *Binanceus) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - var timeInForce RequestParamsTimeForceType + var timeInForce string var sideType string err := s.Validate(bi.GetTradingRequirements()) if err != nil { @@ -507,7 +507,7 @@ func (bi *Binanceus) SubmitOrder(ctx context.Context, s *order.Submit) (*order.S case order.Market: requestParamOrderType = BinanceRequestParamsOrderMarket case order.Limit: - timeInForce = BinanceRequestParamsTimeGTC + timeInForce = order.GoodTillCancel.String() requestParamOrderType = BinanceRequestParamsOrderLimit default: return nil, fmt.Errorf("%w %v", order.ErrUnsupportedOrderType, s.Type) diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 6c5b4174853..a1da60d4bd1 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -373,12 +373,10 @@ 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.ImmediateOrCancel, order.FillOrKill: + if s.TimeInForce.Is(order.ImmediateOrCancel) || s.TimeInForce.Is(order.FillOrKill) { return s.TimeInForce.String() - default: - return "" // GTC (good till cancelled, default value) } + return "" // GTC (good till cancelled, default value) } // NewOrder requests a new order and returns an ID diff --git a/exchanges/bybit/bybit_test.go b/exchanges/bybit/bybit_test.go index caeea790dc5..27a937ab267 100644 --- a/exchanges/bybit/bybit_test.go +++ b/exchanges/bybit/bybit_test.go @@ -292,7 +292,6 @@ func TestModifyOrder(t *testing.T) { Side: order.Buy, AssetType: asset.Options, Pair: spotTradablePair, - PostOnly: true, Price: 1234, Amount: 0.15, TriggerPrice: 1145, diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 4ba92510bdb..c3e8fc6b2c5 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -430,7 +430,7 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. "") case order.Limit: timeInForce := order.GoodTillCancel.String() - if s.TimeInForce == order.ImmediateOrCancel { + if s.TimeInForce.Is(order.ImmediateOrCancel) { timeInForce = order.ImmediateOrCancel.String() } orderID, err = c.PlaceLimitOrder(ctx, diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index 29ccec6d91b..03f3a8607af 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -592,14 +592,14 @@ func (d *Deribit) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm return nil, err } var timeInForce string - switch s.TimeInForce { - case order.GoodTillCancel: + switch { + case s.TimeInForce.Is(order.GoodTillCancel): timeInForce = "good_til_cancelled" - case order.GoodTillDay: + case s.TimeInForce.Is(order.GoodTillDay): timeInForce = "good_till_day" - case order.FillOrKill: + case s.TimeInForce.Is(order.FillOrKill): timeInForce = "fill_or_kill" - case order.ImmediateOrCancel: + case s.TimeInForce.Is(order.ImmediateOrCancel): timeInForce = "immediate_or_cancel" } var data *PrivateTradeData @@ -663,7 +663,7 @@ func (d *Deribit) ModifyOrder(ctx context.Context, action *order.Modify) (*order var err error reqParam := &OrderBuyAndSellParams{ TriggerPrice: action.TriggerPrice, - PostOnly: action.PostOnly, + PostOnly: action.TimeInForce.Is(order.PostOnly), Amount: action.Amount, OrderID: action.OrderID, Price: action.Price, @@ -788,15 +788,12 @@ func (d *Deribit) GetOrderInfo(ctx context.Context, orderID string, _ currency.P } } var tif order.TimeInForce - switch orderInfo.TimeInForce { - case "good_til_cancelled": - tif = order.GoodTillCancel - case "good_til_day": - tif = order.GoodTillDay - case "fill_or_kill": - tif = order.FillOrKill - case "immediate_or_cancel": - tif = order.ImmediateOrCancel + tif, err = order.StringToTimeInForce(orderInfo.TimeInForce) + if err != nil { + tif = order.UnsetTIF + } + if orderInfo.PostOnly { + tif |= order.PostOnly } return &order.Detail{ AssetType: assetType, @@ -921,6 +918,10 @@ func (d *Deribit) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M } var tif order.TimeInForce + tif, err = order.StringToTimeInForce(ordersData[y].TimeInForce) + if err != nil { + tif = order.UnsetTIF + } if ordersData[y].PostOnly { // TODO: Set ordersData[y].TimeInForce values tif = order.PostOnly } @@ -996,8 +997,12 @@ func (d *Deribit) GetOrderHistory(ctx context.Context, getOrdersRequest *order.M } var tif order.TimeInForce + tif, err = order.StringToTimeInForce(ordersData[y].TimeInForce) + if err != nil { + tif = order.UnsetTIF + } if ordersData[y].PostOnly { // TODO: Set ordersData[y].TimeInForce values - tif = order.PostOnly + tif |= order.PostOnly } resp = append(resp, order.Detail{ AssetType: getOrdersRequest.AssetType, diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index 086c1accf65..e3fe7f2a0bb 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "net/url" + "slices" "strconv" "strings" "time" @@ -2849,7 +2850,8 @@ func (g *Gateio) PlaceDeliveryOrder(ctx context.Context, arg *OrderCreateParams) if arg.Size == 0 { return nil, fmt.Errorf("%w, specify positive number to make a bid, and negative number to ask", errInvalidOrderSide) } - if arg.TimeInForce != gtcTIF && arg.TimeInForce != iocTIF && arg.TimeInForce != pocTIF && arg.TimeInForce != fokTIF { + arg.TimeInForce = strings.ToLower(arg.TimeInForce) + if !slices.Contains([]string{gtcTIF, iocTIF, pocTIF, fokTIF}, arg.TimeInForce) { return nil, errInvalidTimeInForce } if arg.Price == "" { diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 75c6c24a13f..003d3b4656e 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -3370,21 +3370,30 @@ func TestGetClientOrderIDFromText(t *testing.T) { func TestGetTypeFromTimeInForce(t *testing.T) { t.Parallel() - typeResp, tif := getTypeFromTimeInForce("gtc") - assert.Equal(t, order.Limit, typeResp, "should be a limit order") - assert.False(t, tif.Is(order.PostOnly), "should return false") - - typeResp, tif = getTypeFromTimeInForce("ioc") - assert.Equal(t, order.Market, typeResp, "should be market order") - assert.False(t, tif.Is(order.PostOnly), "should return false") - - typeResp, tif = getTypeFromTimeInForce("poc") - assert.Equal(t, order.Limit, typeResp, "should be limit order") - assert.True(t, tif.Is(order.PostOnly), "should return true") - - typeResp, tif = getTypeFromTimeInForce("fok") - assert.Equal(t, order.Market, typeResp, "should be market order") - assert.False(t, tif.Is(order.PostOnly), "should return false") + type tifAndPrice struct { + TIF string + Price float64 + } + tifAndPriceStringToValueMap := map[tifAndPrice]struct { + OType order.Type + TIF order.TimeInForce + }{ + {"gtc", 0}: {order.Limit, order.GoodTillCancel}, + {"gtc", 1.2}: {order.Limit, order.GoodTillCancel}, + {"", 0}: {order.Limit, order.UnsetTIF}, + {"", 1.2}: {order.Limit, order.UnsetTIF}, + {"ioc", 0}: {order.Market, order.ImmediateOrCancel}, + {"ioc", 1.3}: {order.Limit, order.ImmediateOrCancel}, + {"poc", .1}: {order.Limit, order.PostOnly}, + {"poc", 0}: {order.Limit, order.PostOnly}, + {"fok", 0}: {order.Market, order.FillOrKill}, + {"fok", 1}: {order.Limit, order.FillOrKill}, + } + for k, v := range tifAndPriceStringToValueMap { + typeResp, tif := getTypeFromTimeInForceAndPrice(k.TIF, k.Price) + assert.Equal(t, v.OType, typeResp) + assert.Equal(t, v.TIF, tif) + } } func TestGetSideAndAmountFromSize(t *testing.T) { diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index e1dc2898278..bf85b6c126e 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, tif := getTypeFromTimeInForce(fOrder.TimeInForce) + ordertype, tif := getTypeFromTimeInForceAndPrice(fOrder.TimeInForce, fOrder.OrderPrice.Float64()) return &order.Detail{ Amount: amount, ExecutedAmount: amount - remaining, @@ -2510,17 +2510,21 @@ func getClientOrderIDFromText(text string) string { return "" } -// getTypeFromTimeInForce returns the order type and if the order is post only -// 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) { +// getTypeFromTimeInForceAndPrice returns the order type and if the order is post only +func getTypeFromTimeInForceAndPrice(tif string, price float64) (orderType order.Type, postOnly order.TimeInForce) { + oType := order.Market + if price > 0 { + oType = order.Limit + } switch tif { case "ioc": - return order.Market, order.UnsetTIF + return oType, order.ImmediateOrCancel case "fok": - return order.Market, order.UnsetTIF + return oType, order.FillOrKill case "poc": return order.Limit, order.PostOnly + case "gtc": + return order.Limit, order.GoodTillCancel default: return order.Limit, order.UnsetTIF } @@ -2546,30 +2550,31 @@ func getFutureOrderSize(s *order.Submit) (float64, error) { } } -// getTimeInForce returns the time in force for a given order. If Market order -// IOC +// getTimeInForce returns the time-in-force for a given order. +// If the time-in-force is unset, it assumes a Market order with an immediate-or-cancel (IOC) time-in-force value. +// If the order type is Limit, it applies a good-til-cancel (GTC) policy. func getTimeInForce(s *order.Submit) (string, error) { - var timeInForce string - switch s.TimeInForce { - case order.ImmediateOrCancel: - timeInForce = "ioc" // market taker only - case order.FillOrKill: - timeInForce = "fok" - case order.PostOnly: - timeInForce = "poc" - case order.GoodTillCancel: - timeInForce = "gtc" - case order.UnsetTIF: + switch { + case s.TimeInForce.Is(order.ImmediateOrCancel): + return "ioc", nil // market taker only + case s.TimeInForce.Is(order.FillOrKill): + return "fok", nil + case s.TimeInForce.Is(order.PostOnly): + return "poc", nil + case s.TimeInForce.Is(order.GoodTillCancel): + return "gtc", nil + case s.TimeInForce.Is(order.UnsetTIF): switch s.Type { case order.Market: - timeInForce = "ioc" + return "ioc", nil case order.Limit: - timeInForce = "gtc" + return "gtc", nil + default: + return "", nil } default: - return timeInForce, fmt.Errorf("%w: time-in-force value of %s", order.ErrInvalidTimeInForce, timeInForce) + return "", fmt.Errorf("%w: time-in-force value of %s", order.ErrInvalidTimeInForce, s.TimeInForce.String()) } - return timeInForce, nil } // GetCurrencyTradeURL returns the URL to the exchange's trade page for the given asset and currency pair diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 58bb667d733..74bc8a69455 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -1023,7 +1023,7 @@ func (h *HUOBI) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submit switch { case s.Type == order.Limit: oType = "limit" - case s.TimeInForce == order.PostOnly: + case s.TimeInForce.Is(order.PostOnly): oType = "post_only" } offset := "open" diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index ed5fc4ae105..b147eec2f94 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -692,10 +692,9 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm var timeInForce string if oType == order.Limit { switch { - case s.TimeInForce == order.FillOrKill: - timeInForce = order.FillOrKill.String() - case s.TimeInForce == order.ImmediateOrCancel: - timeInForce = order.ImmediateOrCancel.String() + case s.TimeInForce.Is(order.FillOrKill) || + s.TimeInForce.Is(order.ImmediateOrCancel): + timeInForce = s.TimeInForce.String() case s.PostOnly: default: timeInForce = order.GoodTillCancel.String() diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 82deb57d5ba..5f01cc2489b 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -930,6 +930,24 @@ func TestUpdateOrderFromModifyResponse(t *testing.T) { assert.Nil(t, od.Trades) } +func TestTimeInForceIs(t *testing.T) { + t.Parallel() + tifValuesMap := map[TimeInForce][]TimeInForce{ + GoodTillCancel | PostOnly: {GoodTillCancel, PostOnly}, + GoodTillCancel: {GoodTillCancel}, + ImmediateOrCancel | PostOnly: {ImmediateOrCancel, PostOnly}, + GoodTillDay: {GoodTillDay}, + FillOrKill | PostOnly: {FillOrKill, PostOnly}, + FillOrKill: {FillOrKill}, + PostOnly: {PostOnly}, + } + for tif := range tifValuesMap { + for _, v := range tifValuesMap[tif] { + require.True(t, tif.Is(v)) + } + } +} + func TestUpdateOrderFromDetail(t *testing.T) { var leet = "1337" @@ -1698,7 +1716,6 @@ func TestIsValid(t *testing.T) { func TestStringToTimeInForce(t *testing.T) { t.Parallel() - var timeInForceStringToValueMap = map[string]struct { TIF TimeInForce Error error @@ -1709,7 +1726,7 @@ func TestStringToTimeInForce(t *testing.T) { "GTT": {TIF: GoodTillTime}, "GOOD_TIL_TIME": {TIF: GoodTillTime}, "FILLORKILL": {TIF: FillOrKill}, - "POST_ONLY_GOOD_TIL_CANCELLED": {TIF: PostOnly}, + "POST_ONLY_GOOD_TIL_CANCELLED": {TIF: GoodTillCancel}, "immedIate_Or_Cancel": {TIF: ImmediateOrCancel}, "": {TIF: UnsetTIF}, "IOC": {TIF: ImmediateOrCancel}, @@ -1722,8 +1739,8 @@ func TestStringToTimeInForce(t *testing.T) { "GTD": {TIF: GoodTillDay}, "GOODtillday": {TIF: GoodTillDay}, "abcdfeg": {TIF: UnknownTIF, Error: ErrInvalidTimeInForce}, - "PoC": {TIF: ImmediateOrCancel}, - "PendingORCANCEL": {TIF: ImmediateOrCancel}, + "PoC": {TIF: PostOnly}, + "PendingORCANCEL": {TIF: PostOnly}, } for tk := range timeInForceStringToValueMap { @@ -1741,23 +1758,16 @@ func TestString(t *testing.T) { GoodTillTime: "GTT", GoodTillDay: "GTD", FillOrKill: "FOK", - PostOnly: "POST_ONLY_GOOD_TIL_CANCELLED", + PostOnly: "POSTONLY", UnknownTIF: "UNKNOWN", UnsetTIF: "", } for x := range valMap { result := x.String() - assert.Equalf(t, result, valMap[x], "expected %v, got %v", x, result) + assert.Equalf(t, valMap[x], result, "expected %v, got %v", x, result) } } -func TestIsIOC(t *testing.T) { - t.Parallel() - require.True(t, ImmediateOrCancel.IsIOC()) - require.False(t, FillOrKill.IsIOC()) - require.False(t, TimeInForce(0).IsIOC()) -} - func TestSideMarshalJSON(t *testing.T) { t.Parallel() b, err := Buy.MarshalJSON() diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index a7bdba913ef..557bac9c80e 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -160,7 +160,6 @@ type Modify struct { // Change fields TimeInForce TimeInForce - PostOnly bool Price float64 Amount float64 TriggerPrice float64 @@ -417,9 +416,9 @@ const ( GoodTillCancel TimeInForce = 1 << iota GoodTillDay GoodTillTime - FillOrKill // FOK represents FillOrKill - ImmediateOrCancel // IOC represents ImmediateOrCancel - PostOnly // PostOnlyGCT represents PostOnlyGoodTilCancelled + FillOrKill + ImmediateOrCancel + PostOnly UnknownTIF supportedTimeInForceFlag = GoodTillCancel | GoodTillDay | GoodTillTime | FillOrKill | ImmediateOrCancel | PostOnly diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index e2f0b6d73c3..98af55a0138 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -720,31 +720,26 @@ func (t Type) String() string { // String implements the stringer interface. func (t TimeInForce) String() string { - switch t { - case ImmediateOrCancel: + switch { + case t.Is(ImmediateOrCancel): return "IOC" - case GoodTillCancel: + case t.Is(GoodTillCancel): return "GTC" - case GoodTillDay: + case t.Is(GoodTillDay): return "GTD" - case GoodTillTime: + case t.Is(GoodTillTime): return "GTT" - case FillOrKill: + case t.Is(FillOrKill): return "FOK" - case PostOnly: + case t.Is(PostOnly): return "POSTONLY" - case UnsetTIF: + case t == UnsetTIF: return "" default: return "UNKNOWN" } } -// IsIOC determines whether the TimeInForce value is set to IOC (Immediate or Cancel). -func (t TimeInForce) IsIOC() bool { - return t == ImmediateOrCancel -} - // Lower returns the type lower case string func (t Type) Lower() string { return strings.ToLower(t.String()) @@ -1237,9 +1232,9 @@ func StringToOrderStatus(status string) (Status, error) { func StringToTimeInForce(timeInForce string) (TimeInForce, error) { timeInForce = strings.ToUpper(timeInForce) switch timeInForce { - case "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL", ImmediateOrCancel.String(), "POC", "PENDINGORCANCEL": + case "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL", ImmediateOrCancel.String(): return ImmediateOrCancel, nil - case "GOODTILLCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED", "GOOD_TILL_CANCELED", GoodTillCancel.String(): + case "GOODTILLCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED", "GOOD_TILL_CANCELED", GoodTillCancel.String(), "POST_ONLY_GOOD_TIL_CANCELLED": return GoodTillCancel, nil case "GOODTILLDAY", GoodTillDay.String(), "GOOD_TIL_DAY", "GOOD_TILL_DAY": return GoodTillDay, nil @@ -1247,7 +1242,7 @@ func StringToTimeInForce(timeInForce string) (TimeInForce, error) { return GoodTillTime, nil case "FILLORKILL", "FILL_OR_KILL", FillOrKill.String(): return FillOrKill, nil - case "POST_ONLY_GOOD_TILL_CANCELLED", PostOnly.String(): + case "POST_ONLY_GOOD_TILL_CANCELLED", PostOnly.String(), "POC", "PENDINGORCANCEL": return PostOnly, nil case "": return UnsetTIF, nil diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index d1feec35286..83607d3972b 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -548,8 +548,8 @@ func (p *Poloniex) ModifyOrder(ctx context.Context, action *order.Modify) (*orde oID, action.Price, action.Amount, - action.PostOnly, - action.TimeInForce.IsIOC()) + action.TimeInForce.Is(order.PostOnly), + action.TimeInForce.Is(order.ImmediateOrCancel)) if err != nil { return nil, err } From 0fbaad8e1205c8e564b09d9bb4bb71e8e7de91ca Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Tue, 4 Mar 2025 11:16:33 +0300 Subject: [PATCH 17/20] fix linter issue and time-in-force processing --- exchanges/binance/binance_cfutures.go | 4 +- exchanges/binanceus/binanceus.go | 2 +- exchanges/order/order_test.go | 37 ++++++++++++++---- exchanges/order/orders.go | 56 +++++++++++++++++++-------- 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/exchanges/binance/binance_cfutures.go b/exchanges/binance/binance_cfutures.go index 3a66b75bd11..9f45edf7243 100644 --- a/exchanges/binance/binance_cfutures.go +++ b/exchanges/binance/binance_cfutures.go @@ -990,8 +990,8 @@ func (b *Binance) FuturesNewOrder(ctx context.Context, x *FuturesNewOrderRequest params.Set("positionSide", x.PositionSide) } params.Set("type", x.OrderType) - if string(x.TimeInForce) != "" { - params.Set("timeInForce", string(x.TimeInForce)) + if x.TimeInForce != "" { + params.Set("timeInForce", x.TimeInForce) } if x.ReduceOnly { params.Set("reduceOnly", "true") diff --git a/exchanges/binanceus/binanceus.go b/exchanges/binanceus/binanceus.go index 34a35299b3a..39b0ac41f3a 100644 --- a/exchanges/binanceus/binanceus.go +++ b/exchanges/binanceus/binanceus.go @@ -1054,7 +1054,7 @@ func (bi *Binanceus) newOrder(ctx context.Context, api string, o *NewOrderReques params.Set("price", strconv.FormatFloat(o.Price, 'f', -1, 64)) } if o.TimeInForce != "" { - params.Set("timeInForce", string(o.TimeInForce)) + params.Set("timeInForce", o.TimeInForce) } if o.NewClientOrderID != "" { params.Set("newClientOrderId", o.NewClientOrderID) diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 5f01cc2489b..0d9f0bc711b 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -1753,14 +1753,20 @@ func TestStringToTimeInForce(t *testing.T) { func TestString(t *testing.T) { t.Parallel() valMap := map[TimeInForce]string{ - ImmediateOrCancel: "IOC", - GoodTillCancel: "GTC", - GoodTillTime: "GTT", - GoodTillDay: "GTD", - FillOrKill: "FOK", - PostOnly: "POSTONLY", - UnknownTIF: "UNKNOWN", - UnsetTIF: "", + ImmediateOrCancel: "IOC", + GoodTillCancel: "GTC", + GoodTillTime: "GTT", + GoodTillDay: "GTD", + FillOrKill: "FOK", + PostOnly: "POSTONLY", + UnknownTIF: "UNKNOWN", + UnsetTIF: "", + ImmediateOrCancel | PostOnly: "IOC,POSTONLY", + GoodTillCancel | PostOnly: "GTC,POSTONLY", + GoodTillTime | PostOnly: "GTT,POSTONLY", + GoodTillDay | PostOnly: "GTD,POSTONLY", + FillOrKill | PostOnly: "FOK,POSTONLY", + FillOrKill | ImmediateOrCancel: "IOC,FOK", } for x := range valMap { result := x.String() @@ -1768,6 +1774,21 @@ func TestString(t *testing.T) { } } +func TestUnmarshalJSON(t *testing.T) { + t.Parallel() + targets := []TimeInForce{ + GoodTillCancel | PostOnly | ImmediateOrCancel, GoodTillCancel | PostOnly, GoodTillCancel, UnsetTIF, PostOnly | ImmediateOrCancel, + GoodTillCancel, GoodTillCancel, PostOnly, PostOnly, ImmediateOrCancel, GoodTillDay, GoodTillDay, GoodTillTime, FillOrKill, FillOrKill, + } + data := `{"tifs": ["GTC,POSTONLY,IOC", "GTC,POSTONLY", "GTC", "", "POSTONLY,IOC", "GoodTilCancel", "GoodTILLCANCEL", "POST_ONLY", "POC","IOC", "GTD", "gtd","gtt", "fok", "fillOrKill"]}` + target := &struct { + TIFs []TimeInForce `json:"tifs"` + }{} + err := json.Unmarshal([]byte(data), &target) + require.NoError(t, err) + require.EqualValues(t, targets, target.TIFs) +} + func TestSideMarshalJSON(t *testing.T) { t.Parallel() b, err := Buy.MarshalJSON() diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 98af55a0138..70a78317499 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -720,24 +720,46 @@ func (t Type) String() string { // String implements the stringer interface. func (t TimeInForce) String() string { - switch { - case t.Is(ImmediateOrCancel): - return "IOC" - case t.Is(GoodTillCancel): - return "GTC" - case t.Is(GoodTillDay): - return "GTD" - case t.Is(GoodTillTime): - return "GTT" - case t.Is(FillOrKill): - return "FOK" - case t.Is(PostOnly): - return "POSTONLY" - case t == UnsetTIF: + var tifStrings []string + + if t.Is(ImmediateOrCancel) { + tifStrings = append(tifStrings, "IOC") + } + if t.Is(GoodTillCancel) { + tifStrings = append(tifStrings, "GTC") + } + if t.Is(GoodTillDay) { + tifStrings = append(tifStrings, "GTD") + } + if t.Is(GoodTillTime) { + tifStrings = append(tifStrings, "GTT") + } + if t.Is(FillOrKill) { + tifStrings = append(tifStrings, "FOK") + } + if t.Is(PostOnly) { + tifStrings = append(tifStrings, "POSTONLY") + } + if t == UnsetTIF { return "" - default: + } + if len(tifStrings) == 0 { return "UNKNOWN" } + return strings.Join(tifStrings, ",") +} + +// UnmarshalJSON deserializes a string data into TimeInForce instance. +func (t *TimeInForce) UnmarshalJSON(data []byte) error { + tifStrings := strings.Split(strings.Trim(string(data), `"`), ",") + for _, val := range tifStrings { + tif, err := StringToTimeInForce(val) + if err != nil { + return err + } + *t |= tif + } + return nil } // Lower returns the type lower case string @@ -1234,7 +1256,7 @@ func StringToTimeInForce(timeInForce string) (TimeInForce, error) { switch timeInForce { case "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL", ImmediateOrCancel.String(): return ImmediateOrCancel, nil - case "GOODTILLCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED", "GOOD_TILL_CANCELED", GoodTillCancel.String(), "POST_ONLY_GOOD_TIL_CANCELLED": + case "GOODTILLCANCEL", "GOODTILCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED", "GOOD_TILL_CANCELED", GoodTillCancel.String(), "POST_ONLY_GOOD_TIL_CANCELLED": return GoodTillCancel, nil case "GOODTILLDAY", GoodTillDay.String(), "GOOD_TIL_DAY", "GOOD_TILL_DAY": return GoodTillDay, nil @@ -1242,7 +1264,7 @@ func StringToTimeInForce(timeInForce string) (TimeInForce, error) { return GoodTillTime, nil case "FILLORKILL", "FILL_OR_KILL", FillOrKill.String(): return FillOrKill, nil - case "POST_ONLY_GOOD_TILL_CANCELLED", PostOnly.String(), "POC", "PENDINGORCANCEL": + case "POST_ONLY_GOOD_TILL_CANCELLED", PostOnly.String(), "POC", "POST_ONLY", "PENDINGORCANCEL": return PostOnly, nil case "": return UnsetTIF, nil From 0cf66a2a58fb1d22c470631f45ef4467cb1a7927 Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Sat, 8 Mar 2025 22:20:19 +0300 Subject: [PATCH 18/20] added a good till crossing tif value --- exchanges/order/order_test.go | 3 +++ exchanges/order/order_types.go | 3 ++- exchanges/order/orders.go | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 16e29ff8c1a..abf07787c98 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -1742,6 +1742,9 @@ func TestStringToTimeInForce(t *testing.T) { "abcdfeg": {TIF: UnknownTIF, Error: ErrInvalidTimeInForce}, "PoC": {TIF: PostOnly}, "PendingORCANCEL": {TIF: PostOnly}, + "GTX": {TIF: GoodTillCrossing}, + "GOOD_TILL_CROSSING": {TIF: GoodTillCrossing}, + "Good Til crossing": {TIF: GoodTillCrossing}, } for tk := range timeInForceStringToValueMap { diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 557bac9c80e..16a050d1a6f 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -416,12 +416,13 @@ const ( GoodTillCancel TimeInForce = 1 << iota GoodTillDay GoodTillTime + GoodTillCrossing FillOrKill ImmediateOrCancel PostOnly UnknownTIF - supportedTimeInForceFlag = GoodTillCancel | GoodTillDay | GoodTillTime | FillOrKill | ImmediateOrCancel | PostOnly + supportedTimeInForceFlag = GoodTillCancel | GoodTillDay | GoodTillTime | GoodTillCrossing | FillOrKill | ImmediateOrCancel | PostOnly ) // ByPrice used for sorting orders by price diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 70a78317499..131b3456818 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -734,6 +734,9 @@ func (t TimeInForce) String() string { if t.Is(GoodTillTime) { tifStrings = append(tifStrings, "GTT") } + if t.Is(GoodTillCrossing) { + tifStrings = append(tifStrings, "GTX") + } if t.Is(FillOrKill) { tifStrings = append(tifStrings, "FOK") } @@ -1262,6 +1265,8 @@ func StringToTimeInForce(timeInForce string) (TimeInForce, error) { return GoodTillDay, nil case "GOODTILLTIME", "GOOD_TIL_TIME", GoodTillTime.String(): return GoodTillTime, nil + case "GOODTILLCROSSING", "GOOD_TIL_CROSSING", "GOOD TIL CROSSING", GoodTillCrossing.String(), "GOOD_TILL_CROSSING": + return GoodTillCrossing, nil case "FILLORKILL", "FILL_OR_KILL", FillOrKill.String(): return FillOrKill, nil case "POST_ONLY_GOOD_TILL_CANCELLED", PostOnly.String(), "POC", "POST_ONLY", "PENDINGORCANCEL": From 1679be7d87c2b963671d721c24aa03a2b7eb9887 Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Sun, 9 Mar 2025 23:37:46 +0300 Subject: [PATCH 19/20] order type fix and fix related tim-in-force entries --- exchanges/btcmarkets/btcmarkets_test.go | 48 +++++++++++----------- exchanges/btcmarkets/btcmarkets_wrapper.go | 2 +- exchanges/deribit/deribit_wrapper.go | 2 +- exchanges/kucoin/kucoin_wrapper.go | 9 ++-- exchanges/order/order_test.go | 2 + exchanges/order/order_types.go | 2 - exchanges/order/orders.go | 2 +- 7 files changed, 33 insertions(+), 34 deletions(-) diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index e5b9bc7746a..f93d188fddf 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -195,27 +195,27 @@ func TestGetTradeByID(t *testing.T) { func TestSubmitOrder(t *testing.T) { t.Parallel() _, err := b.SubmitOrder(context.Background(), &order.Submit{ - Exchange: b.Name, - Price: 100, - Amount: 1, - Type: order.TrailingStop, - AssetType: asset.Spot, - Side: order.Bid, - Pair: currency.NewPair(currency.BTC, currency.AUD), - PostOnly: true, + Exchange: b.Name, + Price: 100, + Amount: 1, + Type: order.TrailingStop, + AssetType: asset.Spot, + Side: order.Bid, + Pair: currency.NewPair(currency.BTC, currency.AUD), + TimeInForce: order.PostOnly, }) if !errors.Is(err, order.ErrTypeIsInvalid) { t.Fatalf("received: '%v' but expected: '%v'", err, order.ErrTypeIsInvalid) } _, err = b.SubmitOrder(context.Background(), &order.Submit{ - Exchange: b.Name, - Price: 100, - Amount: 1, - Type: order.Limit, - AssetType: asset.Spot, - Side: order.AnySide, - Pair: currency.NewPair(currency.BTC, currency.AUD), - PostOnly: true, + Exchange: b.Name, + Price: 100, + Amount: 1, + Type: order.Limit, + AssetType: asset.Spot, + Side: order.AnySide, + Pair: currency.NewPair(currency.BTC, currency.AUD), + TimeInForce: order.PostOnly, }) if !errors.Is(err, order.ErrSideIsInvalid) { t.Fatalf("received: '%v' but expected: '%v'", err, order.ErrSideIsInvalid) @@ -224,14 +224,14 @@ func TestSubmitOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders) _, err = b.SubmitOrder(context.Background(), &order.Submit{ - Exchange: b.Name, - Price: 100, - Amount: 1, - Type: order.Limit, - AssetType: asset.Spot, - Side: order.Bid, - Pair: currency.NewPair(currency.BTC, currency.AUD), - PostOnly: true, + Exchange: b.Name, + Price: 100, + Amount: 1, + Type: order.Limit, + AssetType: asset.Spot, + Side: order.Bid, + Pair: currency.NewPair(currency.BTC, currency.AUD), + TimeInForce: order.PostOnly, }) if err != nil { t.Error(err) diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 6d15bf7086b..a34303f61a9 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -465,7 +465,7 @@ func (b *BTCMarkets) SubmitOrder(ctx context.Context, s *order.Submit) (*order.S b.getTimeInForce(s), "", s.ClientID, - s.PostOnly) + s.TimeInForce.Is(order.PostOnly)) if err != nil { return nil, err } diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index 03f3a8607af..9005967c30c 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -611,7 +611,7 @@ func (d *Deribit) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm Amount: s.Amount, Price: s.Price, TriggerPrice: s.TriggerPrice, - PostOnly: s.PostOnly, + PostOnly: s.TimeInForce.Is(order.PostOnly), ReduceOnly: s.ReduceOnly, } switch { diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index d283fe9e2aa..706c645cc62 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -669,7 +669,7 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm Leverage: s.Leverage, VisibleSize: 0, ReduceOnly: s.ReduceOnly, - PostOnly: s.PostOnly, + PostOnly: s.TimeInForce.Is(order.PostOnly), Hidden: s.Hidden, Stop: stopOrderBoundary, StopPrice: s.TriggerPrice, @@ -696,7 +696,6 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm case s.TimeInForce.Is(order.FillOrKill) || s.TimeInForce.Is(order.ImmediateOrCancel): timeInForce = s.TimeInForce.String() - case s.PostOnly: default: timeInForce = order.GoodTillCancel.String() } @@ -721,7 +720,7 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm s.Pair.String(), oType.Lower(), "", stopType, "", SpotTradeType, timeInForce, s.Amount, s.Price, stopPrice, 0, - 0, 0, s.PostOnly, s.Hidden, s.Iceberg) + 0, 0, s.TimeInForce.Is(order.PostOnly), s.Hidden, s.Iceberg) if err != nil { return nil, err } @@ -734,7 +733,7 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm OrderType: s.Type.Lower(), Size: s.Amount, Price: s.Price, - PostOnly: s.PostOnly, + PostOnly: s.TimeInForce.Is(order.PostOnly), Hidden: s.Hidden, TimeInForce: timeInForce, Iceberg: s.Iceberg, @@ -794,7 +793,7 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm Price: s.Price, Size: s.Amount, VisibleSize: s.Amount, - PostOnly: s.PostOnly, + PostOnly: s.TimeInForce.Is(order.PostOnly), Hidden: s.Hidden, AutoBorrow: s.AutoBorrow, AutoRepay: s.AutoBorrow, diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index abf07787c98..a7b1f44c086 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -936,11 +936,13 @@ func TestTimeInForceIs(t *testing.T) { tifValuesMap := map[TimeInForce][]TimeInForce{ GoodTillCancel | PostOnly: {GoodTillCancel, PostOnly}, GoodTillCancel: {GoodTillCancel}, + GoodTillCrossing | PostOnly: {GoodTillCrossing, PostOnly}, ImmediateOrCancel | PostOnly: {ImmediateOrCancel, PostOnly}, GoodTillDay: {GoodTillDay}, FillOrKill | PostOnly: {FillOrKill, PostOnly}, FillOrKill: {FillOrKill}, PostOnly: {PostOnly}, + GoodTillCrossing: {GoodTillCrossing}, } for tif := range tifValuesMap { for _, v := range tifValuesMap[tif] { diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 16a050d1a6f..74587374a33 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -49,7 +49,6 @@ type Submit struct { // TimeInForce holds time in force values TimeInForce TimeInForce - PostOnly bool // ReduceOnly reduces a position instead of opening an opposing // position; this also equates to closing the position in huobi_wrapper.go // swaps. @@ -109,7 +108,6 @@ type SubmitResponse struct { AssetType asset.Item TimeInForce TimeInForce - PostOnly bool ReduceOnly bool Leverage float64 Price float64 diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 131b3456818..499a00834eb 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/encoding/json" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/validate" "github.com/thrasher-corp/gocryptotrader/log" @@ -490,7 +491,6 @@ func (s *Submit) DeriveSubmitResponse(orderID string) (*SubmitResponse, error) { AssetType: s.AssetType, TimeInForce: s.TimeInForce, - PostOnly: s.PostOnly, ReduceOnly: s.ReduceOnly, Leverage: s.Leverage, Price: s.Price, From 3f752f2973637464cd0b1910ffda522359d7357a Mon Sep 17 00:00:00 2001 From: samuael <39623015+samuael@users.noreply.github.com> Date: Mon, 10 Mar 2025 18:17:10 +0300 Subject: [PATCH 20/20] update time-in-force unmarshaling and unit test --- exchanges/order/order_test.go | 19 +++++++++++++++++-- exchanges/order/orders.go | 7 +++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index a7b1f44c086..bc8ebff8b20 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -381,7 +381,7 @@ func TestFilterOrdersByType(t *testing.T) { } FilterOrdersByType(&orders, AnyType) - assert.Lenf(t, orders, 3, "Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) + assert.Lenf(t, orders, 2, "Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) FilterOrdersByType(&orders, Limit) assert.Lenf(t, orders, 2, "Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) @@ -1867,6 +1867,21 @@ func TestMarshalOrder(t *testing.T) { } j, err := json.Marshal(orderSubmit) require.NoError(t, err, "json.Marshal must not error") - exp := []byte(`{"Exchange":"test","Type":4,"Side":"BUY","Pair":"BTC-USDT","AssetType":"spot","ImmediateOrCancel":false,"FillOrKill":false,"PostOnly":false,"ReduceOnly":false,"Leverage":0,"Price":1000,"Amount":1,"QuoteAmount":0,"TriggerPrice":0,"TriggerPriceType":0,"ClientID":"","ClientOrderID":"","AutoBorrow":false,"MarginType":"multi","RetrieveFees":false,"RetrieveFeeDelay":0,"RiskManagementModes":{"Mode":"","TakeProfit":{"Enabled":false,"TriggerPriceType":0,"Price":0,"LimitPrice":0,"OrderType":0},"StopLoss":{"Enabled":false,"TriggerPriceType":0,"Price":0,"LimitPrice":0,"OrderType":0},"StopEntry":{"Enabled":false,"TriggerPriceType":0,"Price":0,"LimitPrice":0,"OrderType":0}},"Hidden":false,"Iceberg":false,"TrackingMode":0,"TrackingValue":0}`) + exp := []byte(`{"Exchange":"test","Type":4,"Side":"BUY","Pair":"BTC-USDT","AssetType":"spot","TimeInForce":"","ReduceOnly":false,"Leverage":0,"Price":1000,"Amount":1,"QuoteAmount":0,"TriggerPrice":0,"TriggerPriceType":0,"ClientID":"","ClientOrderID":"","AutoBorrow":false,"MarginType":"multi","RetrieveFees":false,"RetrieveFeeDelay":0,"RiskManagementModes":{"Mode":"","TakeProfit":{"Enabled":false,"TriggerPriceType":0,"Price":0,"LimitPrice":0,"OrderType":0},"StopLoss":{"Enabled":false,"TriggerPriceType":0,"Price":0,"LimitPrice":0,"OrderType":0},"StopEntry":{"Enabled":false,"TriggerPriceType":0,"Price":0,"LimitPrice":0,"OrderType":0}},"Hidden":false,"Iceberg":false,"TrackingMode":0,"TrackingValue":0}`) assert.Equal(t, exp, j) } + +func TestMarshalJSON(t *testing.T) { + t.Parallel() + data, err := json.Marshal(GoodTillCrossing) + require.NoError(t, err) + assert.Equal(t, []byte(`"GTX"`), data) + + data = []byte(`{"tif":"IOC"}`) + target := &struct { + TimeInForce TimeInForce `json:"tif"` + }{} + err = json.Unmarshal(data, &target) + require.NoError(t, err) + assert.Equal(t, "IOC", target.TimeInForce.String()) +} diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 499a00834eb..15fed8be3d0 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -14,7 +14,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/encoding/json" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/validate" "github.com/thrasher-corp/gocryptotrader/log" @@ -721,7 +720,6 @@ func (t Type) String() string { // String implements the stringer interface. func (t TimeInForce) String() string { var tifStrings []string - if t.Is(ImmediateOrCancel) { tifStrings = append(tifStrings, "IOC") } @@ -765,6 +763,11 @@ func (t *TimeInForce) UnmarshalJSON(data []byte) error { return nil } +// MarshalJSON returns the JSON-encoded order time-in-force value +func (t TimeInForce) MarshalJSON() ([]byte, error) { + return []byte(`"` + t.String() + `"`), nil +} + // Lower returns the type lower case string func (t Type) Lower() string { return strings.ToLower(t.String())