From 3e5ad9a75f75f7afa8e1592a6fe59e8c8ab062bd Mon Sep 17 00:00:00 2001 From: Rostislav Lyupa <> Date: Tue, 13 Aug 2024 02:34:39 +0300 Subject: [PATCH 1/2] fix PUT /fapi/v1/order --- v2/futures/client.go | 14 +++ v2/futures/order_service.go | 172 +++++++++---------------------- v2/futures/order_service_test.go | 136 +++++++++++++++--------- 3 files changed, 150 insertions(+), 172 deletions(-) diff --git a/v2/futures/client.go b/v2/futures/client.go index a82aee6e..67ec79a4 100644 --- a/v2/futures/client.go +++ b/v2/futures/client.go @@ -39,6 +39,10 @@ type OrderExecutionType string // OrderStatusType define order status type type OrderStatusType string +// PriceMatchType define priceMatch type +// Can't be passed together with price +type PriceMatchType string + // SymbolType define symbol type type SymbolType string @@ -117,6 +121,16 @@ const ( OrderStatusTypeNewInsurance OrderStatusType = "NEW_INSURANCE" OrderStatusTypeNewADL OrderStatusType = "NEW_ADL" + PriceMatchTypeOpponent PriceMatchType = "OPPONENT" + PriceMatchTypeOpponent5 PriceMatchType = "OPPONENT_5" + PriceMatchTypeOpponent10 PriceMatchType = "OPPONENT_10" + PriceMatchTypeOpponent20 PriceMatchType = "OPPONENT_20" + PriceMatchTypeQueue PriceMatchType = "QUEUE" + PriceMatchTypeQueue5 PriceMatchType = "QUEUE_5" + PriceMatchTypeQueue10 PriceMatchType = "QUEUE_10" + PriceMatchTypeQueue20 PriceMatchType = "QUEUE_20" + PriceMatchTypeNone PriceMatchType = "NONE" + SymbolTypeFuture SymbolType = "FUTURE" WorkingTypeMarkPrice WorkingType = "MARK_PRICE" diff --git a/v2/futures/order_service.go b/v2/futures/order_service.go index 7b011264..1c38c087 100644 --- a/v2/futures/order_service.go +++ b/v2/futures/order_service.go @@ -133,7 +133,6 @@ func (s *CreateOrderService) ClosePosition(closePosition bool) *CreateOrderServi } func (s *CreateOrderService) createOrder(ctx context.Context, endpoint string, opts ...RequestOption) (data []byte, header *http.Header, err error) { - r := &request{ method: http.MethodPost, endpoint: endpoint, @@ -241,24 +240,13 @@ type CreateOrderResponse struct { // ModifyOrderService create order type ModifyOrderService struct { c *Client - symbol string orderID *int64 origClientOrderID *string + symbol string side SideType - positionSide *PositionSideType - orderType OrderType - timeInForce *TimeInForceType quantity string - reduceOnly *string price *string - newClientOrderID *string - stopPrice *string - workingType *WorkingType - activationPrice *string - callbackRate *string - priceProtect *string - newOrderRespType NewOrderRespType - closePosition *string + priceMatch *PriceMatchType } // Symbol set symbol @@ -283,141 +271,46 @@ func (s *ModifyOrderService) Side(side SideType) *ModifyOrderService { return s } -// PositionSide set side -func (s *ModifyOrderService) PositionSide(positionSide PositionSideType) *ModifyOrderService { - s.positionSide = &positionSide - return s -} - -// Type set type -func (s *ModifyOrderService) Type(orderType OrderType) *ModifyOrderService { - s.orderType = orderType - return s -} - -// TimeInForce set timeInForce -func (s *ModifyOrderService) TimeInForce(timeInForce TimeInForceType) *ModifyOrderService { - s.timeInForce = &timeInForce - return s -} - // Quantity set quantity func (s *ModifyOrderService) Quantity(quantity string) *ModifyOrderService { s.quantity = quantity return s } -// ReduceOnly set reduceOnly -func (s *ModifyOrderService) ReduceOnly(reduceOnly bool) *ModifyOrderService { - reduceOnlyStr := strconv.FormatBool(reduceOnly) - s.reduceOnly = &reduceOnlyStr - return s -} - // Price set price func (s *ModifyOrderService) Price(price string) *ModifyOrderService { s.price = &price return s } -// NewClientOrderID set newClientOrderID -func (s *ModifyOrderService) NewClientOrderID(newClientOrderID string) *ModifyOrderService { - s.newClientOrderID = &newClientOrderID - return s -} - -// StopPrice set stopPrice -func (s *ModifyOrderService) StopPrice(stopPrice string) *ModifyOrderService { - s.stopPrice = &stopPrice - return s -} - -// WorkingType set workingType -func (s *ModifyOrderService) WorkingType(workingType WorkingType) *ModifyOrderService { - s.workingType = &workingType - return s -} - -// ActivationPrice set activationPrice -func (s *ModifyOrderService) ActivationPrice(activationPrice string) *ModifyOrderService { - s.activationPrice = &activationPrice - return s -} - -// CallbackRate set callbackRate -func (s *ModifyOrderService) CallbackRate(callbackRate string) *ModifyOrderService { - s.callbackRate = &callbackRate - return s -} - -// PriceProtect set priceProtect -func (s *ModifyOrderService) PriceProtect(priceProtect bool) *ModifyOrderService { - priceProtectStr := strconv.FormatBool(priceProtect) - s.priceProtect = &priceProtectStr - return s -} - -// NewOrderResponseType set newOrderResponseType -func (s *ModifyOrderService) NewOrderResponseType(newOrderResponseType NewOrderRespType) *ModifyOrderService { - s.newOrderRespType = newOrderResponseType - return s -} - -// ClosePosition set closePosition -func (s *ModifyOrderService) ClosePosition(closePosition bool) *ModifyOrderService { - closePositionStr := strconv.FormatBool(closePosition) - s.closePosition = &closePositionStr +// PriceMatch set priceMatch +func (s *ModifyOrderService) PriceMatch(priceMatch PriceMatchType) *ModifyOrderService { + s.priceMatch = &priceMatch return s } func (s *ModifyOrderService) modifyOrder(ctx context.Context, endpoint string, opts ...RequestOption) (data []byte, header *http.Header, err error) { - r := &request{ method: http.MethodPut, endpoint: endpoint, secType: secTypeSigned, } m := params{ - "symbol": s.symbol, - "side": s.side, - "type": s.orderType, - "newOrderRespType": s.newOrderRespType, + "symbol": s.symbol, + "side": s.side, + "quantity": s.quantity, } - if s.quantity != "" { - m["quantity"] = s.quantity - } - if s.positionSide != nil { - m["positionSide"] = *s.positionSide - } - if s.timeInForce != nil { - m["timeInForce"] = *s.timeInForce + if s.orderID != nil { + m["orderId"] = *s.orderID } - if s.reduceOnly != nil { - m["reduceOnly"] = *s.reduceOnly + if s.origClientOrderID != nil { + m["origClientOrderId"] = *s.origClientOrderID } if s.price != nil { m["price"] = *s.price } - if s.newClientOrderID != nil { - m["newClientOrderId"] = *s.newClientOrderID - } - if s.stopPrice != nil { - m["stopPrice"] = *s.stopPrice - } - if s.workingType != nil { - m["workingType"] = *s.workingType - } - if s.priceProtect != nil { - m["priceProtect"] = *s.priceProtect - } - if s.activationPrice != nil { - m["activationPrice"] = *s.activationPrice - } - if s.callbackRate != nil { - m["callbackRate"] = *s.callbackRate - } - if s.closePosition != nil { - m["closePosition"] = *s.closePosition + if s.priceMatch != nil { + m["priceMatch"] = *s.priceMatch } r.setFormParams(m) data, header, err = s.c.callAPI(ctx, r, opts...) @@ -428,12 +321,12 @@ func (s *ModifyOrderService) modifyOrder(ctx context.Context, endpoint string, o } // Do send request -func (s *ModifyOrderService) Do(ctx context.Context, opts ...RequestOption) (res *Order, err error) { +func (s *ModifyOrderService) Do(ctx context.Context, opts ...RequestOption) (res *ModifyOrderResponse, err error) { data, _, err := s.modifyOrder(ctx, "/fapi/v1/order", opts...) if err != nil { return nil, err } - res = new(Order) + res = new(ModifyOrderResponse) err = json.Unmarshal(data, res) if err != nil { @@ -442,6 +335,34 @@ func (s *ModifyOrderService) Do(ctx context.Context, opts ...RequestOption) (res return res, nil } +type ModifyOrderResponse struct { + OrderID int64 `json:"orderId"` + Symbol string `json:"symbol"` + Pair string `json:"pair"` + Status OrderStatusType `json:"status"` + ClientOrderID string `json:"clientOrderId"` + Price string `json:"price"` + AveragePrice string `json:"avgPrice"` + OriginalQuantity string `json:"origQty"` + ExecutedQuantity string `json:"executedQty"` + CumulativeQuantity string `json:"cumQty"` + CumulativeBase string `json:"cumBase"` + TimeInForce TimeInForceType `json:"timeInForce"` + Type OrderType `json:"type"` + ReduceOnly bool `json:"reduceOnly"` + ClosePosition bool `json:"closePosition"` + Side SideType `json:"side"` + PositionSide PositionSideType `json:"positionSide"` + StopPrice string `json:"stopPrice"` + WorkingType WorkingType `json:"workingType"` + PriceProtect bool `json:"priceProtect"` // if conditional order trigger is protected + OriginalType OrderType `json:"origType"` + PriceMatch PriceMatchType `json:"priceMatch"` + SelfTradePreventionMode string `json:"selfTradePreventionMode"` + GoodTillDate int64 `json:"goodTillDate"` // order pre-set auto cancel time for TIF GTD order + UpdateTime int64 `json:"updateTime"` +} + // ListOpenOrdersService list opened orders type ListOpenOrdersService struct { c *Client @@ -588,7 +509,7 @@ type Order struct { ReduceOnly bool `json:"reduceOnly"` OrigQuantity string `json:"origQty"` ExecutedQuantity string `json:"executedQty"` - CumQuantity string `json:"cumQty"` + CumQuantity string `json:"cumQty"` // deprecated: use ExecutedQuantity instead CumQuote string `json:"cumQuote"` Status OrderStatusType `json:"status"` TimeInForce TimeInForceType `json:"timeInForce"` @@ -737,7 +658,7 @@ func (s *CancelOrderService) Do(ctx context.Context, opts ...RequestOption) (res // CancelOrderResponse define response of canceling order type CancelOrderResponse struct { ClientOrderID string `json:"clientOrderId"` - CumQuantity string `json:"cumQty"` + CumQuantity string `json:"cumQty"` // deprecated: use ExecutedQuantity instead CumQuote string `json:"cumQuote"` ExecutedQuantity string `json:"executedQty"` OrderID int64 `json:"orderId"` @@ -874,6 +795,7 @@ func (s *ListLiquidationOrdersService) Limit(limit int) *ListLiquidationOrdersSe } // Do send request +// Deprecated: use /fapi/v1/forceOrders instead func (s *ListLiquidationOrdersService) Do(ctx context.Context, opts ...RequestOption) (res []*LiquidationOrder, err error) { r := &request{ method: http.MethodGet, diff --git a/v2/futures/order_service_test.go b/v2/futures/order_service_test.go index ff58feae..f142aed6 100644 --- a/v2/futures/order_service_test.go +++ b/v2/futures/order_service_test.go @@ -438,74 +438,116 @@ func (s *orderServiceTestSuite) TestListOrders() { func (s *orderServiceTestSuite) TestModifyOrder() { data := []byte(`{ - "clientOrderId": "myOrder1", - "cumQty": "0", - "cumQuote": "0", + "orderId": 20072994037, + "symbol": "BTCUSDT", + "pair": "BTCUSDT", + "status": "NEW", + "clientOrderId": "LJ9R4QZDihCaS8UAOOLpgW", + "price": "30005", + "avgPrice": "0.0", + "origQty": "1", "executedQty": "0", - "orderId": 283194212, - "origQty": "11", - "price": "8301", + "cumQty": "0", + "cumBase": "0", + "timeInForce": "GTC", + "type": "LIMIT", "reduceOnly": false, + "closePosition": false, "side": "BUY", - "status": "CANCELED", - "stopPrice": "8300", - "symbol": "BTCUSDT", - "timeInForce": "GTC", - "type": "TAKE_PROFIT", - "updateTime": 1571110484038, + "positionSide": "LONG", + "stopPrice": "0", "workingType": "CONTRACT_PRICE", - "activatePrice": "10000", - "priceRate":"0.1", - "positionSide":"BOTH", - "priceProtect": false + "priceProtect": false, + "origType": "LIMIT", + "priceMatch": "NONE", + "selfTradePreventionMode": "NONE", + "goodTillDate": 0, + "updateTime": 1629182711600 }`) s.mockDo(data, nil) defer s.assertDo() + orderID := int64(20072994037) + origClientOrderID := "LJ9R4QZDihCaS8UAOOLpgW" symbol := "BTCUSDT" - orderID := int64(28) - origClientOrderID := "myOrder1" - price := "8301" - side := SideTypeSell + side := SideTypeBuy + quantity := "1" + price := "30005" + priceMatch := PriceMatchTypeNone s.assertReq(func(r *request) { e := newSignedRequest().setFormParams(params{ - "symbol": symbol, "orderId": orderID, "origClientOrderId": origClientOrderID, - "price": price, + "symbol": symbol, "side": side, + "quantity": quantity, + "price": price, + "priceMatch": priceMatch, }) s.assertRequestEqual(e, r) }) - res, err := s.client.NewModifyOrderService().Symbol(symbol).Side(side). - OrderID(orderID).OrigClientOrderID(origClientOrderID).Price(price). - Do(newContext()) + res, err := s.client.NewModifyOrderService().OrderID(orderID).OrigClientOrderID(origClientOrderID). + Symbol(symbol).Side(side).Quantity(quantity).Price(price).PriceMatch(priceMatch).Do(newContext()) r := s.r() r.NoError(err) - e := &Order{ - ClientOrderID: origClientOrderID, - CumQuantity: "0", - CumQuote: "0", - ExecutedQuantity: "0", - OrderID: 283194212, - OrigQuantity: "11", - Price: "8301", - ReduceOnly: false, - Side: SideTypeBuy, - Status: OrderStatusTypeCanceled, - StopPrice: "8300", - Symbol: symbol, - TimeInForce: TimeInForceTypeGTC, - Type: OrderTypeTakeProfit, - UpdateTime: 1571110484038, - WorkingType: WorkingTypeContractPrice, - ActivatePrice: "10000", - PriceRate: "0.1", - PositionSide: "BOTH", - PriceProtect: false, + e := &ModifyOrderResponse{ + OrderID: orderID, + Symbol: symbol, + Pair: "BTCUSDT", + Status: OrderStatusTypeNew, + ClientOrderID: origClientOrderID, + Price: price, + AveragePrice: "0.0", + OriginalQuantity: quantity, + ExecutedQuantity: "0", + CumulativeQuantity: "0", + CumulativeBase: "0", + TimeInForce: TimeInForceTypeGTC, + Type: OrderTypeLimit, + ReduceOnly: false, + ClosePosition: false, + Side: side, + PositionSide: PositionSideTypeLong, + StopPrice: "0", + WorkingType: WorkingTypeContractPrice, + PriceProtect: false, + OriginalType: OrderTypeLimit, + PriceMatch: priceMatch, + SelfTradePreventionMode: "NONE", + GoodTillDate: 0, + UpdateTime: 1629182711600, } - s.assertOrderEqual(e, res) + s.assertModifyOrderResponseEqual(e, res) +} + +func (s *baseOrderTestSuite) assertModifyOrderResponseEqual(e, a *ModifyOrderResponse) { + r := s.r() + r.Equal(e.OrderID, a.OrderID, "OrderID") + r.Equal(e.Symbol, a.Symbol, "Symbol") + r.Equal(e.Pair, a.Pair, "Pair") + r.Equal(e.Status, a.Status, "Status") + r.Equal(e.ClientOrderID, a.ClientOrderID, "ClientOrderID") + r.Equal(e.Price, a.Price, "Price") + r.Equal(e.AveragePrice, a.AveragePrice, "AveragePrice") + r.Equal(e.OriginalQuantity, a.OriginalQuantity, "OriginalQuantity") + r.Equal(e.ExecutedQuantity, a.ExecutedQuantity, "ExecutedQuantity") + r.Equal(e.CumulativeQuantity, a.CumulativeQuantity, "CumulativeQuantity") + r.Equal(e.CumulativeBase, a.CumulativeBase, "CumulativeBase") + r.Equal(e.TimeInForce, a.TimeInForce, "TimeInForce") + r.Equal(e.Type, a.Type, "Type") + r.Equal(e.ReduceOnly, a.ReduceOnly, "ReduceOnly") + r.Equal(e.ClosePosition, a.ClosePosition, "ClosePosition") + r.Equal(e.Side, a.Side, "Side") + r.Equal(e.PositionSide, a.PositionSide, "PositionSide") + r.Equal(e.StopPrice, a.StopPrice, "StopPrice") + r.Equal(e.WorkingType, a.WorkingType, "WorkingType") + r.Equal(e.PriceProtect, a.PriceProtect, "PriceProtect") + r.Equal(e.OriginalType, a.OriginalType, "OriginalType") + r.Equal(e.PriceMatch, a.PriceMatch, "PriceMatch") + r.Equal(e.SelfTradePreventionMode, a.SelfTradePreventionMode, "SelfTradePreventionMode") + r.Equal(e.GoodTillDate, a.GoodTillDate, "GoodTillDate") + r.Equal(e.UpdateTime, a.UpdateTime, "UpdateTime") } func (s *orderServiceTestSuite) TestCancelOrder() { From a3f8064431cc8213bfda0e801a7f5dfc19a913e3 Mon Sep 17 00:00:00 2001 From: Rostislav Lyupa <> Date: Tue, 13 Aug 2024 02:49:48 +0300 Subject: [PATCH 2/2] improve comments --- v2/futures/order_service.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/v2/futures/order_service.go b/v2/futures/order_service.go index 1c38c087..55fd5121 100644 --- a/v2/futures/order_service.go +++ b/v2/futures/order_service.go @@ -255,11 +255,13 @@ func (s *ModifyOrderService) Symbol(symbol string) *ModifyOrderService { return s } +// OrderID will prevail over OrigClientOrderID func (s *ModifyOrderService) OrderID(orderID int64) *ModifyOrderService { s.orderID = &orderID return s } +// OrigClientOrderID is not necessary if OrderID is provided func (s *ModifyOrderService) OrigClientOrderID(origClientOrderID string) *ModifyOrderService { s.origClientOrderID = &origClientOrderID return s @@ -320,7 +322,16 @@ func (s *ModifyOrderService) modifyOrder(ctx context.Context, endpoint string, o return data, header, nil } -// Do send request +// Do send request: +// - Either orderId or origClientOrderId must be sent, and the orderId will prevail if both are sent +// - Either price or priceMatch must be sent. Sending both will fail the request +// - When the new quantity or price doesn't satisfy PriceFilter / PercentPriceFilter / LotSizeFilter, +// amendment will be rejected and the order will stay as it is +// - However the order will be cancelled by the amendment in the following situations: +// -- when the order is in partially filled status and the new quantity <= executedQty +// -- when the order is TimeInForceTypeGTX and the new price will cause it to be executed immediately +// - One order can only be modified for less than 10000 times +// - Will set ModifyOrderResponse.SelfTradePreventionMode to "NONE" func (s *ModifyOrderService) Do(ctx context.Context, opts ...RequestOption) (res *ModifyOrderResponse, err error) { data, _, err := s.modifyOrder(ctx, "/fapi/v1/order", opts...) if err != nil {