From d339c4f850fe8dfe2362f93adbc57e61bb14e31c Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Sat, 13 Apr 2024 11:15:01 +0800 Subject: [PATCH 1/5] feat: set auto cance pending order --- pkg/exchange/okex/exchange.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index c243af7b66..3a94f6816c 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -421,6 +421,7 @@ func (e *Exchange) submitClosePositionOrder(ctx context.Context, order types.Sub } orderReq.Tag(order.Tag) + orderReq.AutoCxl("true") orderHead, err := orderReq.Do(ctx) if err != nil { From 8957b23481b9f18b691de4b2a20c7149960cff39 Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Sun, 14 Apr 2024 10:16:39 +0800 Subject: [PATCH 2/5] feat: update close position --- pkg/exchange/okex/exchange.go | 109 ++++++- .../okex/okexapi/cancel_algo_order_request.go | 163 ++++++++++ .../okex/okexapi/close_position_request.go | 2 +- .../close_position_request_requestgen.go | 13 +- .../get_algo_pending_orders_request.go | 129 ++++++++ ..._algo_pending_orders_request_requestgen.go | 283 ++++++++++++++++++ .../okex/okexapi/get_open_orders_request.go | 7 + 7 files changed, 694 insertions(+), 12 deletions(-) create mode 100644 pkg/exchange/okex/okexapi/cancel_algo_order_request.go create mode 100644 pkg/exchange/okex/okexapi/get_algo_pending_orders_request.go create mode 100644 pkg/exchange/okex/okexapi/get_algo_pending_orders_request_requestgen.go diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 3a94f6816c..9f4156d84a 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -38,6 +38,8 @@ var ( batchCancelOrderLimiter = rate.NewLimiter(rate.Every(33*time.Millisecond), 1) // Rate Limit: 60 requests per 2 seconds, Rate limit rule: UserID queryOpenOrderLimiter = rate.NewLimiter(rate.Every(33*time.Millisecond), 1) + // Rate Limit: 60 requests per 2 seconds, Rate limit rule: UserID + queryAlgoOpenOrderLimiter = rate.NewLimiter(rate.Every(33*time.Millisecond), 1) // Rate Limit: 20 requests per 2 seconds, Rate limit rule: UserID queryClosedOrderRateLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 1) // Rate Limit: 10 requests per 2 seconds, Rate limit rule: UserID @@ -325,6 +327,20 @@ func (e *Exchange) submitSpotOrder(ctx context.Context, order types.SubmitOrder) func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrder) (*types.Order, error) { if order.ClosePosition { + orders, err := e.QueryAlgoOpenOrders(ctx, order.Symbol) + log.WithField("orders", orders). + WithField("error", err). + Info("before_ClosePosition_QueryOpenOrders_result") + + if len(orders) > 0 { + err := e.CancelAlgoOrders(ctx, orders...) + if err != nil { + log.WithField("Symbol", order.Symbol). + WithError(err). + Error("before_ClosePosition_CancelOrders_fail") + } + } + return e.submitClosePositionOrder(ctx, order) } @@ -421,7 +437,11 @@ func (e *Exchange) submitClosePositionOrder(ctx context.Context, order types.Sub } orderReq.Tag(order.Tag) - orderReq.AutoCxl("true") + orderReq.AutoCxl(true) + + params, _ := orderReq.GetParameters() + log.WithField("params", params). + Info("order_req_start") orderHead, err := orderReq.Do(ctx) if err != nil { @@ -429,7 +449,7 @@ func (e *Exchange) submitClosePositionOrder(ctx context.Context, order types.Sub } log.WithField("orderHead", orderHead). - Debug("order req result") + Info("order_req_end") return &types.Order{ SubmitOrder: order, @@ -456,9 +476,15 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [ return nil, fmt.Errorf("query open orders rate limiter wait error: %w", err) } - req := e.client.NewGetOpenOrdersRequest(). + req := e.client.NewGetOpenOrdersRequest() + if e.IsMargin { + req = e.client.NewGetMarginOpenOrdersRequest() + } + + req. InstrumentID(instrumentID). After(strconv.FormatInt(nextCursor, 10)) + openOrders, err := req.Do(ctx) if err != nil { return nil, fmt.Errorf("failed to query open orders: %w", err) @@ -488,6 +514,57 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [ return orders, err } +func (e *Exchange) QueryAlgoOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) { + instrumentID := toLocalSymbol(symbol) + + //nextCursor := "0" + for { + if err := queryAlgoOpenOrderLimiter.Wait(ctx); err != nil { + return nil, fmt.Errorf("query open orders rate limiter wait error: %w", err) + } + + req := e.client.NewGetAlgoOrdersRequest() + req. + InstrumentID(instrumentID) + + params, _ := req.GetQueryParameters() + log.WithField("symbol", symbol). + WithField("params", params). + Info("QueryAlgoOpenOrders_start") + + openOrders, err := req.Do(ctx) + if err != nil { + return nil, fmt.Errorf("failed to query open orders: %w", err) + } + + log.WithField("symbol", symbol). + WithField("openOrders", openOrders). + Info("QueryAlgoOpenOrders_result") + + for _, o := range openOrders { + orders = append(orders, types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: symbol, + }, + UUID: o.AlgoID, + }) + } + + orderLen := len(openOrders) + // a defensive programming to ensure the length of order response is expected. + if orderLen > defaultQueryLimit { + return nil, fmt.Errorf("unexpected open orders length %d", orderLen) + } + + if orderLen < defaultQueryLimit { + break + } + //nextCursor = openOrders[orderLen-1].CTime + } + + return orders, err +} + func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error { if len(orders) == 0 { return nil @@ -520,6 +597,32 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) erro return err } +func (e *Exchange) CancelAlgoOrders(ctx context.Context, orders ...types.Order) error { + if len(orders) == 0 { + return nil + } + + var reqs []*okexapi.CancelAlgoOrder + for _, order := range orders { + if len(order.Symbol) == 0 { + return ErrSymbolRequired + } + + reqs = append(reqs, &okexapi.CancelAlgoOrder{ + InstrumentID: toLocalSymbol(order.Symbol), + AlgoOrderID: order.UUID, + }) + } + + if err := batchCancelOrderLimiter.Wait(ctx); err != nil { + return fmt.Errorf("batch cancel order rate limiter wait error: %w", err) + } + batchReq := e.client.NewCancelAlgoOrderRequest() + batchReq.SetPayload(reqs) + _, err := batchReq.Do(ctx) + return err +} + func (e *Exchange) NewStream() types.Stream { return NewStream(e.client, e) } diff --git a/pkg/exchange/okex/okexapi/cancel_algo_order_request.go b/pkg/exchange/okex/okexapi/cancel_algo_order_request.go new file mode 100644 index 0000000000..8101b669aa --- /dev/null +++ b/pkg/exchange/okex/okexapi/cancel_algo_order_request.go @@ -0,0 +1,163 @@ +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" + + "github.com/c9s/requestgen" +) + +type CancelAlgoOrderResponse struct { + AlgoID string `json:"algoId"` + SCode string `json:"sCode"` + SMsg string `json:"sMsg"` +} + +type CancelAlgoOrder struct { + InstrumentID string `json:"instId"` + AlgoOrderID string `json:"algoId"` +} + +type CancelAlgoOrderRequest struct { + client requestgen.AuthenticatedAPIClient + + Payload []*CancelAlgoOrder +} + +func (c *RestClient) NewCancelAlgoOrderRequest() *CancelAlgoOrderRequest { + return &CancelAlgoOrderRequest{ + client: c, + } +} + +func (c *CancelAlgoOrderRequest) SetPayload(CancelAlgoOrders []*CancelAlgoOrder) *CancelAlgoOrderRequest { + c.Payload = CancelAlgoOrders + return c +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (c *CancelAlgoOrderRequest) 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 *CancelAlgoOrderRequest) GetParameters() (interface{}, error) { + return c.Payload, nil +} + +// GetParametersJSON converts the parameters from GetParameters into the JSON format +func (c *CancelAlgoOrderRequest) 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 *CancelAlgoOrderRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (c *CancelAlgoOrderRequest) 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 *CancelAlgoOrderRequest) 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 *CancelAlgoOrderRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (c *CancelAlgoOrderRequest) 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 *CancelAlgoOrderRequest) GetPath() string { + return "/api/v5/trade/cancel-algos" +} + +// Do generates the request object and send the request object to the API endpoint +func (c *CancelAlgoOrderRequest) Do(ctx context.Context) ([]CancelAlgoOrderResponse, 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 []CancelAlgoOrderResponse + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/exchange/okex/okexapi/close_position_request.go b/pkg/exchange/okex/okexapi/close_position_request.go index ad3f9a4727..aee0c20803 100644 --- a/pkg/exchange/okex/okexapi/close_position_request.go +++ b/pkg/exchange/okex/okexapi/close_position_request.go @@ -20,7 +20,7 @@ type ClosePositionRequest struct { posSide *string `param:"posSide"` marginMode string `param:"mgnMode"` ccy *string `param:"ccy"` - autoCxl *string `param:"autoCxl"` + autoCxl bool `param:"autoCxl"` clOrdId *string `param:"clOrdId"` tag *string `param:"tag"` } diff --git a/pkg/exchange/okex/okexapi/close_position_request_requestgen.go b/pkg/exchange/okex/okexapi/close_position_request_requestgen.go index b557444c28..d26974d67d 100644 --- a/pkg/exchange/okex/okexapi/close_position_request_requestgen.go +++ b/pkg/exchange/okex/okexapi/close_position_request_requestgen.go @@ -31,8 +31,8 @@ func (c *ClosePositionRequest) Ccy(ccy string) *ClosePositionRequest { return c } -func (c *ClosePositionRequest) AutoCxl(autoCxl string) *ClosePositionRequest { - c.autoCxl = &autoCxl +func (c *ClosePositionRequest) AutoCxl(autoCxl bool) *ClosePositionRequest { + c.autoCxl = autoCxl return c } @@ -88,13 +88,10 @@ func (c *ClosePositionRequest) GetParameters() (map[string]interface{}, error) { } else { } // check autoCxl field -> json key autoCxl - if c.autoCxl != nil { - autoCxl := *c.autoCxl + autoCxl := c.autoCxl - // assign parameter of autoCxl - params["autoCxl"] = autoCxl - } else { - } + // assign parameter of autoCxl + params["autoCxl"] = autoCxl // check clOrdId field -> json key clOrdId if c.clOrdId != nil { clOrdId := *c.clOrdId diff --git a/pkg/exchange/okex/okexapi/get_algo_pending_orders_request.go b/pkg/exchange/okex/okexapi/get_algo_pending_orders_request.go new file mode 100644 index 0000000000..63f974b40d --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_algo_pending_orders_request.go @@ -0,0 +1,129 @@ +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 + +type AlgoOrder struct { + InstType string `json:"instType"` + InstID string `json:"instId"` + Ccy string `json:"ccy"` + OrdID string `json:"ordId"` + OrdIDList []string `json:"ordIdList"` + AlgoID string `json:"algoId"` + ClOrdID string `json:"clOrdId"` + Sz string `json:"sz"` + CloseFraction string `json:"closeFraction"` + OrdType string `json:"ordType"` + Side string `json:"side"` + PosSide string `json:"posSide"` + TdMode string `json:"tdMode"` + TgtCcy string `json:"tgtCcy"` + State string `json:"state"` + Lever string `json:"lever"` + TpTriggerPx string `json:"tpTriggerPx"` + TpTriggerPxType string `json:"tpTriggerPxType"` + TpOrdPx string `json:"tpOrdPx"` + SlTriggerPx string `json:"slTriggerPx"` + SlTriggerPxType string `json:"slTriggerPxType"` + SlOrdPx string `json:"slOrdPx"` + TriggerPx string `json:"triggerPx"` + TriggerPxType string `json:"triggerPxType"` + OrdPx string `json:"ordPx"` + ActualSz string `json:"actualSz"` + Tag string `json:"tag"` + ActualPx string `json:"actualPx"` + ActualSide string `json:"actualSide"` + TriggerTime string `json:"triggerTime"` + PxVar string `json:"pxVar"` + PxSpread string `json:"pxSpread"` + SzLimit string `json:"szLimit"` + PxLimit string `json:"pxLimit"` + TimeInterval string `json:"timeInterval"` + CallbackRatio string `json:"callbackRatio"` + CallbackSpread string `json:"callbackSpread"` + ActivePx string `json:"activePx"` + MoveTriggerPx string `json:"moveTriggerPx"` + ReduceOnly string `json:"reduceOnly"` + QuickMgnType string `json:"quickMgnType"` + Last string `json:"last"` + FailCode string `json:"failCode"` + AlgoClOrdID string `json:"algoClOrdId"` + AmendPxOnTriggerType string `json:"amendPxOnTriggerType"` + AttachAlgoOrds []AttachAlgoOrd `json:"attachAlgoOrds"` + LinkedOrd LinkedOrd `json:"linkedOrd"` + CTime string `json:"cTime"` + UTime string `json:"uTime"` +} + +type AttachAlgoOrd struct { + AttachAlgoClOrdID string `json:"attachAlgoClOrdId"` + TpTriggerPx string `json:"tpTriggerPx"` + TpTriggerPxType string `json:"tpTriggerPxType"` + TpOrdPx string `json:"tpOrdPx"` + SlTriggerPx string `json:"slTriggerPx"` + SlTriggerPxType string `json:"slTriggerPxType"` + SlOrdPx string `json:"slOrdPx"` +} + +type LinkedOrd struct { + OrdID string `json:"ordId"` +} + +type AlgoOrderType string + +const ( + AlgoOrderTypeConditional AlgoOrderType = "conditional" + AlgoOrderTypeOCO AlgoOrderType = "oco" + AlgoOrderTypeTrigger AlgoOrderType = "trigger" + AlgoOrderTypeMoveOrderStop AlgoOrderType = "move_order_stop" + AlgoOrderTypeIceberg AlgoOrderType = "iceberg" + AlgoOrderTypeTWAP AlgoOrderType = "twap" +) + +//go:generate GetRequest -url "/api/v5/trade/orders-algo-pending" -type GetAlgoPendingOrdersRequest -responseDataType []AlgoOrder +type GetAlgoPendingOrdersRequest struct { + client requestgen.AuthenticatedAPIClient + + // Order type (required) + // conditional: One-way stop order + // oco: One-cancels-the-other order + // trigger: Trigger order + // move_order_stop: Trailing order + // iceberg: Iceberg order + // twap: TWAP order + ordType AlgoOrderType `param:"ordType,query"` + + // Instrument type (optional) + // SPOT, SWAP, FUTURES, MARGIN + instrumentType *InstrumentType `param:"instType,query"` + + // Instrument ID, e.g. BTC-USDT (optional) + instrumentID *string `param:"instId,query"` + + // Algo ID (optional) + algoID *string `param:"algoId,query"` + + // Client-supplied Algo ID (optional) + // A combination of case-sensitive alphanumerics, all numbers, or all letters of up to 32 characters. + algoClOrdID *string `param:"algoClOrdId,query"` + + // Pagination of data to return records earlier than the requested algoId (optional) + after *string `param:"after,query,timestamp"` + + // Pagination of data to return records newer than the requested algoId (optional) + before *string `param:"before,query,timestamp"` + + // Number of results per request. The maximum is 100. The default is 100 (optional) + limit *string `param:"limit,query"` +} + +func (c *RestClient) NewGetAlgoOrdersRequest() *GetAlgoPendingOrdersRequest { + return &GetAlgoPendingOrdersRequest{ + client: c, + ordType: AlgoOrderTypeOCO, + } +} diff --git a/pkg/exchange/okex/okexapi/get_algo_pending_orders_request_requestgen.go b/pkg/exchange/okex/okexapi/get_algo_pending_orders_request_requestgen.go new file mode 100644 index 0000000000..406995e0be --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_algo_pending_orders_request_requestgen.go @@ -0,0 +1,283 @@ +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/trade/orders-algo-pending -type GetAlgoPendingOrdersRequest -responseDataType []AlgoOrder"; DO NOT EDIT. + +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (g *GetAlgoPendingOrdersRequest) OrdType(ordType AlgoOrderType) *GetAlgoPendingOrdersRequest { + g.ordType = ordType + return g +} + +func (g *GetAlgoPendingOrdersRequest) InstrumentType(instrumentType InstrumentType) *GetAlgoPendingOrdersRequest { + g.instrumentType = &instrumentType + return g +} + +func (g *GetAlgoPendingOrdersRequest) InstrumentID(instrumentID string) *GetAlgoPendingOrdersRequest { + g.instrumentID = &instrumentID + return g +} + +func (g *GetAlgoPendingOrdersRequest) AlgoID(algoID string) *GetAlgoPendingOrdersRequest { + g.algoID = &algoID + return g +} + +func (g *GetAlgoPendingOrdersRequest) AlgoClOrdID(algoClOrdID string) *GetAlgoPendingOrdersRequest { + g.algoClOrdID = &algoClOrdID + return g +} + +func (g *GetAlgoPendingOrdersRequest) After(after string) *GetAlgoPendingOrdersRequest { + g.after = &after + return g +} + +func (g *GetAlgoPendingOrdersRequest) Before(before string) *GetAlgoPendingOrdersRequest { + g.before = &before + return g +} + +func (g *GetAlgoPendingOrdersRequest) Limit(limit string) *GetAlgoPendingOrdersRequest { + g.limit = &limit + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetAlgoPendingOrdersRequest) GetQueryParameters() (url.Values, error) { + var params = map[string]interface{}{} + // check ordType field -> json key ordType + ordType := g.ordType + + // TEMPLATE check-valid-values + switch ordType { + case AlgoOrderTypeConditional, AlgoOrderTypeOCO, AlgoOrderTypeTrigger, AlgoOrderTypeMoveOrderStop, AlgoOrderTypeIceberg, AlgoOrderTypeTWAP: + params["ordType"] = ordType + + default: + return nil, fmt.Errorf("ordType value %v is invalid", ordType) + + } + // END TEMPLATE check-valid-values + + // assign parameter of ordType + params["ordType"] = ordType + // check instrumentType field -> json key instType + if g.instrumentType != nil { + instrumentType := *g.instrumentType + + // TEMPLATE check-valid-values + switch instrumentType { + case InstrumentTypeSpot, InstrumentTypeMargin, InstrumentTypeSwap, InstrumentTypeFutures, InstrumentTypeOption, InstrumentTypeAny: + params["instType"] = instrumentType + + default: + return nil, fmt.Errorf("instType value %v is invalid", instrumentType) + + } + // END TEMPLATE check-valid-values + + // assign parameter of instrumentType + params["instType"] = instrumentType + } else { + } + // check instrumentID field -> json key instId + if g.instrumentID != nil { + instrumentID := *g.instrumentID + + // assign parameter of instrumentID + params["instId"] = instrumentID + } else { + } + // check algoID field -> json key algoId + if g.algoID != nil { + algoID := *g.algoID + + // assign parameter of algoID + params["algoId"] = algoID + } else { + } + // check algoClOrdID field -> json key algoClOrdId + if g.algoClOrdID != nil { + algoClOrdID := *g.algoClOrdID + + // assign parameter of algoClOrdID + params["algoClOrdId"] = algoClOrdID + } else { + } + // check after field -> json key after + if g.after != nil { + after := *g.after + + // assign parameter of after + params["after"] = after + } else { + } + // check before field -> json key before + if g.before != nil { + before := *g.before + + // assign parameter of before + params["before"] = before + } else { + } + // check limit field -> json key limit + if g.limit != nil { + limit := *g.limit + + // assign parameter of limit + params["limit"] = limit + } else { + } + + 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 (g *GetAlgoPendingOrdersRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetAlgoPendingOrdersRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := g.GetParameters() + if err != nil { + return query, err + } + + for _k, _v := range params { + if g.isVarSlice(_v) { + g.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 (g *GetAlgoPendingOrdersRequest) GetParametersJSON() ([]byte, error) { + params, err := g.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 (g *GetAlgoPendingOrdersRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetAlgoPendingOrdersRequest) 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 (g *GetAlgoPendingOrdersRequest) 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 (g *GetAlgoPendingOrdersRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetAlgoPendingOrdersRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := g.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 (g *GetAlgoPendingOrdersRequest) GetPath() string { + return "/api/v5/trade/orders-algo-pending" +} + +// Do generates the request object and send the request object to the API endpoint +func (g *GetAlgoPendingOrdersRequest) Do(ctx context.Context) ([]AlgoOrder, error) { + + // no body params + var params interface{} + query, err := g.GetQueryParameters() + if err != nil { + return nil, err + } + + var apiURL string + + apiURL = g.GetPath() + + req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := g.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 []AlgoOrder + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/exchange/okex/okexapi/get_open_orders_request.go b/pkg/exchange/okex/okexapi/get_open_orders_request.go index 448745caa1..7623b9af6d 100644 --- a/pkg/exchange/okex/okexapi/get_open_orders_request.go +++ b/pkg/exchange/okex/okexapi/get_open_orders_request.go @@ -41,3 +41,10 @@ func (c *RestClient) NewGetOpenOrdersRequest() *GetOpenOrdersRequest { instrumentType: InstrumentTypeSpot, } } + +func (c *RestClient) NewGetMarginOpenOrdersRequest() *GetOpenOrdersRequest { + return &GetOpenOrdersRequest{ + client: c, + instrumentType: InstrumentTypeMargin, + } +} From 1c9cfc80fa43bb13f2086582ae67fac29e461aa0 Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Sun, 14 Apr 2024 11:58:34 +0800 Subject: [PATCH 3/5] feat: update exchange --- pkg/exchange/okex/exchange.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 9f4156d84a..3fa817dc68 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -517,7 +517,6 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [ func (e *Exchange) QueryAlgoOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) { instrumentID := toLocalSymbol(symbol) - //nextCursor := "0" for { if err := queryAlgoOpenOrderLimiter.Wait(ctx); err != nil { return nil, fmt.Errorf("query open orders rate limiter wait error: %w", err) @@ -559,7 +558,6 @@ func (e *Exchange) QueryAlgoOpenOrders(ctx context.Context, symbol string) (orde if orderLen < defaultQueryLimit { break } - //nextCursor = openOrders[orderLen-1].CTime } return orders, err From 24b9b97d3cb411b5ff82dc3b690c0f08ec072217 Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Sun, 14 Apr 2024 14:42:58 +0800 Subject: [PATCH 4/5] feat: fixbug for pedding orders --- pkg/exchange/okex/exchange.go | 77 +++++++++++++++++-- .../get_algo_pending_orders_request.go | 7 ++ 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 3fa817dc68..1dd3612d33 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -327,18 +327,26 @@ func (e *Exchange) submitSpotOrder(ctx context.Context, order types.SubmitOrder) func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrder) (*types.Order, error) { if order.ClosePosition { - orders, err := e.QueryAlgoOpenOrders(ctx, order.Symbol) + orders, err1 := e.QueryAlgoOpenOrders(ctx, order.Symbol) + ocoOrders, err2 := e.QueryOCOAlgoOpenOrders(ctx, order.Symbol) + log.WithField("orders", orders). - WithField("error", err). + WithField("ocoOrders", ocoOrders). + WithField("error1", err1). + WithField("error2", err2). Info("before_ClosePosition_QueryOpenOrders_result") - if len(orders) > 0 { - err := e.CancelAlgoOrders(ctx, orders...) + if len(orders)+len(ocoOrders) > 0 { + allOrders := append(orders, ocoOrders...) + err := e.CancelAlgoOrders(ctx, allOrders...) if err != nil { log.WithField("Symbol", order.Symbol). WithError(err). Error("before_ClosePosition_CancelOrders_fail") } + + log.WithField("Symbol", order.Symbol). + Info("before_ClosePosition_CancelOrders_ok") } return e.submitClosePositionOrder(ctx, order) @@ -563,6 +571,55 @@ func (e *Exchange) QueryAlgoOpenOrders(ctx context.Context, symbol string) (orde return orders, err } +func (e *Exchange) QueryOCOAlgoOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) { + instrumentID := toLocalSymbol(symbol) + + for { + if err := queryAlgoOpenOrderLimiter.Wait(ctx); err != nil { + return nil, fmt.Errorf("query open orders rate limiter wait error: %w", err) + } + + req := e.client.NewGetOCOAlgoOrdersRequest() + req. + InstrumentID(instrumentID) + + params, _ := req.GetQueryParameters() + log.WithField("symbol", symbol). + WithField("params", params). + Info("QueryOCOAlgoOpenOrders_start") + + openOrders, err := req.Do(ctx) + if err != nil { + return nil, fmt.Errorf("failed to query open orders: %w", err) + } + + log.WithField("symbol", symbol). + WithField("openOrders", openOrders). + Info("QueryOCOAlgoOpenOrders_result") + + for _, o := range openOrders { + orders = append(orders, types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: symbol, + }, + UUID: o.AlgoID, + }) + } + + orderLen := len(openOrders) + // a defensive programming to ensure the length of order response is expected. + if orderLen > defaultQueryLimit { + return nil, fmt.Errorf("unexpected open orders length %d", orderLen) + } + + if orderLen < defaultQueryLimit { + break + } + } + + return orders, err +} + func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error { if len(orders) == 0 { return nil @@ -617,7 +674,17 @@ func (e *Exchange) CancelAlgoOrders(ctx context.Context, orders ...types.Order) } batchReq := e.client.NewCancelAlgoOrderRequest() batchReq.SetPayload(reqs) - _, err := batchReq.Do(ctx) + + params, _ := batchReq.GetParameters() + log.WithField("params", params). + Info("CancelAlgoOrders_start") + + resp, err := batchReq.Do(ctx) + + log.WithField("resp", resp). + WithField("error", err). + Info("CancelAlgoOrders_result") + return err } diff --git a/pkg/exchange/okex/okexapi/get_algo_pending_orders_request.go b/pkg/exchange/okex/okexapi/get_algo_pending_orders_request.go index 63f974b40d..5d602d96a0 100644 --- a/pkg/exchange/okex/okexapi/get_algo_pending_orders_request.go +++ b/pkg/exchange/okex/okexapi/get_algo_pending_orders_request.go @@ -122,6 +122,13 @@ type GetAlgoPendingOrdersRequest struct { } func (c *RestClient) NewGetAlgoOrdersRequest() *GetAlgoPendingOrdersRequest { + return &GetAlgoPendingOrdersRequest{ + client: c, + ordType: AlgoOrderTypeConditional, + } +} + +func (c *RestClient) NewGetOCOAlgoOrdersRequest() *GetAlgoPendingOrdersRequest { return &GetAlgoPendingOrdersRequest{ client: c, ordType: AlgoOrderTypeOCO, From c2f4f19f337cba0cfd008e93ff7deaca18d68be3 Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Sun, 14 Apr 2024 14:53:13 +0800 Subject: [PATCH 5/5] feat: fixbug for CancelAlgoOrders --- pkg/exchange/okex/exchange.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 1dd3612d33..55ede73882 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -337,7 +337,16 @@ func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrde Info("before_ClosePosition_QueryOpenOrders_result") if len(orders)+len(ocoOrders) > 0 { - allOrders := append(orders, ocoOrders...) + allOrders := make([]types.Order, 0) + + if len(orders) > 0 { + allOrders = append(allOrders, orders...) + } + + if len(ocoOrders) > 0 { + allOrders = append(allOrders, ocoOrders...) + } + err := e.CancelAlgoOrders(ctx, allOrders...) if err != nil { log.WithField("Symbol", order.Symbol).