Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add modify batch orders feature #647 #648

Merged
merged 2 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions v2/futures/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,11 @@ func (c *Client) NewCreateBatchOrdersService() *CreateBatchOrdersService {
return &CreateBatchOrdersService{c: c}
}

// NewModifyBatchOrdersService init modifying batch order service
func (c *Client) NewModifyBatchOrdersService() *ModifyBatchOrdersService {
return &ModifyBatchOrdersService{c: c}
}

// NewGetOrderService init get order service
func (c *Client) NewGetOrderService() *GetOrderService {
return &GetOrderService{c: c}
Expand Down
171 changes: 171 additions & 0 deletions v2/futures/order_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1086,3 +1086,174 @@ func (s *CreateBatchOrdersService) Do(ctx context.Context, opts ...RequestOption

return batchCreateOrdersResponse, nil
}

// ModifyOrder contains parameters for order modification request
type ModifyOrder struct {
orderID *int64
origClientOrderID *string
symbol string
side SideType
quantity string
price *string
priceMatch *PriceMatchType
}

// Symbol set symbol
func (s *ModifyOrder) Symbol(symbol string) *ModifyOrder {
s.symbol = symbol
return s
}

// OrderID will prevail over OrigClientOrderID
func (s *ModifyOrder) OrderID(orderID int64) *ModifyOrder {
s.orderID = &orderID
return s
}

// OrigClientOrderID is not necessary if OrderID is provided
func (s *ModifyOrder) OrigClientOrderID(origClientOrderID string) *ModifyOrder {
s.origClientOrderID = &origClientOrderID
return s
}

// Side set side
func (s *ModifyOrder) Side(side SideType) *ModifyOrder {
s.side = side
return s
}

// Quantity set quantity
func (s *ModifyOrder) Quantity(quantity string) *ModifyOrder {
s.quantity = quantity
return s
}

// Price set price
func (s *ModifyOrder) Price(price string) *ModifyOrder {
s.price = &price
return s
}

// PriceMatch set priceMatch
func (s *ModifyOrder) PriceMatch(priceMatch PriceMatchType) *ModifyOrder {
s.priceMatch = &priceMatch
return s
}

// ModifyBatchOrdersService handles batch modification of orders
type ModifyBatchOrdersService struct {
c *Client
orders []*ModifyOrder
}

// CreateBatchOrdersResponse contains the response from CreateBatchOrders operation
type ModifyBatchOrdersResponse struct {
// Total number of messages in the response
N int
// List of orders which were modified successfully which can have a length between 0 and N
Orders []*Order
// List of errors of length N, where each item corresponds to a nil value if
// the order from that specific index was placed succeessfully OR an non-nil *APIError if there was an error with
// the order at that index
Errors []error
}

func newModifyBatchOrdersResponse(n int) *ModifyBatchOrdersResponse {
return &ModifyBatchOrdersResponse{
N: n,
Errors: make([]error, n),
}
}

// OrderList set the list of ModifyOrder to be used in the ModifyBatchOrders operation
func (s *ModifyBatchOrdersService) OrderList(orders []*ModifyOrder) *ModifyBatchOrdersService {
s.orders = orders
return s
}

// Do sends a request to modify a batch of orders.
// It constructs the necessary parameters for each order and marshals them into a JSON payload.
// The function returns a ModifyBatchOrdersResponse, which contains the results of the modification attempt.
func (s *ModifyBatchOrdersService) Do(ctx context.Context, opts ...RequestOption) (res *ModifyBatchOrdersResponse, err error) {
// Create a new request with method PUT and the appropriate endpoint.
r := &request{
method: http.MethodPut,
endpoint: "/fapi/v1/batchOrders",
secType: secTypeSigned,
}

orders := []params{}
// Iterate through the orders to construct parameters for each order.
for _, order := range s.orders {
m := params{
"symbol": order.symbol,
"side": order.side,
"quantity": order.quantity,
"price": order.price,
}

// Convert orderID to string to avoid API error with code -1102.
if order.orderID != nil {
m["orderId"] = strconv.FormatInt(*order.orderID, 10)
}
if order.origClientOrderID != nil {
m["origClientOrderId"] = *order.origClientOrderID
}
if order.priceMatch != nil {
m["priceMatch"] = *order.priceMatch
}

orders = append(orders, m)
}

// Marshal the orders into a JSON payload.
b, err := json.Marshal(orders)
if err != nil {
return &ModifyBatchOrdersResponse{}, err
}

// Set the marshaled orders as form parameters.
m := params{
"batchOrders": string(b),
}
r.setFormParams(m)

// Call the API with the constructed request.
data, _, err := s.c.callAPI(ctx, r, opts...)
if err != nil {
return &ModifyBatchOrdersResponse{}, err
}

rawMessages := make([]*json.RawMessage, 0)
// Unmarshal the response into raw JSON messages.
err = json.Unmarshal(data, &rawMessages)
if err != nil {
return &ModifyBatchOrdersResponse{}, err
}

// Create a response object to hold the results.
batchModifyOrdersResponse := newModifyBatchOrdersResponse(len(rawMessages))
for i, j := range rawMessages {
// Check if the response contains an API error.
e := new(common.APIError)
if err := json.Unmarshal(*j, e); err != nil {
return nil, err
}

// If there's an error code or message, record it and continue.
if e.Code > 0 || e.Message != "" {
batchModifyOrdersResponse.Errors[i] = e
continue
}

// Otherwise, unmarshal the order information.
o := new(Order)
if err := json.Unmarshal(*j, o); err != nil {
return nil, err
}

batchModifyOrdersResponse.Orders = append(batchModifyOrdersResponse.Orders, o)
}

return batchModifyOrdersResponse, nil
}
102 changes: 102 additions & 0 deletions v2/futures/order_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,3 +823,105 @@ func (s *orderServiceTestSuite) TestCreateBatchOrders() {
}
r.EqualValues(e, res)
}

func (s *orderServiceTestSuite) TestModifyBatchOrders() {
data := []byte(`[
{
"orderId": 42042723,
"symbol": "BTCUSDT",
"status": "NEW",
"clientOrderId": "Ne7DEEvLvv8b8egTqrZceu",
"price": "99995.00",
"avgPrice": "0.00",
"origQty": "1",
"executedQty": "0",
"cumQty": "0",
"cumQuote": "0.00",
"timeInForce": "GTC",
"type": "LIMIT",
"reduceOnly": false,
"closePosition": false,
"side": "BUY",
"positionSide": "BOTH",
"stopPrice": "0.00",
"workingType": "CONTRACT_PRICE",
"priceProtect": false,
"origType": "LIMIT",
"priceMatch": "NONE",
"selfTradePreventionMode": "NONE",
"goodTillDate": 0,
"updateTime": 1733500988978
},
{
"code": -1102,
"msg": "Mandatory parameter 'price' was not sent, was empty/null, or malformed."
}
]`)
s.mockDo(data, nil)
defer s.assertDo()

orders := []*ModifyOrder{
new(ModifyOrder).
Symbol("BTCUSDT").
OrigClientOrderID("Ne7DEEvLvv8b8egTqrZceu").
Quantity("1").
Side("BUY").
Price("99995.00"),
new(ModifyOrder).
Symbol("BTCUSDT").
OrigClientOrderID("GYby67jLvv8b8egTr8Adrf").
Quantity("1").
Side("SELL").
Price("-100005.00"),
}

res, err := s.client.NewModifyBatchOrdersService().OrderList(orders).Do(context.Background())

r := s.r()
r.NoError(err)

r.Equal(1, len(res.Orders))

e := &Order{
Symbol: "BTCUSDT",
OrderID: 42042723,
ClientOrderID: "Ne7DEEvLvv8b8egTqrZceu",
Price: "99995.00",
ReduceOnly: false,
OrigQuantity: "1",
ExecutedQuantity: "0",
CumQuantity: "0",
CumQuote: "0.00",
Status: "NEW",
TimeInForce: "GTC",
Type: "LIMIT",
Side: "BUY",
StopPrice: "0.00",
Time: 0,
UpdateTime: 1733500988978,
WorkingType: "CONTRACT_PRICE",
ActivatePrice: "",
PriceRate: "",
AvgPrice: "0.00",
OrigType: "LIMIT",
PositionSide: "BOTH",
PriceProtect: false,
ClosePosition: false,
PriceMatch: "NONE",
SelfTradePreventionMode: "NONE",
GoodTillDate: 0,
}
s.assertOrderEqual(e, res.Orders[0])

r.Equal(
[]error{
nil,
&common.APIError{
Code: -1102,
Message: "Mandatory parameter 'price' was not sent, was empty/null, or malformed.",
Response: nil,
},
},
res.Errors)

}
Loading