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 1 commit
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
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)

}
Loading