mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 08:45:16 +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)
|
assert.Equal(t, len(ordersResp.List), 1)
|
||||||
t.Logf("apiResp: %+v", ordersResp.List[0])
|
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"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"go.uber.org/multierr"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/exchange/bybit/bybitapi"
|
"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) {
|
func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||||
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||||
log.WithError(err).Errorf("markets rate limiter wait error")
|
return nil, fmt.Errorf("markets rate limiter wait error: %v", err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instruments, err := e.client.NewGetInstrumentsInfoRequest().Do(ctx)
|
instruments, err := e.client.NewGetInstrumentsInfoRequest().Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to query instruments, err: %v", err)
|
return nil, fmt.Errorf("failed to get instruments, err: %v", err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
marketMap := types.MarketMap{}
|
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) {
|
func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticker, error) {
|
||||||
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||||
log.WithError(err).Errorf("ticker rate limiter wait error")
|
return nil, fmt.Errorf("ticker order rate limiter wait error: %v", err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := e.client.NewGetTickersRequest().Symbol(symbol).DoWithResponseTime(ctx)
|
s, err := e.client.NewGetTickersRequest().Symbol(symbol).DoWithResponseTime(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to get tickers, symbol: %s, err: %v", symbol, err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s.List) != 1 {
|
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))
|
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 {
|
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||||
log.WithError(err).Errorf("ticker rate limiter wait error")
|
return nil, fmt.Errorf("tickers rate limiter wait error: %v", err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
allTickers, err := e.client.NewGetTickersRequest().DoWithResponseTime(ctx)
|
allTickers, err := e.client.NewGetTickersRequest().DoWithResponseTime(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to get tickers, err: %v", err)
|
|
||||||
return nil, 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 {
|
if err = tradeRateLimiter.Wait(ctx); err != nil {
|
||||||
log.WithError(err).Errorf("trade rate limiter wait error")
|
return nil, fmt.Errorf("place order rate limiter wait error: %v", err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
res, err := req.Do(ctx)
|
res, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to get open order, cursor: %s, err: %v", cursor, err)
|
return nil, fmt.Errorf("failed to query open orders, err: %v", err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, order := range res.List {
|
for _, order := range res.List {
|
||||||
order, err := toGlobalOrder(order)
|
order, err := toGlobalOrder(order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to convert order, err: %v", err)
|
return nil, fmt.Errorf("failed to convert order, err: %v", err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
orders = append(orders, *order)
|
orders = append(orders, *order)
|
||||||
|
@ -223,13 +214,11 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*t
|
||||||
req.OrderLinkId(order.ClientOrderID)
|
req.OrderLinkId(order.ClientOrderID)
|
||||||
|
|
||||||
if err := orderRateLimiter.Wait(ctx); err != nil {
|
if err := orderRateLimiter.Wait(ctx); err != nil {
|
||||||
log.WithError(err).Errorf("place order rate limiter wait error")
|
return nil, fmt.Errorf("place order rate limiter wait error: %v", err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
res, err := req.Do(ctx)
|
res, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to place order, order: %#v, err: %v", order, err)
|
return nil, fmt.Errorf("failed to place order, order: %#v, err: %v", order, err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(res.OrderId) == 0 || res.OrderLinkId != order.ClientOrderID {
|
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])
|
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