mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
pkg/exchange: support cancel order
This commit is contained in:
parent
fab1107515
commit
5105046053
|
@ -97,4 +97,36 @@ func TestClient(t *testing.T) {
|
|||
assert.Equal(t, len(ordersResp.List), 1)
|
||||
t.Logf("apiResp: %+v", ordersResp.List[0])
|
||||
})
|
||||
|
||||
t.Run("PostCancelOrderRequest", func(t *testing.T) {
|
||||
req := client.NewPlaceOrderRequest().
|
||||
Symbol("DOTUSDT").
|
||||
Side(SideBuy).
|
||||
OrderType(OrderTypeLimit).
|
||||
Qty("1").
|
||||
Price("4.6").
|
||||
OrderLinkId(uuid.NewString()).
|
||||
TimeInForce(TimeInForceGTC)
|
||||
apiResp, err := req.Do(ctx)
|
||||
assert.NoError(t, err)
|
||||
t.Logf("apiResp: %+v", apiResp)
|
||||
|
||||
ordersResp, err := client.NewGetOpenOrderRequest().OrderLinkId(apiResp.OrderLinkId).Do(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(ordersResp.List), 1)
|
||||
t.Logf("apiResp: %+v", ordersResp.List[0])
|
||||
|
||||
cancelReq := client.NewCancelOrderRequest().
|
||||
Symbol("DOTUSDT").
|
||||
OrderLinkId(apiResp.OrderLinkId)
|
||||
cancelResp, err := cancelReq.Do(ctx)
|
||||
assert.NoError(t, err)
|
||||
t.Logf("apiResp: %+v", cancelResp)
|
||||
|
||||
ordersResp, err = client.NewGetOpenOrderRequest().OrderLinkId(apiResp.OrderLinkId).Do(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(ordersResp.List), 1)
|
||||
assert.Equal(t, ordersResp.List[0].OrderStatus, OrderStatusCancelled)
|
||||
t.Logf("apiResp: %+v", ordersResp.List[0])
|
||||
})
|
||||
}
|
||||
|
|
35
pkg/exchange/bybit/bybitapi/post_cancel_order_request.go
Normal file
35
pkg/exchange/bybit/bybitapi/post_cancel_order_request.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package bybitapi
|
||||
|
||||
import (
|
||||
"github.com/c9s/requestgen"
|
||||
)
|
||||
|
||||
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Result
|
||||
|
||||
type CancelOrderResponse struct {
|
||||
OrderId string `json:"orderId"`
|
||||
OrderLinkId string `json:"orderLinkId"`
|
||||
}
|
||||
|
||||
//go:generate PostRequest -url "/v5/order/cancel" -type PostCancelOrderRequest -responseDataType .CancelOrderResponse
|
||||
type PostCancelOrderRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
|
||||
category Category `param:"category" validValues:"spot"`
|
||||
symbol string `param:"symbol"`
|
||||
// User customised order ID. Either orderId or orderLinkId is required
|
||||
orderLinkId string `param:"orderLinkId"`
|
||||
|
||||
orderId *string `param:"orderLinkId"`
|
||||
// orderFilter default type is Order
|
||||
// tpsl order type are not currently supported
|
||||
orderFilter *string `param:"timeInForce" validValues:"Order"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewCancelOrderRequest() *PostCancelOrderRequest {
|
||||
return &PostCancelOrderRequest{
|
||||
client: c,
|
||||
category: CategorySpot,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Result -url /v5/order/cancel -type PostCancelOrderRequest -responseDataType .CancelOrderResponse"; DO NOT EDIT.
|
||||
|
||||
package bybitapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (p *PostCancelOrderRequest) Category(category Category) *PostCancelOrderRequest {
|
||||
p.category = category
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostCancelOrderRequest) Symbol(symbol string) *PostCancelOrderRequest {
|
||||
p.symbol = symbol
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostCancelOrderRequest) OrderLinkId(orderLinkId string) *PostCancelOrderRequest {
|
||||
p.orderLinkId = orderLinkId
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostCancelOrderRequest) OrderId(orderId string) *PostCancelOrderRequest {
|
||||
p.orderId = &orderId
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostCancelOrderRequest) OrderFilter(orderFilter string) *PostCancelOrderRequest {
|
||||
p.orderFilter = &orderFilter
|
||||
return p
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (p *PostCancelOrderRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for _k, _v := range params {
|
||||
query.Add(_k, fmt.Sprintf("%v", _v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (p *PostCancelOrderRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check category field -> json key category
|
||||
category := p.category
|
||||
|
||||
// TEMPLATE check-valid-values
|
||||
switch category {
|
||||
case "spot":
|
||||
params["category"] = category
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("category value %v is invalid", category)
|
||||
|
||||
}
|
||||
// END TEMPLATE check-valid-values
|
||||
|
||||
// assign parameter of category
|
||||
params["category"] = category
|
||||
// check symbol field -> json key symbol
|
||||
symbol := p.symbol
|
||||
|
||||
// assign parameter of symbol
|
||||
params["symbol"] = symbol
|
||||
// check orderLinkId field -> json key orderLinkId
|
||||
orderLinkId := p.orderLinkId
|
||||
|
||||
// assign parameter of orderLinkId
|
||||
params["orderLinkId"] = orderLinkId
|
||||
// check orderId field -> json key orderLinkId
|
||||
if p.orderId != nil {
|
||||
orderId := *p.orderId
|
||||
|
||||
// assign parameter of orderId
|
||||
params["orderLinkId"] = orderId
|
||||
} else {
|
||||
}
|
||||
// check orderFilter field -> json key timeInForce
|
||||
if p.orderFilter != nil {
|
||||
orderFilter := *p.orderFilter
|
||||
|
||||
// TEMPLATE check-valid-values
|
||||
switch orderFilter {
|
||||
case "Order":
|
||||
params["timeInForce"] = orderFilter
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("timeInForce value %v is invalid", orderFilter)
|
||||
|
||||
}
|
||||
// END TEMPLATE check-valid-values
|
||||
|
||||
// assign parameter of orderFilter
|
||||
params["timeInForce"] = orderFilter
|
||||
} else {
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (p *PostCancelOrderRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := p.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for _k, _v := range params {
|
||||
if p.isVarSlice(_v) {
|
||||
p.iterateSlice(_v, func(it interface{}) {
|
||||
query.Add(_k+"[]", fmt.Sprintf("%v", it))
|
||||
})
|
||||
} else {
|
||||
query.Add(_k, fmt.Sprintf("%v", _v))
|
||||
}
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (p *PostCancelOrderRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := p.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (p *PostCancelOrderRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (p *PostCancelOrderRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for _k, _v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + _k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, _v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (p *PostCancelOrderRequest) iterateSlice(slice interface{}, _f func(it interface{})) {
|
||||
sliceValue := reflect.ValueOf(slice)
|
||||
for _i := 0; _i < sliceValue.Len(); _i++ {
|
||||
it := sliceValue.Index(_i).Interface()
|
||||
_f(it)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PostCancelOrderRequest) isVarSlice(_v interface{}) bool {
|
||||
rt := reflect.TypeOf(_v)
|
||||
switch rt.Kind() {
|
||||
case reflect.Slice:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PostCancelOrderRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := p.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for _k, _v := range params {
|
||||
slugs[_k] = fmt.Sprintf("%v", _v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (p *PostCancelOrderRequest) Do(ctx context.Context) (*CancelOrderResponse, error) {
|
||||
|
||||
params, err := p.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/v5/order/cancel"
|
||||
|
||||
req, err := p.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := p.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data CancelOrderResponse
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &data, nil
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.uber.org/multierr"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange/bybit/bybitapi"
|
||||
|
@ -66,14 +67,12 @@ func (e *Exchange) PlatformFeeCurrency() string {
|
|||
|
||||
func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||
log.WithError(err).Errorf("markets rate limiter wait error")
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("markets rate limiter wait error: %v", err)
|
||||
}
|
||||
|
||||
instruments, err := e.client.NewGetInstrumentsInfoRequest().Do(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("failed to query instruments, err: %v", err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to get instruments, err: %v", err)
|
||||
}
|
||||
|
||||
marketMap := types.MarketMap{}
|
||||
|
@ -86,18 +85,15 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
|||
|
||||
func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticker, error) {
|
||||
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||
log.WithError(err).Errorf("ticker rate limiter wait error")
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("ticker order rate limiter wait error: %v", err)
|
||||
}
|
||||
|
||||
s, err := e.client.NewGetTickersRequest().Symbol(symbol).DoWithResponseTime(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("failed to get tickers, symbol: %s, err: %v", symbol, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(s.List) != 1 {
|
||||
log.Warnf("unexpected ticker length, exp: 1, got: %d", len(s.List))
|
||||
return nil, fmt.Errorf("unexpected ticker lenght, exp:1, got:%d", len(s.List))
|
||||
}
|
||||
|
||||
|
@ -121,12 +117,10 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbols ...string) (map[str
|
|||
}
|
||||
|
||||
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||
log.WithError(err).Errorf("ticker rate limiter wait error")
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("tickers rate limiter wait error: %v", err)
|
||||
}
|
||||
allTickers, err := e.client.NewGetTickersRequest().DoWithResponseTime(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("failed to get tickers, err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -147,20 +141,17 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
|
|||
}
|
||||
|
||||
if err = tradeRateLimiter.Wait(ctx); err != nil {
|
||||
log.WithError(err).Errorf("trade rate limiter wait error")
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("place order rate limiter wait error: %v", err)
|
||||
}
|
||||
res, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("failed to get open order, cursor: %s, err: %v", cursor, err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to query open orders, err: %v", err)
|
||||
}
|
||||
|
||||
for _, order := range res.List {
|
||||
order, err := toGlobalOrder(order)
|
||||
if err != nil {
|
||||
log.Warnf("failed to convert order, err: %v", err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to convert order, err: %v", err)
|
||||
}
|
||||
|
||||
orders = append(orders, *order)
|
||||
|
@ -223,13 +214,11 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*t
|
|||
req.OrderLinkId(order.ClientOrderID)
|
||||
|
||||
if err := orderRateLimiter.Wait(ctx); err != nil {
|
||||
log.WithError(err).Errorf("place order rate limiter wait error")
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("place order rate limiter wait error: %v", err)
|
||||
}
|
||||
res, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("failed to place order, order: %#v, err: %v", order, err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to place order, order: %#v, err: %v", order, err)
|
||||
}
|
||||
|
||||
if len(res.OrderId) == 0 || res.OrderLinkId != order.ClientOrderID {
|
||||
|
@ -247,3 +236,44 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*t
|
|||
|
||||
return toGlobalOrder(ordersResp.List[0])
|
||||
}
|
||||
|
||||
func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (errs error) {
|
||||
if len(orders) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, order := range orders {
|
||||
req := e.client.NewCancelOrderRequest()
|
||||
|
||||
switch {
|
||||
case len(order.ClientOrderID) != 0:
|
||||
req.OrderLinkId(order.ClientOrderID)
|
||||
case len(order.UUID) != 0 && order.OrderID != 0:
|
||||
req.OrderId(order.UUID)
|
||||
default:
|
||||
errs = multierr.Append(
|
||||
errs,
|
||||
fmt.Errorf("the order uuid and client order id are empty, order: %#v", order),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
req.Symbol(order.Market.Symbol)
|
||||
|
||||
if err := orderRateLimiter.Wait(ctx); err != nil {
|
||||
errs = multierr.Append(errs, fmt.Errorf("cancel order rate limiter wait, order id: %s, error: %v", order.ClientOrderID, err))
|
||||
continue
|
||||
}
|
||||
res, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
errs = multierr.Append(errs, fmt.Errorf("failed to cancel order id: %s, err: %v", order.ClientOrderID, err))
|
||||
continue
|
||||
}
|
||||
if res.OrderId != order.UUID || res.OrderLinkId != order.ClientOrderID {
|
||||
errs = multierr.Append(errs, fmt.Errorf("unexpected order id, resp: %#v, order: %#v", res, order))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user