From 373242d3064413464745c6ff91ba5173fff239f0 Mon Sep 17 00:00:00 2001 From: Edwin Date: Wed, 10 Jan 2024 16:39:52 +0800 Subject: [PATCH] pkg/exchange: generate cancel order by requestgen --- pkg/exchange/okex/exchange.go | 18 +- .../okex/okexapi/cancel_order_request.go | 21 ++ .../okexapi/cancel_order_request_accessors.go | 76 ------- .../cancel_order_request_requestgen.go | 195 ++++++++++++++++++ pkg/exchange/okex/okexapi/client_test.go | 62 ++++++ pkg/exchange/okex/okexapi/trade.go | 57 +---- 6 files changed, 295 insertions(+), 134 deletions(-) create mode 100644 pkg/exchange/okex/okexapi/cancel_order_request.go delete mode 100644 pkg/exchange/okex/okexapi/cancel_order_request_accessors.go create mode 100644 pkg/exchange/okex/okexapi/cancel_order_request_requestgen.go diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index d3ca2e9397..0dcb7955c2 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -24,11 +24,12 @@ var ( marketDataLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 5) orderRateLimiter = rate.NewLimiter(rate.Every(300*time.Millisecond), 5) - queryMarketLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 10) - queryTickerLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 10) - queryTickersLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 10) - queryAccountLimiter = rate.NewLimiter(rate.Every(200*time.Millisecond), 5) - placeOrderLimiter = rate.NewLimiter(rate.Every(30*time.Millisecond), 30) + queryMarketLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 10) + queryTickerLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 10) + queryTickersLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 10) + queryAccountLimiter = rate.NewLimiter(rate.Every(200*time.Millisecond), 5) + placeOrderLimiter = rate.NewLimiter(rate.Every(30*time.Millisecond), 30) + batchCancelOrderLimiter = rate.NewLimiter(rate.Every(5*time.Millisecond), 200) ) const ID = "okex" @@ -321,11 +322,18 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) erro req.InstrumentID(toLocalSymbol(order.Symbol)) req.OrderID(strconv.FormatUint(order.OrderID, 10)) if len(order.ClientOrderID) > 0 { + _, err := strconv.ParseInt(order.ClientOrderID, 10, 64) + if err != nil { + return fmt.Errorf("client order id should be numberic: %s, err: %w", order.ClientOrderID, err) + } req.ClientOrderID(order.ClientOrderID) } reqs = append(reqs, req) } + if err := batchCancelOrderLimiter.Wait(ctx); err != nil { + return fmt.Errorf("batch cancel order rate limiter wait error: %w", err) + } batchReq := e.client.NewBatchCancelOrderRequest() batchReq.Add(reqs...) _, err := batchReq.Do(ctx) diff --git a/pkg/exchange/okex/okexapi/cancel_order_request.go b/pkg/exchange/okex/okexapi/cancel_order_request.go new file mode 100644 index 0000000000..3b5cd40175 --- /dev/null +++ b/pkg/exchange/okex/okexapi/cancel_order_request.go @@ -0,0 +1,21 @@ +package okexapi + +import "github.com/c9s/requestgen" + +//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data +//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data + +//go:generate PostRequest -url "/api/v5/trade/cancel-order" -type CancelOrderRequest -responseDataType []OrderResponse +type CancelOrderRequest struct { + client requestgen.AuthenticatedAPIClient + + instrumentID string `param:"instId"` + orderID *string `param:"ordId"` + clientOrderID *string `param:"clOrdId"` +} + +func (c *RestClient) NewCancelOrderRequest() *CancelOrderRequest { + return &CancelOrderRequest{ + client: c, + } +} diff --git a/pkg/exchange/okex/okexapi/cancel_order_request_accessors.go b/pkg/exchange/okex/okexapi/cancel_order_request_accessors.go deleted file mode 100644 index aaaf3060ba..0000000000 --- a/pkg/exchange/okex/okexapi/cancel_order_request_accessors.go +++ /dev/null @@ -1,76 +0,0 @@ -// Code generated by "requestgen -type CancelOrderRequest"; DO NOT EDIT. - -package okexapi - -import ( - "encoding/json" - "fmt" - "net/url" -) - -func (c *CancelOrderRequest) InstrumentID(instrumentID string) *CancelOrderRequest { - c.instrumentID = instrumentID - return c -} - -func (c *CancelOrderRequest) OrderID(orderID string) *CancelOrderRequest { - c.orderID = &orderID - return c -} - -func (c *CancelOrderRequest) ClientOrderID(clientOrderID string) *CancelOrderRequest { - c.clientOrderID = &clientOrderID - return c -} - -func (c *CancelOrderRequest) GetParameters() (map[string]interface{}, error) { - var params = map[string]interface{}{} - - // check instrumentID field -> json key instId - instrumentID := c.instrumentID - - // assign parameter of instrumentID - params["instId"] = instrumentID - - // check orderID field -> json key ordId - if c.orderID != nil { - orderID := *c.orderID - - // assign parameter of orderID - params["ordId"] = orderID - } - - // check clientOrderID field -> json key clOrdId - if c.clientOrderID != nil { - clientOrderID := *c.clientOrderID - - // assign parameter of clientOrderID - params["clOrdId"] = clientOrderID - } - - return params, nil -} - -func (c *CancelOrderRequest) GetParametersQuery() (url.Values, error) { - query := url.Values{} - - params, err := c.GetParameters() - if err != nil { - return query, err - } - - for k, v := range params { - query.Add(k, fmt.Sprintf("%v", v)) - } - - return query, nil -} - -func (c *CancelOrderRequest) GetParametersJSON() ([]byte, error) { - params, err := c.GetParameters() - if err != nil { - return nil, err - } - - return json.Marshal(params) -} diff --git a/pkg/exchange/okex/okexapi/cancel_order_request_requestgen.go b/pkg/exchange/okex/okexapi/cancel_order_request_requestgen.go new file mode 100644 index 0000000000..ad550a6e23 --- /dev/null +++ b/pkg/exchange/okex/okexapi/cancel_order_request_requestgen.go @@ -0,0 +1,195 @@ +// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Data -url /api/v5/trade/cancel-order -type CancelOrderRequest -responseDataType []OrderResponse"; DO NOT EDIT. + +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (c *CancelOrderRequest) InstrumentID(instrumentID string) *CancelOrderRequest { + c.instrumentID = instrumentID + return c +} + +func (c *CancelOrderRequest) OrderID(orderID string) *CancelOrderRequest { + c.orderID = &orderID + return c +} + +func (c *CancelOrderRequest) ClientOrderID(clientOrderID string) *CancelOrderRequest { + c.clientOrderID = &clientOrderID + return c +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (c *CancelOrderRequest) 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 (c *CancelOrderRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check instrumentID field -> json key instId + instrumentID := c.instrumentID + + // assign parameter of instrumentID + params["instId"] = instrumentID + // check orderID field -> json key ordId + if c.orderID != nil { + orderID := *c.orderID + + // assign parameter of orderID + params["ordId"] = orderID + } else { + } + // check clientOrderID field -> json key clOrdId + if c.clientOrderID != nil { + clientOrderID := *c.clientOrderID + + // assign parameter of clientOrderID + params["clOrdId"] = clientOrderID + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (c *CancelOrderRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := c.GetParameters() + if err != nil { + return query, err + } + + for _k, _v := range params { + if c.isVarSlice(_v) { + c.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 (c *CancelOrderRequest) GetParametersJSON() ([]byte, error) { + params, err := c.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 (c *CancelOrderRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (c *CancelOrderRequest) 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 (c *CancelOrderRequest) 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 (c *CancelOrderRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (c *CancelOrderRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := c.GetSlugParameters() + if err != nil { + return slugs, nil + } + + for _k, _v := range params { + slugs[_k] = fmt.Sprintf("%v", _v) + } + + return slugs, nil +} + +// GetPath returns the request path of the API +func (c *CancelOrderRequest) GetPath() string { + return "/api/v5/trade/cancel-order" +} + +// Do generates the request object and send the request object to the API endpoint +func (c *CancelOrderRequest) Do(ctx context.Context) ([]OrderResponse, error) { + + params, err := c.GetParameters() + if err != nil { + return nil, err + } + query := url.Values{} + + var apiURL string + + apiURL = c.GetPath() + + req, err := c.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := c.client.SendRequest(req) + if err != nil { + return nil, err + } + + var apiResponse APIResponse + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + + type responseValidator interface { + Validate() error + } + validator, ok := interface{}(apiResponse).(responseValidator) + if ok { + if err := validator.Validate(); err != nil { + return nil, err + } + } + var data []OrderResponse + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/exchange/okex/okexapi/client_test.go b/pkg/exchange/okex/okexapi/client_test.go index 91fe26395f..bcc0150272 100644 --- a/pkg/exchange/okex/okexapi/client_test.go +++ b/pkg/exchange/okex/okexapi/client_test.go @@ -2,10 +2,12 @@ package okexapi import ( "context" + "fmt" "os" "strconv" "testing" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/c9s/bbgo/pkg/testutil" @@ -108,6 +110,66 @@ func TestClient_PlaceOrderRequest(t *testing.T) { t.Log(res) } +func TestClient_CancelOrderRequest(t *testing.T) { + client := getTestClientOrSkip(t) + ctx := context.Background() + req := client.NewPlaceOrderRequest() + clientId := fmt.Sprintf("%d", uuid.New().ID()) + + order, err := req. + InstrumentID("BTC-USDT"). + TradeMode(TradeModeCash). + Side(SideTypeSell). + OrderType(OrderTypeLimit). + TargetCurrency(TargetCurrencyBase). + ClientOrderID(clientId). + Price("48000"). + Size("0.001"). + Do(ctx) + assert.NoError(t, err) + assert.NotEmpty(t, order) + t.Logf("place order: %+v", order) + + c := client.NewGetOrderDetailsRequest().ClientOrderID(clientId).InstrumentID("BTC-USDT") + res, err := c.Do(ctx) + assert.NoError(t, err) + t.Log(res) + + cancelResp, err := client.NewCancelOrderRequest().ClientOrderID(clientId).InstrumentID("BTC-USDT").Do(ctx) + assert.NoError(t, err) + t.Log(cancelResp) +} + +func TestClient_BatchCancelOrderRequest(t *testing.T) { + client := getTestClientOrSkip(t) + ctx := context.Background() + req := client.NewPlaceOrderRequest() + clientId := fmt.Sprintf("%d", uuid.New().ID()) + + order, err := req. + InstrumentID("BTC-USDT"). + TradeMode(TradeModeCash). + Side(SideTypeSell). + OrderType(OrderTypeLimit). + TargetCurrency(TargetCurrencyBase). + ClientOrderID(clientId). + Price("48000"). + Size("0.001"). + Do(ctx) + assert.NoError(t, err) + assert.NotEmpty(t, order) + t.Logf("place order: %+v", order) + + c := client.NewGetOrderDetailsRequest().ClientOrderID(clientId).InstrumentID("BTC-USDT") + res, err := c.Do(ctx) + assert.NoError(t, err) + t.Log(res) + + cancelResp, err := client.NewBatchCancelOrderRequest().Add(&CancelOrderRequest{instrumentID: "BTC-USDT", clientOrderID: &clientId}).Do(ctx) + assert.NoError(t, err) + t.Log(cancelResp) +} + func TestClient_GetPendingOrderRequest(t *testing.T) { client := getTestClientOrSkip(t) ctx := context.Background() diff --git a/pkg/exchange/okex/okexapi/trade.go b/pkg/exchange/okex/okexapi/trade.go index 09f03e360f..19d5c497f0 100644 --- a/pkg/exchange/okex/okexapi/trade.go +++ b/pkg/exchange/okex/okexapi/trade.go @@ -17,12 +17,6 @@ func (c *RestClient) NewBatchPlaceOrderRequest() *BatchPlaceOrderRequest { } } -func (c *RestClient) NewCancelOrderRequest() *CancelOrderRequest { - return &CancelOrderRequest{ - client: c, - } -} - func (c *RestClient) NewBatchCancelOrderRequest() *BatchCancelOrderRequest { return &BatchCancelOrderRequest{ client: c, @@ -52,52 +46,6 @@ func (r *PlaceOrderRequest) Parameters() map[string]interface{} { return params } -//go:generate requestgen -type CancelOrderRequest -type CancelOrderRequest struct { - client *RestClient - - instrumentID string `param:"instId"` - orderID *string `param:"ordId"` - clientOrderID *string `param:"clOrdId"` -} - -func (r *CancelOrderRequest) Parameters() map[string]interface{} { - payload, _ := r.GetParameters() - return payload -} - -func (r *CancelOrderRequest) Do(ctx context.Context) ([]OrderResponse, error) { - payload, err := r.GetParameters() - if err != nil { - return nil, err - } - - if r.clientOrderID == nil && r.orderID != nil { - return nil, errors.New("either orderID or clientOrderID is required for canceling order") - } - - req, err := r.client.NewAuthenticatedRequest(ctx, "POST", "/api/v5/trade/cancel-order", nil, payload) - if err != nil { - return nil, err - } - - response, err := r.client.SendRequest(req) - if err != nil { - return nil, err - } - - var apiResponse APIResponse - if err := response.DecodeJSON(&apiResponse); err != nil { - return nil, err - } - var data []OrderResponse - if err := json.Unmarshal(apiResponse.Data, &data); err != nil { - return nil, err - } - - return data, nil -} - type BatchCancelOrderRequest struct { client *RestClient @@ -113,7 +61,10 @@ func (r *BatchCancelOrderRequest) Do(ctx context.Context) ([]OrderResponse, erro var parameterList []map[string]interface{} for _, req := range r.reqs { - params := req.Parameters() + params, err := req.GetParameters() + if err != nil { + return nil, err + } parameterList = append(parameterList, params) }