Skip to content

Commit 7c57681

Browse files
authored
Add modify batch orders feature #647 (#648)
* 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 * Add MarketOrder instead using Service Added MarketOrder structure for ModifyBatchOrderService. Also modified tests for these changes.
1 parent f635eb8 commit 7c57681

File tree

3 files changed

+278
-0
lines changed

3 files changed

+278
-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

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

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

v2/futures/order_service_test.go

+102
Original file line numberDiff line numberDiff line change
@@ -823,3 +823,105 @@ 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 := []*ModifyOrder{
864+
new(ModifyOrder).
865+
Symbol("BTCUSDT").
866+
OrigClientOrderID("Ne7DEEvLvv8b8egTqrZceu").
867+
Quantity("1").
868+
Side("BUY").
869+
Price("99995.00"),
870+
new(ModifyOrder).
871+
Symbol("BTCUSDT").
872+
OrigClientOrderID("GYby67jLvv8b8egTr8Adrf").
873+
Quantity("1").
874+
Side("SELL").
875+
Price("-100005.00"),
876+
}
877+
878+
res, err := s.client.NewModifyBatchOrdersService().OrderList(orders).Do(context.Background())
879+
880+
r := s.r()
881+
r.NoError(err)
882+
883+
r.Equal(1, len(res.Orders))
884+
885+
e := &Order{
886+
Symbol: "BTCUSDT",
887+
OrderID: 42042723,
888+
ClientOrderID: "Ne7DEEvLvv8b8egTqrZceu",
889+
Price: "99995.00",
890+
ReduceOnly: false,
891+
OrigQuantity: "1",
892+
ExecutedQuantity: "0",
893+
CumQuantity: "0",
894+
CumQuote: "0.00",
895+
Status: "NEW",
896+
TimeInForce: "GTC",
897+
Type: "LIMIT",
898+
Side: "BUY",
899+
StopPrice: "0.00",
900+
Time: 0,
901+
UpdateTime: 1733500988978,
902+
WorkingType: "CONTRACT_PRICE",
903+
ActivatePrice: "",
904+
PriceRate: "",
905+
AvgPrice: "0.00",
906+
OrigType: "LIMIT",
907+
PositionSide: "BOTH",
908+
PriceProtect: false,
909+
ClosePosition: false,
910+
PriceMatch: "NONE",
911+
SelfTradePreventionMode: "NONE",
912+
GoodTillDate: 0,
913+
}
914+
s.assertOrderEqual(e, res.Orders[0])
915+
916+
r.Equal(
917+
[]error{
918+
nil,
919+
&common.APIError{
920+
Code: -1102,
921+
Message: "Mandatory parameter 'price' was not sent, was empty/null, or malformed.",
922+
Response: nil,
923+
},
924+
},
925+
res.Errors)
926+
927+
}

0 commit comments

Comments
 (0)