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

feat: v5 cancel all orders #113

Merged
merged 4 commits into from
Mar 30, 2023
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ The following API endpoints have been implemented
- [`/v5/order/amend` Amend Order](https://bybit-exchange.github.io/docs/v5/order/amend-order)
- [`/v5/order/cancel` Cancel Order](https://bybit-exchange.github.io/docs/v5/order/cancel-order)
- [`/v5/order/realtime` Get Open Orders](https://bybit-exchange.github.io/docs/v5/order/open-order)
- [`/v5/order/cancel-all` Cancel All Orders](https://bybit-exchange.github.io/docs/v5/order/cancel-all)

#### Account

Expand Down
28 changes: 28 additions & 0 deletions integrationtest/v5/order/order_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,31 @@ func TestGetOpenOrders(t *testing.T) {
require.NoError(t, err)
}
}

func TestCancelAllOrders(t *testing.T) {
client := bybit.NewTestClient().WithAuthFromEnv()
category := bybit.CategoryV5Spot
symbol := bybit.SymbolV5BTCUSDT
{
price := "10000.0"
_, err := client.V5().Order().CreateOrder(bybit.V5CreateOrderParam{
Category: category,
Symbol: symbol,
Side: bybit.SideBuy,
OrderType: bybit.OrderTypeLimit,
Qty: "0.01",
Price: &price,
})
require.NoError(t, err)
}

res, err := client.V5().Order().CancelAllOrders(bybit.V5CancelAllOrdersParam{
Category: category,
})
require.NoError(t, err)
{
goldenFilename := "./testdata/v5-cancel-all-orders.json"
testhelper.Compare(t, goldenFilename, testhelper.ConvertToJSON(res.Result))
testhelper.UpdateFile(t, goldenFilename, testhelper.ConvertToJSON(res.Result))
}
}
11 changes: 11 additions & 0 deletions integrationtest/v5/order/testdata/v5-cancel-all-orders.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"LinearInverseOption": {
"list": {
"orderId": "",
"orderLinkId": ""
}
},
"Spot": {
"success": "1"
}
}
80 changes: 80 additions & 0 deletions v5_order_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type V5OrderServiceI interface {
AmendOrder(V5AmendOrderParam) (*V5AmendOrderResponse, error)
CancelOrder(V5CancelOrderParam) (*V5CancelOrderResponse, error)
GetOpenOrders(V5GetOpenOrdersParam) (*V5GetOpenOrdersResponse, error)
CancelAllOrders(V5CancelAllOrdersParam) (*V5CancelAllOrdersResponse, error)
}

// V5OrderService :
Expand Down Expand Up @@ -260,3 +261,82 @@ func (s *V5OrderService) GetOpenOrders(param V5GetOpenOrdersParam) (*V5GetOpenOr

return &res, nil
}

// V5CancelAllOrdersParam :
// If you pass multiple of these params, the system will process one of param, which priority is symbol > baseCoin > settleCoin.
type V5CancelAllOrdersParam struct {
Category CategoryV5 `json:"category"`

Symbol *SymbolV5 `json:"symbol,omitempty"`
BaseCoin *Coin `json:"baseCoin,omitempty"`
SettleCoin *Coin `json:"settleCoin,omitempty"`
OrderFilter *OrderFilter `json:"orderFilter,omitempty"` // If not passed, Order by default
}

func (p V5CancelAllOrdersParam) validate() error {
if p.Category == CategoryV5Linear || p.Category == CategoryV5Inverse {
if p.Symbol == nil && p.BaseCoin == nil && p.SettleCoin == nil {
return fmt.Errorf("symbol or baseCoin or settleCoin is needed for linear and inverse")
}
}
if p.Category != CategoryV5Spot && p.OrderFilter != nil {
return fmt.Errorf("orderFilter is for spot only")
}
return nil
}

// V5CancelAllOrdersResponse :
type V5CancelAllOrdersResponse struct {
CommonV5Response `json:",inline"`
Result V5CancelAllOrdersResult `json:"result"`
}

// V5CancelAllOrdersResult :
type V5CancelAllOrdersResult struct {
LinearInverseOption *V5CancelAllOrdersLinearInverseOptionResult
Spot *V5CancelAllOrdersSpotResult
}

// UnmarshalJSON :
func (r *V5CancelAllOrdersResult) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &r.LinearInverseOption); err != nil {
return err
}
if err := json.Unmarshal(data, &r.Spot); err != nil {
return err
}
return nil
}

// V5CancelAllOrdersLinearInverseOptionResult :
type V5CancelAllOrdersLinearInverseOptionResult struct {
List []struct {
OrderID string `json:"orderId"`
OrderLinkID string `json:"orderLinkId"`
} `json:"list"`
}

// V5CancelAllOrdersSpotResult :
type V5CancelAllOrdersSpotResult struct {
Success string `json:"success"` // 1: success, 0: fail
}

// CancelAllOrders :
func (s *V5OrderService) CancelAllOrders(param V5CancelAllOrdersParam) (*V5CancelAllOrdersResponse, error) {
var res V5CancelAllOrdersResponse

if err := param.validate(); err != nil {
return nil, fmt.Errorf("validate param: %w", err)
}

body, err := json.Marshal(param)
if err != nil {
return &res, fmt.Errorf("json marshal: %w", err)
}

if err := s.client.postV5JSON("/v5/order/cancel-all", body, &res); err != nil {
return &res, err
}

return &res, nil
}
103 changes: 103 additions & 0 deletions v5_order_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,106 @@ func TestV5Order_GetOpenOrders(t *testing.T) {
assert.Error(t, err)
})
}

func TestV5Order_CancelAllOrders(t *testing.T) {
t.Run("success", func(t *testing.T) {
t.Run("linear", func(t *testing.T) {
symbol := SymbolV5BTCUSDT
param := V5CancelAllOrdersParam{
Category: CategoryV5Linear,
Symbol: &symbol,
}

path := "/v5/order/cancel-all"
method := http.MethodPost
status := http.StatusOK
respBody := map[string]interface{}{
"result": map[string]interface{}{
"list": []map[string]interface{}{
{
"orderId": "f6a73e1f-39b5-4dee-af21-1460b2e3b27c",
"orderLinkId": "a001",
},
},
},
}
bytesBody, err := json.Marshal(respBody)
require.NoError(t, err)

server, teardown := testhelper.NewServer(
testhelper.WithHandlerOption(path, method, status, bytesBody),
)
defer teardown()

client := NewTestClient().
WithBaseURL(server.URL).
WithAuth("test", "test")

resp, err := client.V5().Order().CancelAllOrders(param)
require.NoError(t, err)

require.NotNil(t, resp)
testhelper.Compare(t, respBody["result"], resp.Result.LinearInverseOption)
})
t.Run("spot", func(t *testing.T) {
param := V5CancelAllOrdersParam{
Category: CategoryV5Spot,
}

path := "/v5/order/cancel-all"
method := http.MethodPost
status := http.StatusOK
respBody := map[string]interface{}{
"result": map[string]interface{}{
"success": "1",
},
}
bytesBody, err := json.Marshal(respBody)
require.NoError(t, err)

server, teardown := testhelper.NewServer(
testhelper.WithHandlerOption(path, method, status, bytesBody),
)
defer teardown()

client := NewTestClient().
WithBaseURL(server.URL).
WithAuth("test", "test")

resp, err := client.V5().Order().CancelAllOrders(param)
require.NoError(t, err)

require.NotNil(t, resp)
testhelper.Compare(t, respBody["result"], resp.Result.Spot)
})
})
t.Run("authentication required", func(t *testing.T) {
symbol := SymbolV5BTCUSDT
param := V5CancelAllOrdersParam{
Category: CategoryV5Spot,
Symbol: &symbol,
}

path := "/v5/order/cancel-all"
method := http.MethodPost
status := http.StatusOK
respBody := map[string]interface{}{
"result": map[string]interface{}{
"success": "1",
},
}
bytesBody, err := json.Marshal(respBody)
require.NoError(t, err)

server, teardown := testhelper.NewServer(
testhelper.WithHandlerOption(path, method, status, bytesBody),
)
defer teardown()

client := NewTestClient().
WithBaseURL(server.URL)

_, err = client.V5().Order().CancelAllOrders(param)
assert.Error(t, err)
})
}