Skip to content

Commit d9999dc

Browse files
committed
Add modify batch orders feature #647
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
1 parent 4c9ea21 commit d9999dc

File tree

3 files changed

+227
-0
lines changed

3 files changed

+227
-0
lines changed

v2/futures/client.go

+5
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,11 @@ func (c *Client) NewCreateBatchOrdersService() *CreateBatchOrdersService {
480480
return &CreateBatchOrdersService{c: c}
481481
}
482482

483+
// NewModifyBatchOrdersService init modifying batch order service
484+
func (c *Client) NewModifyBatchOrdersService() *ModifyBatchOrdersService {
485+
return &ModifyBatchOrdersService{c: c}
486+
}
487+
483488
// NewGetOrderService init get order service
484489
func (c *Client) NewGetOrderService() *GetOrderService {
485490
return &GetOrderService{c: c}

v2/futures/order_service.go

+118
Original file line numberDiff line numberDiff line change
@@ -1086,3 +1086,121 @@ func (s *CreateBatchOrdersService) Do(ctx context.Context, opts ...RequestOption
10861086

10871087
return batchCreateOrdersResponse, nil
10881088
}
1089+
1090+
// ModifyBatchOrdersService handles batch modification of orders
1091+
type ModifyBatchOrdersService struct {
1092+
c *Client
1093+
orders []*ModifyOrderService
1094+
}
1095+
1096+
// CreateBatchOrdersResponse contains the response from CreateBatchOrders operation
1097+
type ModifyBatchOrdersResponse struct {
1098+
// Total number of messages in the response
1099+
N int
1100+
// List of orders which were modified successfully which can have a length between 0 and N
1101+
Orders []*Order
1102+
// List of errors of length N, where each item corresponds to a nil value if
1103+
// the order from that specific index was placed succeessfully OR an non-nil *APIError if there was an error with
1104+
// the order at that index
1105+
Errors []error
1106+
}
1107+
1108+
func newModifyBatchOrdersResponse(n int) *ModifyBatchOrdersResponse {
1109+
return &ModifyBatchOrdersResponse{
1110+
N: n,
1111+
Errors: make([]error, n),
1112+
}
1113+
}
1114+
1115+
// OrderList set the list of ModifyOrderService to be used in the ModifyBatchOrders operation
1116+
func (s *ModifyBatchOrdersService) OrderList(orders []*ModifyOrderService) *ModifyBatchOrdersService {
1117+
s.orders = orders
1118+
return s
1119+
}
1120+
1121+
// Do sends a request to modify a batch of orders.
1122+
// It constructs the necessary parameters for each order and marshals them into a JSON payload.
1123+
// The function returns a ModifyBatchOrdersResponse, which contains the results of the modification attempt.
1124+
func (s *ModifyBatchOrdersService) Do(ctx context.Context, opts ...RequestOption) (res *ModifyBatchOrdersResponse, err error) {
1125+
// Create a new request with method PUT and the appropriate endpoint.
1126+
r := &request{
1127+
method: http.MethodPut,
1128+
endpoint: "/fapi/v1/batchOrders",
1129+
secType: secTypeSigned,
1130+
}
1131+
1132+
orders := []params{}
1133+
// Iterate through the orders to construct parameters for each order.
1134+
for _, order := range s.orders {
1135+
m := params{
1136+
"symbol": order.symbol,
1137+
"side": order.side,
1138+
"quantity": order.quantity,
1139+
"price": order.price,
1140+
}
1141+
1142+
// Convert orderID to string to avoid API error with code -1102.
1143+
if order.orderID != nil {
1144+
m["orderId"] = strconv.FormatInt(*order.orderID, 10)
1145+
}
1146+
if order.origClientOrderID != nil {
1147+
m["origClientOrderId"] = *order.origClientOrderID
1148+
}
1149+
if order.priceMatch != nil {
1150+
m["priceMatch"] = *order.priceMatch
1151+
}
1152+
1153+
orders = append(orders, m)
1154+
}
1155+
1156+
// Marshal the orders into a JSON payload.
1157+
b, err := json.Marshal(orders)
1158+
if err != nil {
1159+
return &ModifyBatchOrdersResponse{}, err
1160+
}
1161+
1162+
// Set the marshaled orders as form parameters.
1163+
m := params{
1164+
"batchOrders": string(b),
1165+
}
1166+
r.setFormParams(m)
1167+
1168+
// Call the API with the constructed request.
1169+
data, _, err := s.c.callAPI(ctx, r, opts...)
1170+
if err != nil {
1171+
return &ModifyBatchOrdersResponse{}, err
1172+
}
1173+
1174+
rawMessages := make([]*json.RawMessage, 0)
1175+
// Unmarshal the response into raw JSON messages.
1176+
err = json.Unmarshal(data, &rawMessages)
1177+
if err != nil {
1178+
return &ModifyBatchOrdersResponse{}, err
1179+
}
1180+
1181+
// Create a response object to hold the results.
1182+
batchModifyOrdersResponse := newModifyBatchOrdersResponse(len(rawMessages))
1183+
for i, j := range rawMessages {
1184+
// Check if the response contains an API error.
1185+
e := new(common.APIError)
1186+
if err := json.Unmarshal(*j, e); err != nil {
1187+
return nil, err
1188+
}
1189+
1190+
// If there's an error code or message, record it and continue.
1191+
if e.Code > 0 || e.Message != "" {
1192+
batchModifyOrdersResponse.Errors[i] = e
1193+
continue
1194+
}
1195+
1196+
// Otherwise, unmarshal the order information.
1197+
o := new(Order)
1198+
if err := json.Unmarshal(*j, o); err != nil {
1199+
return nil, err
1200+
}
1201+
1202+
batchModifyOrdersResponse.Orders = append(batchModifyOrdersResponse.Orders, o)
1203+
}
1204+
1205+
return batchModifyOrdersResponse, nil
1206+
}

v2/futures/order_service_test.go

+104
Original file line numberDiff line numberDiff line change
@@ -823,3 +823,107 @@ func (s *orderServiceTestSuite) TestCreateBatchOrders() {
823823
}
824824
r.EqualValues(e, res)
825825
}
826+
827+
func (s *orderServiceTestSuite) TestModifyBatchOrders() {
828+
data := []byte(`[
829+
{
830+
"orderId": 42042723,
831+
"symbol": "BTCUSDT",
832+
"status": "NEW",
833+
"clientOrderId": "Ne7DEEvLvv8b8egTqrZceu",
834+
"price": "99995.00",
835+
"avgPrice": "0.00",
836+
"origQty": "1",
837+
"executedQty": "0",
838+
"cumQty": "0",
839+
"cumQuote": "0.00",
840+
"timeInForce": "GTC",
841+
"type": "LIMIT",
842+
"reduceOnly": false,
843+
"closePosition": false,
844+
"side": "BUY",
845+
"positionSide": "BOTH",
846+
"stopPrice": "0.00",
847+
"workingType": "CONTRACT_PRICE",
848+
"priceProtect": false,
849+
"origType": "LIMIT",
850+
"priceMatch": "NONE",
851+
"selfTradePreventionMode": "NONE",
852+
"goodTillDate": 0,
853+
"updateTime": 1733500988978
854+
},
855+
{
856+
"code": -1102,
857+
"msg": "Mandatory parameter 'price' was not sent, was empty/null, or malformed."
858+
}
859+
]`)
860+
s.mockDo(data, nil)
861+
defer s.assertDo()
862+
863+
orders := []*CreateOrderService{
864+
s.client.NewCreateOrderService().
865+
Symbol("BTCUSDT").
866+
Side(SideTypeBuy).
867+
Type(OrderTypeLimit).
868+
Quantity("1").
869+
Price("99995.00").
870+
TimeInForce(TimeInForceTypeGTC),
871+
s.client.NewCreateOrderService().
872+
Symbol("BTCUSDT").
873+
Side(SideTypeSell).
874+
Type(OrderTypeLimit).
875+
Quantity("1").
876+
Price("-100005.00").
877+
TimeInForce(TimeInForceTypeGTC),
878+
}
879+
880+
res, err := s.client.NewCreateBatchOrdersService().OrderList(orders).Do(context.Background())
881+
882+
r := s.r()
883+
r.NoError(err)
884+
885+
r.Equal(1, len(res.Orders))
886+
887+
e := &Order{
888+
Symbol: "BTCUSDT",
889+
OrderID: 42042723,
890+
ClientOrderID: "Ne7DEEvLvv8b8egTqrZceu",
891+
Price: "99995.00",
892+
ReduceOnly: false,
893+
OrigQuantity: "1",
894+
ExecutedQuantity: "0",
895+
CumQuantity: "0",
896+
CumQuote: "0.00",
897+
Status: "NEW",
898+
TimeInForce: "GTC",
899+
Type: "LIMIT",
900+
Side: "BUY",
901+
StopPrice: "0.00",
902+
Time: 0,
903+
UpdateTime: 1733500988978,
904+
WorkingType: "CONTRACT_PRICE",
905+
ActivatePrice: "",
906+
PriceRate: "",
907+
AvgPrice: "0.00",
908+
OrigType: "LIMIT",
909+
PositionSide: "BOTH",
910+
PriceProtect: false,
911+
ClosePosition: false,
912+
PriceMatch: "NONE",
913+
SelfTradePreventionMode: "NONE",
914+
GoodTillDate: 0,
915+
}
916+
s.assertOrderEqual(e, res.Orders[0])
917+
918+
r.Equal(
919+
[]error{
920+
nil,
921+
&common.APIError{
922+
Code: -1102,
923+
Message: "Mandatory parameter 'price' was not sent, was empty/null, or malformed.",
924+
Response: nil,
925+
},
926+
},
927+
res.Errors)
928+
929+
}

0 commit comments

Comments
 (0)