Skip to content

Commit

Permalink
Add modify batch orders feature #647
Browse files Browse the repository at this point in the history
Implementation of order batches modification and test for it. Presented in the official api of binance: https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Modify-Multiple-Orders
  • Loading branch information
khanbekov committed Dec 6, 2024
1 parent 4c9ea21 commit d9999dc
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 0 deletions.
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
118 changes: 118 additions & 0 deletions v2/futures/order_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1086,3 +1086,121 @@ func (s *CreateBatchOrdersService) Do(ctx context.Context, opts ...RequestOption

return batchCreateOrdersResponse, nil
}

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

// 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 ModifyOrderService to be used in the ModifyBatchOrders operation
func (s *ModifyBatchOrdersService) OrderList(orders []*ModifyOrderService) *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
}
104 changes: 104 additions & 0 deletions v2/futures/order_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,3 +823,107 @@ 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 := []*CreateOrderService{
s.client.NewCreateOrderService().
Symbol("BTCUSDT").
Side(SideTypeBuy).
Type(OrderTypeLimit).
Quantity("1").
Price("99995.00").
TimeInForce(TimeInForceTypeGTC),
s.client.NewCreateOrderService().
Symbol("BTCUSDT").
Side(SideTypeSell).
Type(OrderTypeLimit).
Quantity("1").
Price("-100005.00").
TimeInForce(TimeInForceTypeGTC),
}

res, err := s.client.NewCreateBatchOrdersService().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)

}

0 comments on commit d9999dc

Please sign in to comment.