From bd58e47911003a957006d6bc3d4cadfe493109bb Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Sun, 14 Apr 2024 23:09:07 +0800 Subject: [PATCH 1/8] feat: impl ExchangePositionUpdateService --- pkg/exchange/okex/exchange.go | 56 ++++ .../okex/okexapi/amend_algo_order_request.go | 84 +++++ .../amend_algo_order_request_requestgen.go | 308 ++++++++++++++++++ pkg/types/exchange.go | 4 + pkg/types/position.go | 4 + 5 files changed, 456 insertions(+) create mode 100644 pkg/exchange/okex/okexapi/amend_algo_order_request.go create mode 100644 pkg/exchange/okex/okexapi/amend_algo_order_request_requestgen.go diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 55ede73882..3ae064179b 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -46,6 +46,8 @@ var ( queryTradeLimiter = rate.NewLimiter(rate.Every(200*time.Millisecond), 1) // Rate Limit: 40 requests per 2 seconds, Rate limit rule: IP queryKLineLimiter = rate.NewLimiter(rate.Every(50*time.Millisecond), 1) + // Rate Limit: 20 requests per 2 seconds, Rate limit rule: IP + AmendAlgoOrdeLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 1) ) const ( @@ -420,11 +422,18 @@ func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrde orderReq.TakeProfitOrdPx("-1") } + params, _ := orderReq.GetParameters() + log.WithField("params", params). + Info("submitMarginOrder_start") + orders, err := orderReq.Do(ctx) if err != nil { return nil, err } + log.WithField("orders", orders). + Info("submitMarginOrder_result") + if len(orders) != 1 { return nil, fmt.Errorf("unexpected length of order response: %v", orders) } @@ -1002,3 +1011,50 @@ func (e *Exchange) IsSupportedInterval(interval types.Interval) bool { _, ok := SupportedIntervals[interval] return ok } + +func (e *Exchange) UpdatePosition(ctx context.Context, position *types.Position) error { + ocoOrders, err := e.QueryOCOAlgoOpenOrders(ctx, position.Symbol) + if err != nil { + return errors.Wrap(err, "QueryOCOAlgoOpenOrders_fail") + } + + log.WithField("ocoOrders", ocoOrders). + Info("QueryOCOAlgoOpenOrders_result") + + if len(ocoOrders) > 0 { + order := ocoOrders[0] + + req := e.client.NewAmendAlgoOrderRequest() + req.InstID(toLocalSymbol(position.Symbol)) + req.AlgoID(order.UUID) + + if position.TpTriggerPx != nil { + req.NewTpTriggerPxType("last") + req.NewTpTriggerPx(position.Market.FormatPrice(*position.TpTriggerPx)) + req.NewTpOrdPx("-1") + } + + if position.SlTriggerPx != nil { + req.NewSlTriggerPxType("last") + req.NewSlTriggerPx(position.Market.FormatPrice(*position.SlTriggerPx)) + req.NewSlOrdPx("-1") + } + + params, _ := req.GetParameters() + log.WithField("params", params).Info("AmendAlgoOrder_start") + + resp, err := req.Do(ctx) + if err != nil { + log.WithField("params", params). + Error("AmendAlgoOrder_fail") + return err + } + + log.WithField("resp", resp). + Info("AmendAlgoOrder_ok") + + return nil + } + + return nil +} diff --git a/pkg/exchange/okex/okexapi/amend_algo_order_request.go b/pkg/exchange/okex/okexapi/amend_algo_order_request.go new file mode 100644 index 0000000000..194f688e18 --- /dev/null +++ b/pkg/exchange/okex/okexapi/amend_algo_order_request.go @@ -0,0 +1,84 @@ +package okexapi + +import ( + "github.com/c9s/requestgen" +) + +type AmendAlgoOrder struct { + AlgoID string `json:"algoId"` + AlgoClOrdID string `json:"algoClOrdId"` + ReqID string `json:"reqId"` + SCode string `json:"sCode"` + SMsg string `json:"sMsg"` +} + +//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/amend-algos" -type AmendAlgoOrderRequest -responseType .AmendAlgoOrderResponse + +type AmendAlgoOrderRequest struct { + client requestgen.AuthenticatedAPIClient + + // Instrument ID (required) + instID string `param:"instId"` + + // Algo ID (conditional) + // Either algoId or algoClOrdId is required. If both are passed, algoId will be used. + algoID *string `param:"algoId"` + + // Client-supplied Algo ID (conditional) + // Either algoId or algoClOrdId is required. If both are passed, algoId will be used. + algoClOrdID *string `param:"algoClOrdId"` + + // Whether the order needs to be automatically canceled when the order amendment fails (optional) + // Valid options: false or true, the default is false. + cxlOnFail *bool `param:"cxlOnFail"` + + // Client Request ID as assigned by the client for order amendment (conditional) + // A combination of case-sensitive alphanumerics, all numbers, or all letters of up to 32 characters. + // The response will include the corresponding reqId to help you identify the request if you provide it in the request. + reqID *string `param:"reqId"` + + // New quantity after amendment (conditional) + newSz *string `param:"newSz"` + + // Take-profit trigger price (conditional) + // Either the take-profit trigger price or order price is 0, it means that the take-profit is deleted + newTpTriggerPx *string `param:"newTpTriggerPx"` + + // Take-profit order price (conditional) + // If the price is -1, take-profit will be executed at the market price. + newTpOrdPx *string `param:"newTpOrdPx"` + + // Stop-loss trigger price (conditional) + // Either the stop-loss trigger price or order price is 0, it means that the stop-loss is deleted + newSlTriggerPx *string `param:"newSlTriggerPx"` + + // Stop-loss order price (conditional) + // If the price is -1, stop-loss will be executed at the market price. + newSlOrdPx *string `param:"newSlOrdPx"` + + // Take-profit trigger price type (conditional) + // last: last price + // index: index price + // mark: mark price + newTpTriggerPxType *string `param:"newTpTriggerPxType"` + + // Stop-loss trigger price type (conditional) + // last: last price + // index: index price + // mark: mark price + newSlTriggerPxType *string `param:"newSlTriggerPxType"` +} + +type AmendAlgoOrderResponse struct { + APIResponse + Data []AmendAlgoOrder `json:"data"` +} + +func (c *RestClient) NewAmendAlgoOrderRequest() *AmendAlgoOrderRequest { + return &AmendAlgoOrderRequest{ + client: c, + } +} diff --git a/pkg/exchange/okex/okexapi/amend_algo_order_request_requestgen.go b/pkg/exchange/okex/okexapi/amend_algo_order_request_requestgen.go new file mode 100644 index 0000000000..0397d47930 --- /dev/null +++ b/pkg/exchange/okex/okexapi/amend_algo_order_request_requestgen.go @@ -0,0 +1,308 @@ +// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Data -url /api/v5/trade/amend-algos -type AmendAlgoOrderRequest -responseType .AmendAlgoOrderResponse"; DO NOT EDIT. + +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (r *AmendAlgoOrderRequest) InstID(instID string) *AmendAlgoOrderRequest { + r.instID = instID + return r +} + +func (r *AmendAlgoOrderRequest) AlgoID(algoID string) *AmendAlgoOrderRequest { + r.algoID = &algoID + return r +} + +func (r *AmendAlgoOrderRequest) AlgoClOrdID(algoClOrdID string) *AmendAlgoOrderRequest { + r.algoClOrdID = &algoClOrdID + return r +} + +func (r *AmendAlgoOrderRequest) CxlOnFail(cxlOnFail bool) *AmendAlgoOrderRequest { + r.cxlOnFail = &cxlOnFail + return r +} + +func (r *AmendAlgoOrderRequest) ReqID(reqID string) *AmendAlgoOrderRequest { + r.reqID = &reqID + return r +} + +func (r *AmendAlgoOrderRequest) NewSz(newSz string) *AmendAlgoOrderRequest { + r.newSz = &newSz + return r +} + +func (r *AmendAlgoOrderRequest) NewTpTriggerPx(newTpTriggerPx string) *AmendAlgoOrderRequest { + r.newTpTriggerPx = &newTpTriggerPx + return r +} + +func (r *AmendAlgoOrderRequest) NewTpOrdPx(newTpOrdPx string) *AmendAlgoOrderRequest { + r.newTpOrdPx = &newTpOrdPx + return r +} + +func (r *AmendAlgoOrderRequest) NewSlTriggerPx(newSlTriggerPx string) *AmendAlgoOrderRequest { + r.newSlTriggerPx = &newSlTriggerPx + return r +} + +func (r *AmendAlgoOrderRequest) NewSlOrdPx(newSlOrdPx string) *AmendAlgoOrderRequest { + r.newSlOrdPx = &newSlOrdPx + return r +} + +func (r *AmendAlgoOrderRequest) NewTpTriggerPxType(newTpTriggerPxType string) *AmendAlgoOrderRequest { + r.newTpTriggerPxType = &newTpTriggerPxType + return r +} + +func (r *AmendAlgoOrderRequest) NewSlTriggerPxType(newSlTriggerPxType string) *AmendAlgoOrderRequest { + r.newSlTriggerPxType = &newSlTriggerPxType + return r +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (r *AmendAlgoOrderRequest) 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 (r *AmendAlgoOrderRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check instID field -> json key instId + instID := r.instID + + // assign parameter of instID + params["instId"] = instID + // check algoID field -> json key algoId + if r.algoID != nil { + algoID := *r.algoID + + // assign parameter of algoID + params["algoId"] = algoID + } else { + } + // check algoClOrdID field -> json key algoClOrdId + if r.algoClOrdID != nil { + algoClOrdID := *r.algoClOrdID + + // assign parameter of algoClOrdID + params["algoClOrdId"] = algoClOrdID + } else { + } + // check cxlOnFail field -> json key cxlOnFail + if r.cxlOnFail != nil { + cxlOnFail := *r.cxlOnFail + + // assign parameter of cxlOnFail + params["cxlOnFail"] = cxlOnFail + } else { + } + // check reqID field -> json key reqId + if r.reqID != nil { + reqID := *r.reqID + + // assign parameter of reqID + params["reqId"] = reqID + } else { + } + // check newSz field -> json key newSz + if r.newSz != nil { + newSz := *r.newSz + + // assign parameter of newSz + params["newSz"] = newSz + } else { + } + // check newTpTriggerPx field -> json key newTpTriggerPx + if r.newTpTriggerPx != nil { + newTpTriggerPx := *r.newTpTriggerPx + + // assign parameter of newTpTriggerPx + params["newTpTriggerPx"] = newTpTriggerPx + } else { + } + // check newTpOrdPx field -> json key newTpOrdPx + if r.newTpOrdPx != nil { + newTpOrdPx := *r.newTpOrdPx + + // assign parameter of newTpOrdPx + params["newTpOrdPx"] = newTpOrdPx + } else { + } + // check newSlTriggerPx field -> json key newSlTriggerPx + if r.newSlTriggerPx != nil { + newSlTriggerPx := *r.newSlTriggerPx + + // assign parameter of newSlTriggerPx + params["newSlTriggerPx"] = newSlTriggerPx + } else { + } + // check newSlOrdPx field -> json key newSlOrdPx + if r.newSlOrdPx != nil { + newSlOrdPx := *r.newSlOrdPx + + // assign parameter of newSlOrdPx + params["newSlOrdPx"] = newSlOrdPx + } else { + } + // check newTpTriggerPxType field -> json key newTpTriggerPxType + if r.newTpTriggerPxType != nil { + newTpTriggerPxType := *r.newTpTriggerPxType + + // assign parameter of newTpTriggerPxType + params["newTpTriggerPxType"] = newTpTriggerPxType + } else { + } + // check newSlTriggerPxType field -> json key newSlTriggerPxType + if r.newSlTriggerPxType != nil { + newSlTriggerPxType := *r.newSlTriggerPxType + + // assign parameter of newSlTriggerPxType + params["newSlTriggerPxType"] = newSlTriggerPxType + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (r *AmendAlgoOrderRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := r.GetParameters() + if err != nil { + return query, err + } + + for _k, _v := range params { + if r.isVarSlice(_v) { + r.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 (r *AmendAlgoOrderRequest) GetParametersJSON() ([]byte, error) { + params, err := r.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 (r *AmendAlgoOrderRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (r *AmendAlgoOrderRequest) 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 (r *AmendAlgoOrderRequest) 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 (r *AmendAlgoOrderRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (r *AmendAlgoOrderRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := r.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 (r *AmendAlgoOrderRequest) GetPath() string { + return "/api/v5/trade/amend-algos" +} + +// Do generates the request object and send the request object to the API endpoint +func (r *AmendAlgoOrderRequest) Do(ctx context.Context) (*AmendAlgoOrderResponse, error) { + + params, err := r.GetParameters() + if err != nil { + return nil, err + } + query := url.Values{} + + var apiURL string + + apiURL = r.GetPath() + + req, err := r.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := r.client.SendRequest(req) + if err != nil { + return nil, err + } + + var apiResponse AmendAlgoOrderResponse + 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 + } + } + return &apiResponse, nil +} diff --git a/pkg/types/exchange.go b/pkg/types/exchange.go index b67a9b2b28..58a63f52f2 100644 --- a/pkg/types/exchange.go +++ b/pkg/types/exchange.go @@ -156,6 +156,10 @@ type ExchangeRewardService interface { QueryRewards(ctx context.Context, startTime time.Time) ([]Reward, error) } +type ExchangePositionUpdateService interface { + UpdatePosition(ctx context.Context, position *Position) error +} + type TradeQueryOptions struct { StartTime *time.Time EndTime *time.Time diff --git a/pkg/types/position.go b/pkg/types/position.go index 177cafc8d6..ab6e7d08d6 100644 --- a/pkg/types/position.go +++ b/pkg/types/position.go @@ -83,6 +83,10 @@ type Position struct { // ttl is the ttl to keep in persistence ttl time.Duration + + // for update Take-profit and stop-loss + TpTriggerPx *fixedpoint.Value + SlTriggerPx *fixedpoint.Value } func (s *Position) SetTTL(ttl time.Duration) { From 736ce69244eca112495b856689bbd30f3669634d Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Mon, 15 Apr 2024 22:23:53 +0800 Subject: [PATCH 2/8] feat: update position add log --- pkg/exchange/okex/exchange.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 3ae064179b..646fa707e3 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -567,9 +567,11 @@ func (e *Exchange) QueryAlgoOpenOrders(ctx context.Context, symbol string) (orde Info("QueryAlgoOpenOrders_result") for _, o := range openOrders { + amount, _ := fixedpoint.NewFromString(o.Sz) orders = append(orders, types.Order{ SubmitOrder: types.SubmitOrder{ - Symbol: symbol, + Symbol: symbol, + Quantity: amount, }, UUID: o.AlgoID, }) @@ -1027,6 +1029,7 @@ func (e *Exchange) UpdatePosition(ctx context.Context, position *types.Position) req := e.client.NewAmendAlgoOrderRequest() req.InstID(toLocalSymbol(position.Symbol)) req.AlgoID(order.UUID) + req.NewSz(position.Market.FormatQuantity(order.Quantity)) if position.TpTriggerPx != nil { req.NewTpTriggerPxType("last") @@ -1046,6 +1049,7 @@ func (e *Exchange) UpdatePosition(ctx context.Context, position *types.Position) resp, err := req.Do(ctx) if err != nil { log.WithField("params", params). + WithError(err). Error("AmendAlgoOrder_fail") return err } From 556398c423f22443d213208a30aa66fc77a7978b Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Tue, 16 Apr 2024 08:38:11 +0800 Subject: [PATCH 3/8] feat: add place algo order --- pkg/exchange/okex/exchange.go | 115 +++-- .../okex/okexapi/place_algo_order_request.go | 158 ++++++ .../place_algo_order_request_requestgen.go | 449 ++++++++++++++++++ 3 files changed, 695 insertions(+), 27 deletions(-) create mode 100644 pkg/exchange/okex/okexapi/place_algo_order_request.go create mode 100644 pkg/exchange/okex/okexapi/place_algo_order_request_requestgen.go diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 646fa707e3..705ee609e6 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -327,39 +327,44 @@ 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, err1 := e.QueryAlgoOpenOrders(ctx, order.Symbol) - ocoOrders, err2 := e.QueryOCOAlgoOpenOrders(ctx, order.Symbol) +func (e *Exchange) autoCancelAllAlgoOrders(ctx context.Context, symbol string) error { + orders, err1 := e.QueryAlgoOpenOrders(ctx, symbol) + ocoOrders, err2 := e.QueryOCOAlgoOpenOrders(ctx, symbol) - log.WithField("orders", orders). - WithField("ocoOrders", ocoOrders). - WithField("error1", err1). - WithField("error2", err2). - Info("before_ClosePosition_QueryOpenOrders_result") + log.WithField("orders", orders). + WithField("ocoOrders", ocoOrders). + WithField("error1", err1). + WithField("error2", err2). + Info("autoCancelAlgoOrders_QueryOpenOrders_result") - if len(orders)+len(ocoOrders) > 0 { - allOrders := make([]types.Order, 0) + if len(orders)+len(ocoOrders) > 0 { + allOrders := make([]types.Order, 0) - if len(orders) > 0 { - allOrders = append(allOrders, orders...) - } - - if len(ocoOrders) > 0 { - allOrders = append(allOrders, ocoOrders...) - } + if len(orders) > 0 { + allOrders = append(allOrders, orders...) + } - err := e.CancelAlgoOrders(ctx, allOrders...) - if err != nil { - log.WithField("Symbol", order.Symbol). - WithError(err). - Error("before_ClosePosition_CancelOrders_fail") - } + if len(ocoOrders) > 0 { + allOrders = append(allOrders, ocoOrders...) + } - log.WithField("Symbol", order.Symbol). - Info("before_ClosePosition_CancelOrders_ok") + err := e.CancelAlgoOrders(ctx, allOrders...) + if err != nil { + log.WithField("Symbol", symbol). + WithError(err). + Error("autoCancelAlgoOrders_CancelOrders_fail") } + log.WithField("Symbol", symbol). + Info("autoCancelAlgoOrders_CancelOrders_ok") + } + + return nil +} + +func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrder) (*types.Order, error) { + if order.ClosePosition { + e.autoCancelAllAlgoOrders(ctx, order.Symbol) return e.submitClosePositionOrder(ctx, order) } @@ -1014,7 +1019,7 @@ func (e *Exchange) IsSupportedInterval(interval types.Interval) bool { return ok } -func (e *Exchange) UpdatePosition(ctx context.Context, position *types.Position) error { +func (e *Exchange) UpdatePosition_Bakup(ctx context.Context, position *types.Position) error { ocoOrders, err := e.QueryOCOAlgoOpenOrders(ctx, position.Symbol) if err != nil { return errors.Wrap(err, "QueryOCOAlgoOpenOrders_fail") @@ -1062,3 +1067,59 @@ func (e *Exchange) UpdatePosition(ctx context.Context, position *types.Position) return nil } + +func (e *Exchange) PlaceTakeProfitStopLossOrder(ctx context.Context, position *types.Position) error { + orderReq := e.client.NewPlaceOCOAlgoOrderRequest() + + // Margin mode cross isolated + if e.IsIsolatedMargin { + orderReq.TdMode(okexapi.TradeModeIsolated) + } else { + orderReq.TdMode(okexapi.TradeModeCross) + } + + orderReq.InstID(toLocalSymbol(position.Symbol)) + + if position.IsLong() { + orderReq.Side("sell") + } else if position.IsShort() { + orderReq.Side("buy") + } + + orderReq.Sz(position.Market.FormatQuantity(position.Quote)) + + if position.TpTriggerPx != nil { + orderReq.TpTriggerPxType("last") + orderReq.TpTriggerPx(position.Market.FormatPrice(*position.TpTriggerPx)) + orderReq.TpOrdPx("-1") + } + + if position.SlTriggerPx != nil { + orderReq.SlTriggerPxType("last") + orderReq.SlTriggerPx(position.Market.FormatPrice(*position.SlTriggerPx)) + orderReq.SlOrdPx("-1") + } + + params, _ := orderReq.GetParameters() + log.WithField("params", params). + Info("PlaceTakeProfitStopLossOrder_start") + + orders, err := orderReq.Do(ctx) + if err != nil { + log.WithField("params", params). + WithError(err). + Error("PlaceTakeProfitStopLossOrder_fail") + + return err + } + + log.WithField("orders", orders). + Info("PlaceTakeProfitStopLossOrder_ok") + + return nil +} + +func (e *Exchange) UpdatePosition(ctx context.Context, position *types.Position) error { + e.autoCancelAllAlgoOrders(ctx, position.Symbol) + return e.PlaceTakeProfitStopLossOrder(ctx, position) +} diff --git a/pkg/exchange/okex/okexapi/place_algo_order_request.go b/pkg/exchange/okex/okexapi/place_algo_order_request.go new file mode 100644 index 0000000000..4721522abb --- /dev/null +++ b/pkg/exchange/okex/okexapi/place_algo_order_request.go @@ -0,0 +1,158 @@ +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 PlaceAlgoOrderResponse struct { + AlgoID string `json:"algoId"` + CClOrdID string `json:"clOrdId"` + AlgoClOrdID string `json:"algoClOrdId"` + SCode string `json:"sCode"` + SMsg string `json:"sMsg"` +} + +//go:generate PostRequest -url "/api/v5/trade/order-algo" -type PlaceAlgoOrderRequest -responseType .PlaceAlgoOrderResponse +type PlaceAlgoOrderRequest struct { + client requestgen.AuthenticatedAPIClient + + // Instrument ID, e.g. BTC-USDT (required) + instID string `param:"instId"` + + // Trade mode (required) + // Margin mode: cross, isolated + // Non-Margin mode: cash + // spot_isolated (only applicable to SPOT lead trading) + tdMode TradeMode `param:"tdMode" validValues:"cross,isolated,cash"` + + // Margin currency (optional) + // Only applicable to cross MARGIN orders in Single-currency margin. + ccy *string `param:"ccy"` + + // Order side, buy or sell (required) + side string `param:"side" validValues:"buy,sell"` + + // Position side (conditional) + // Required in long/short mode and only be long or short + posSide *string `param:"posSide"` + + // Order type (required) + // conditional: One-way stop order + // oco: One-cancels-the-other order + // trigger: Trigger order + // move_order_stop: Trailing order + // twap: TWAP order + ordType AlgoOrderType `param:"ordType"` + + // Quantity to buy or sell (conditional) + // Either sz or closeFraction is required. + sz *string `param:"sz"` + + // Order tag (optional) + // A combination of case-sensitive alphanumerics, all numbers, or all letters of up to 16 characters. + tag *string `param:"tag"` + + // Order quantity unit setting for sz (optional) + // base_ccy: Base currency, quote_ccy: Quote currency + // Only applicable to SPOT traded with Market buy conditional order + // Default is quote_ccy for buy, base_ccy for sell + tgtCcy *string `param:"tgtCcy"` + + // 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"` + + // Fraction of position to be closed when the algo order is triggered. (conditional) + // Currently the system supports fully closing the position only so the only accepted value is 1. + // For the same position, only one TPSL pending order for fully closing the position is supported. + // This is only applicable to FUTURES or SWAP instruments. + // If posSide is net, reduceOnly must be true. + // This is only applicable if ordType is conditional or oco. + // This is only applicable if the stop loss and take profit order is executed as market order. + // This is not supported in Portfolio Margin mode. + // Either sz or closeFraction is required. + closeFraction *string `param:"closeFraction"` + + // Take-profit trigger price (optional) + // If you fill in this parameter, you should fill in the take-profit order price as well. + tpTriggerPx *string `param:"tpTriggerPx"` + + // Take-profit trigger price type (optional) + // last: last price + // index: index price + // mark: mark price + // The default is last + tpTriggerPxType *string `param:"tpTriggerPxType"` + + // Take-profit order price (optional) + // If you fill in this parameter, you should fill in the take-profit trigger price as well. + // If the price is -1, take-profit will be executed at the market price. + tpOrdPx *string `param:"tpOrdPx"` + + // TP order kind (optional) + // condition or limit + // The default is condition + tpOrdKind *string `param:"tpOrdKind"` + + // Stop-loss trigger price (optional) + // If you fill in this parameter, you should fill in the stop-loss order price. + slTriggerPx *string `param:"slTriggerPx"` + + // Stop-loss trigger price type (optional) + // last: last price + // index: index price + // mark: mark price + // The default is last + slTriggerPxType *string `param:"slTriggerPxType"` + + // Stop-loss order price (optional) + // If you fill in this parameter, you should fill in the stop-loss trigger price. + // If the price is -1, stop-loss will be executed at the market price. + slOrdPx *string `param:"slOrdPx"` + + // Whether the TP/SL order placed by the user is associated with the corresponding position of the instrument. (optional) + // If it is associated, the TP/SL order will be canceled when the position is fully closed; + // if it is not, the TP/SL order will not be affected when the position is fully closed. + // Valid values: + // true: Place a TP/SL order associated with the position + // false: Place a TP/SL order that is not associated with the position + // The default value is false. If true is passed in, users must pass reduceOnly = true as well, + // indicating that when placing a TP/SL order associated with a position, it must be a reduceOnly order. + // Only applicable to Single-currency margin and Multi-currency margin. + cxlOnClosePos *bool `param:"cxlOnClosePos"` + + // Whether the order can only reduce the position size. (optional) + // Valid options: true or false. The default value is false. + reduceOnly *bool `param:"reduceOnly"` + + // Quick Margin type (optional) + // Only applicable to Quick Margin Mode of isolated margin + // manual, auto_borrow, auto_repay + // The default value is manual (Deprecated) + quickMgnType *string `param:"quickMgnType"` + + // Trigger Order Parameters + //TriggerPx *string `json:"triggerPx"` + //TriggerPxType *string `json:"triggerPxType"` + //OrderPx *string `json:"orderPx"` + //AttachAlgoOrds []AttachAlgoOr `json:"attachAlgoOrds"` + + // Trailing Stop Order Parameters + //CallbackRatio *string `json:"callbackRatio"` + + // TWAP Order Parameters + //SzLimit *string `json:"szLimit"` + //PxLimit *string `json:"pxLimit"` + //TimeInterval *string `json:"timeInterval"` + //PxSpread *string `json:"pxSpread"` +} + +func (c *RestClient) NewPlaceOCOAlgoOrderRequest() *PlaceAlgoOrderRequest { + return &PlaceAlgoOrderRequest{ + client: c, + ordType: AlgoOrderTypeOCO, + } +} diff --git a/pkg/exchange/okex/okexapi/place_algo_order_request_requestgen.go b/pkg/exchange/okex/okexapi/place_algo_order_request_requestgen.go new file mode 100644 index 0000000000..661c9ca6b6 --- /dev/null +++ b/pkg/exchange/okex/okexapi/place_algo_order_request_requestgen.go @@ -0,0 +1,449 @@ +// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Data -url /api/v5/trade/order-algo -type PlaceAlgoOrderRequest -responseType .PlaceAlgoOrderResponse"; DO NOT EDIT. + +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (p *PlaceAlgoOrderRequest) InstID(instID string) *PlaceAlgoOrderRequest { + p.instID = instID + return p +} + +func (p *PlaceAlgoOrderRequest) TdMode(tdMode TradeMode) *PlaceAlgoOrderRequest { + p.tdMode = tdMode + return p +} + +func (p *PlaceAlgoOrderRequest) Ccy(ccy string) *PlaceAlgoOrderRequest { + p.ccy = &ccy + return p +} + +func (p *PlaceAlgoOrderRequest) Side(side string) *PlaceAlgoOrderRequest { + p.side = side + return p +} + +func (p *PlaceAlgoOrderRequest) PosSide(posSide string) *PlaceAlgoOrderRequest { + p.posSide = &posSide + return p +} + +func (p *PlaceAlgoOrderRequest) OrdType(ordType AlgoOrderType) *PlaceAlgoOrderRequest { + p.ordType = ordType + return p +} + +func (p *PlaceAlgoOrderRequest) Sz(sz string) *PlaceAlgoOrderRequest { + p.sz = &sz + return p +} + +func (p *PlaceAlgoOrderRequest) Tag(tag string) *PlaceAlgoOrderRequest { + p.tag = &tag + return p +} + +func (p *PlaceAlgoOrderRequest) TgtCcy(tgtCcy string) *PlaceAlgoOrderRequest { + p.tgtCcy = &tgtCcy + return p +} + +func (p *PlaceAlgoOrderRequest) AlgoClOrdId(algoClOrdId string) *PlaceAlgoOrderRequest { + p.algoClOrdId = &algoClOrdId + return p +} + +func (p *PlaceAlgoOrderRequest) CloseFraction(closeFraction string) *PlaceAlgoOrderRequest { + p.closeFraction = &closeFraction + return p +} + +func (p *PlaceAlgoOrderRequest) TpTriggerPx(tpTriggerPx string) *PlaceAlgoOrderRequest { + p.tpTriggerPx = &tpTriggerPx + return p +} + +func (p *PlaceAlgoOrderRequest) TpTriggerPxType(tpTriggerPxType string) *PlaceAlgoOrderRequest { + p.tpTriggerPxType = &tpTriggerPxType + return p +} + +func (p *PlaceAlgoOrderRequest) TpOrdPx(tpOrdPx string) *PlaceAlgoOrderRequest { + p.tpOrdPx = &tpOrdPx + return p +} + +func (p *PlaceAlgoOrderRequest) TpOrdKind(tpOrdKind string) *PlaceAlgoOrderRequest { + p.tpOrdKind = &tpOrdKind + return p +} + +func (p *PlaceAlgoOrderRequest) SlTriggerPx(slTriggerPx string) *PlaceAlgoOrderRequest { + p.slTriggerPx = &slTriggerPx + return p +} + +func (p *PlaceAlgoOrderRequest) SlTriggerPxType(slTriggerPxType string) *PlaceAlgoOrderRequest { + p.slTriggerPxType = &slTriggerPxType + return p +} + +func (p *PlaceAlgoOrderRequest) SlOrdPx(slOrdPx string) *PlaceAlgoOrderRequest { + p.slOrdPx = &slOrdPx + return p +} + +func (p *PlaceAlgoOrderRequest) CxlOnClosePos(cxlOnClosePos bool) *PlaceAlgoOrderRequest { + p.cxlOnClosePos = &cxlOnClosePos + return p +} + +func (p *PlaceAlgoOrderRequest) ReduceOnly(reduceOnly bool) *PlaceAlgoOrderRequest { + p.reduceOnly = &reduceOnly + return p +} + +func (p *PlaceAlgoOrderRequest) QuickMgnType(quickMgnType string) *PlaceAlgoOrderRequest { + p.quickMgnType = &quickMgnType + return p +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (p *PlaceAlgoOrderRequest) 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 *PlaceAlgoOrderRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check instID field -> json key instId + instID := p.instID + + // assign parameter of instID + params["instId"] = instID + // check tdMode field -> json key tdMode + tdMode := p.tdMode + + // TEMPLATE check-valid-values + switch tdMode { + case "cross", "isolated", "cash": + params["tdMode"] = tdMode + + default: + return nil, fmt.Errorf("tdMode value %v is invalid", tdMode) + + } + // END TEMPLATE check-valid-values + + // assign parameter of tdMode + params["tdMode"] = tdMode + // check ccy field -> json key ccy + if p.ccy != nil { + ccy := *p.ccy + + // assign parameter of ccy + params["ccy"] = ccy + } else { + } + // check side field -> json key side + side := p.side + + // TEMPLATE check-valid-values + switch side { + case "buy", "sell": + params["side"] = side + + default: + return nil, fmt.Errorf("side value %v is invalid", side) + + } + // END TEMPLATE check-valid-values + + // assign parameter of side + params["side"] = side + // check posSide field -> json key posSide + if p.posSide != nil { + posSide := *p.posSide + + // assign parameter of posSide + params["posSide"] = posSide + } else { + } + // check ordType field -> json key ordType + ordType := p.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 sz field -> json key sz + if p.sz != nil { + sz := *p.sz + + // assign parameter of sz + params["sz"] = sz + } else { + } + // check tag field -> json key tag + if p.tag != nil { + tag := *p.tag + + // assign parameter of tag + params["tag"] = tag + } else { + } + // check tgtCcy field -> json key tgtCcy + if p.tgtCcy != nil { + tgtCcy := *p.tgtCcy + + // assign parameter of tgtCcy + params["tgtCcy"] = tgtCcy + } else { + } + // check algoClOrdId field -> json key algoClOrdId + if p.algoClOrdId != nil { + algoClOrdId := *p.algoClOrdId + + // assign parameter of algoClOrdId + params["algoClOrdId"] = algoClOrdId + } else { + } + // check closeFraction field -> json key closeFraction + if p.closeFraction != nil { + closeFraction := *p.closeFraction + + // assign parameter of closeFraction + params["closeFraction"] = closeFraction + } else { + } + // check tpTriggerPx field -> json key tpTriggerPx + if p.tpTriggerPx != nil { + tpTriggerPx := *p.tpTriggerPx + + // assign parameter of tpTriggerPx + params["tpTriggerPx"] = tpTriggerPx + } else { + } + // check tpTriggerPxType field -> json key tpTriggerPxType + if p.tpTriggerPxType != nil { + tpTriggerPxType := *p.tpTriggerPxType + + // assign parameter of tpTriggerPxType + params["tpTriggerPxType"] = tpTriggerPxType + } else { + } + // check tpOrdPx field -> json key tpOrdPx + if p.tpOrdPx != nil { + tpOrdPx := *p.tpOrdPx + + // assign parameter of tpOrdPx + params["tpOrdPx"] = tpOrdPx + } else { + } + // check tpOrdKind field -> json key tpOrdKind + if p.tpOrdKind != nil { + tpOrdKind := *p.tpOrdKind + + // assign parameter of tpOrdKind + params["tpOrdKind"] = tpOrdKind + } else { + } + // check slTriggerPx field -> json key slTriggerPx + if p.slTriggerPx != nil { + slTriggerPx := *p.slTriggerPx + + // assign parameter of slTriggerPx + params["slTriggerPx"] = slTriggerPx + } else { + } + // check slTriggerPxType field -> json key slTriggerPxType + if p.slTriggerPxType != nil { + slTriggerPxType := *p.slTriggerPxType + + // assign parameter of slTriggerPxType + params["slTriggerPxType"] = slTriggerPxType + } else { + } + // check slOrdPx field -> json key slOrdPx + if p.slOrdPx != nil { + slOrdPx := *p.slOrdPx + + // assign parameter of slOrdPx + params["slOrdPx"] = slOrdPx + } else { + } + // check cxlOnClosePos field -> json key cxlOnClosePos + if p.cxlOnClosePos != nil { + cxlOnClosePos := *p.cxlOnClosePos + + // assign parameter of cxlOnClosePos + params["cxlOnClosePos"] = cxlOnClosePos + } else { + } + // check reduceOnly field -> json key reduceOnly + if p.reduceOnly != nil { + reduceOnly := *p.reduceOnly + + // assign parameter of reduceOnly + params["reduceOnly"] = reduceOnly + } else { + } + // check quickMgnType field -> json key quickMgnType + if p.quickMgnType != nil { + quickMgnType := *p.quickMgnType + + // assign parameter of quickMgnType + params["quickMgnType"] = quickMgnType + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (p *PlaceAlgoOrderRequest) 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 *PlaceAlgoOrderRequest) 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 *PlaceAlgoOrderRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (p *PlaceAlgoOrderRequest) 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 *PlaceAlgoOrderRequest) 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 *PlaceAlgoOrderRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (p *PlaceAlgoOrderRequest) 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 +} + +// GetPath returns the request path of the API +func (p *PlaceAlgoOrderRequest) GetPath() string { + return "/api/v5/trade/order-algo" +} + +// Do generates the request object and send the request object to the API endpoint +func (p *PlaceAlgoOrderRequest) Do(ctx context.Context) (*PlaceAlgoOrderResponse, error) { + + params, err := p.GetParameters() + if err != nil { + return nil, err + } + query := url.Values{} + + var apiURL string + + apiURL = p.GetPath() + + 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 PlaceAlgoOrderResponse + 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 + } + } + return &apiResponse, nil +} From c8969c1f8fef8bf2f371811fe2ccd7a6a346072c Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Tue, 16 Apr 2024 21:56:50 +0800 Subject: [PATCH 4/8] feat: fixbug for PlaceTakeProfitStopLossOrder sz --- pkg/exchange/okex/exchange.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 705ee609e6..0c87f077bf 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -1086,7 +1086,7 @@ func (e *Exchange) PlaceTakeProfitStopLossOrder(ctx context.Context, position *t orderReq.Side("buy") } - orderReq.Sz(position.Market.FormatQuantity(position.Quote)) + orderReq.Sz(position.Market.FormatQuantity(position.Quote.Abs())) if position.TpTriggerPx != nil { orderReq.TpTriggerPxType("last") From 0dc3c27fca20f4163c27cf87437ff43981a473d2 Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Wed, 17 Apr 2024 07:45:41 +0800 Subject: [PATCH 5/8] feat: update PlaceTakeProfitAndStopLossOrder set CloseFraction --- pkg/exchange/okex/exchange.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 0c87f077bf..7911c3e82a 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -1068,7 +1068,7 @@ func (e *Exchange) UpdatePosition_Bakup(ctx context.Context, position *types.Pos return nil } -func (e *Exchange) PlaceTakeProfitStopLossOrder(ctx context.Context, position *types.Position) error { +func (e *Exchange) PlaceTakeProfitAndStopLossOrder(ctx context.Context, position *types.Position) error { orderReq := e.client.NewPlaceOCOAlgoOrderRequest() // Margin mode cross isolated @@ -1086,7 +1086,9 @@ func (e *Exchange) PlaceTakeProfitStopLossOrder(ctx context.Context, position *t orderReq.Side("buy") } - orderReq.Sz(position.Market.FormatQuantity(position.Quote.Abs())) + orderReq.CloseFraction("1") + orderReq.CxlOnClosePos(true) + orderReq.ReduceOnly(true) if position.TpTriggerPx != nil { orderReq.TpTriggerPxType("last") @@ -1121,5 +1123,5 @@ func (e *Exchange) PlaceTakeProfitStopLossOrder(ctx context.Context, position *t func (e *Exchange) UpdatePosition(ctx context.Context, position *types.Position) error { e.autoCancelAllAlgoOrders(ctx, position.Symbol) - return e.PlaceTakeProfitStopLossOrder(ctx, position) + return e.PlaceTakeProfitAndStopLossOrder(ctx, position) } From 73b8187867e6c32692da6aebc58216752af08f3c Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Thu, 18 Apr 2024 01:52:43 +0800 Subject: [PATCH 6/8] feat: fixbug for place algo --- .../okex/okexapi/place_algo_order_request.go | 2 +- .../okexapi/place_algo_order_request_requestgen.go | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/exchange/okex/okexapi/place_algo_order_request.go b/pkg/exchange/okex/okexapi/place_algo_order_request.go index 4721522abb..377b043785 100644 --- a/pkg/exchange/okex/okexapi/place_algo_order_request.go +++ b/pkg/exchange/okex/okexapi/place_algo_order_request.go @@ -15,7 +15,7 @@ type PlaceAlgoOrderResponse struct { SMsg string `json:"sMsg"` } -//go:generate PostRequest -url "/api/v5/trade/order-algo" -type PlaceAlgoOrderRequest -responseType .PlaceAlgoOrderResponse +//go:generate PostRequest -url "/api/v5/trade/order-algo" -type PlaceAlgoOrderRequest -responseDataType []PlaceAlgoOrderResponse type PlaceAlgoOrderRequest struct { client requestgen.AuthenticatedAPIClient diff --git a/pkg/exchange/okex/okexapi/place_algo_order_request_requestgen.go b/pkg/exchange/okex/okexapi/place_algo_order_request_requestgen.go index 661c9ca6b6..d07c3e0c32 100644 --- a/pkg/exchange/okex/okexapi/place_algo_order_request_requestgen.go +++ b/pkg/exchange/okex/okexapi/place_algo_order_request_requestgen.go @@ -1,4 +1,4 @@ -// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Data -url /api/v5/trade/order-algo -type PlaceAlgoOrderRequest -responseType .PlaceAlgoOrderResponse"; DO NOT EDIT. +// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Data -url /api/v5/trade/order-algo -type PlaceAlgoOrderRequest -responseDataType []PlaceAlgoOrderResponse"; DO NOT EDIT. package okexapi @@ -409,7 +409,7 @@ func (p *PlaceAlgoOrderRequest) GetPath() string { } // Do generates the request object and send the request object to the API endpoint -func (p *PlaceAlgoOrderRequest) Do(ctx context.Context) (*PlaceAlgoOrderResponse, error) { +func (p *PlaceAlgoOrderRequest) Do(ctx context.Context) ([]PlaceAlgoOrderResponse, error) { params, err := p.GetParameters() if err != nil { @@ -431,7 +431,7 @@ func (p *PlaceAlgoOrderRequest) Do(ctx context.Context) (*PlaceAlgoOrderResponse return nil, err } - var apiResponse PlaceAlgoOrderResponse + var apiResponse APIResponse if err := response.DecodeJSON(&apiResponse); err != nil { return nil, err } @@ -445,5 +445,9 @@ func (p *PlaceAlgoOrderRequest) Do(ctx context.Context) (*PlaceAlgoOrderResponse return nil, err } } - return &apiResponse, nil + var data []PlaceAlgoOrderResponse + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil } From dec170d40ffebd5a1c76aaf6cab131eaf8ce4a31 Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Thu, 18 Apr 2024 06:51:23 +0800 Subject: [PATCH 7/8] feat: fixbug for place stop loss order --- pkg/exchange/okex/exchange.go | 10 +- .../okex/okexapi/amend_algo_order_request.go | 13 +- .../amend_algo_order_request_requestgen.go | 168 +++++++++--------- 3 files changed, 93 insertions(+), 98 deletions(-) diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 7911c3e82a..3f2abfeedf 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -1071,6 +1071,8 @@ func (e *Exchange) UpdatePosition_Bakup(ctx context.Context, position *types.Pos func (e *Exchange) PlaceTakeProfitAndStopLossOrder(ctx context.Context, position *types.Position) error { orderReq := e.client.NewPlaceOCOAlgoOrderRequest() + orderReq.InstID(toLocalSymbol(position.Symbol)) + // Margin mode cross isolated if e.IsIsolatedMargin { orderReq.TdMode(okexapi.TradeModeIsolated) @@ -1078,18 +1080,14 @@ func (e *Exchange) PlaceTakeProfitAndStopLossOrder(ctx context.Context, position orderReq.TdMode(okexapi.TradeModeCross) } - orderReq.InstID(toLocalSymbol(position.Symbol)) - if position.IsLong() { orderReq.Side("sell") + orderReq.Sz(position.Market.FormatQuantity(position.Quote.Abs())) } else if position.IsShort() { orderReq.Side("buy") + orderReq.Sz(position.Market.FormatQuantity(position.Base.Abs())) } - orderReq.CloseFraction("1") - orderReq.CxlOnClosePos(true) - orderReq.ReduceOnly(true) - if position.TpTriggerPx != nil { orderReq.TpTriggerPxType("last") orderReq.TpTriggerPx(position.Market.FormatPrice(*position.TpTriggerPx)) diff --git a/pkg/exchange/okex/okexapi/amend_algo_order_request.go b/pkg/exchange/okex/okexapi/amend_algo_order_request.go index 194f688e18..df979000b0 100644 --- a/pkg/exchange/okex/okexapi/amend_algo_order_request.go +++ b/pkg/exchange/okex/okexapi/amend_algo_order_request.go @@ -4,6 +4,8 @@ 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 AmendAlgoOrder struct { AlgoID string `json:"algoId"` AlgoClOrdID string `json:"algoClOrdId"` @@ -12,11 +14,7 @@ type AmendAlgoOrder struct { SMsg string `json:"sMsg"` } -//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/amend-algos" -type AmendAlgoOrderRequest -responseType .AmendAlgoOrderResponse - +//go:generate PostRequest -url "/api/v5/trade/amend-algos" -type AmendAlgoOrderRequest -responseDataType []AmendAlgoOrder type AmendAlgoOrderRequest struct { client requestgen.AuthenticatedAPIClient @@ -72,11 +70,6 @@ type AmendAlgoOrderRequest struct { newSlTriggerPxType *string `param:"newSlTriggerPxType"` } -type AmendAlgoOrderResponse struct { - APIResponse - Data []AmendAlgoOrder `json:"data"` -} - func (c *RestClient) NewAmendAlgoOrderRequest() *AmendAlgoOrderRequest { return &AmendAlgoOrderRequest{ client: c, diff --git a/pkg/exchange/okex/okexapi/amend_algo_order_request_requestgen.go b/pkg/exchange/okex/okexapi/amend_algo_order_request_requestgen.go index 0397d47930..4621644443 100644 --- a/pkg/exchange/okex/okexapi/amend_algo_order_request_requestgen.go +++ b/pkg/exchange/okex/okexapi/amend_algo_order_request_requestgen.go @@ -1,4 +1,4 @@ -// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Data -url /api/v5/trade/amend-algos -type AmendAlgoOrderRequest -responseType .AmendAlgoOrderResponse"; DO NOT EDIT. +// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Data -url /api/v5/trade/amend-algos -type AmendAlgoOrderRequest -responseDataType []AmendAlgoOrder"; DO NOT EDIT. package okexapi @@ -11,68 +11,68 @@ import ( "regexp" ) -func (r *AmendAlgoOrderRequest) InstID(instID string) *AmendAlgoOrderRequest { - r.instID = instID - return r +func (a *AmendAlgoOrderRequest) InstID(instID string) *AmendAlgoOrderRequest { + a.instID = instID + return a } -func (r *AmendAlgoOrderRequest) AlgoID(algoID string) *AmendAlgoOrderRequest { - r.algoID = &algoID - return r +func (a *AmendAlgoOrderRequest) AlgoID(algoID string) *AmendAlgoOrderRequest { + a.algoID = &algoID + return a } -func (r *AmendAlgoOrderRequest) AlgoClOrdID(algoClOrdID string) *AmendAlgoOrderRequest { - r.algoClOrdID = &algoClOrdID - return r +func (a *AmendAlgoOrderRequest) AlgoClOrdID(algoClOrdID string) *AmendAlgoOrderRequest { + a.algoClOrdID = &algoClOrdID + return a } -func (r *AmendAlgoOrderRequest) CxlOnFail(cxlOnFail bool) *AmendAlgoOrderRequest { - r.cxlOnFail = &cxlOnFail - return r +func (a *AmendAlgoOrderRequest) CxlOnFail(cxlOnFail bool) *AmendAlgoOrderRequest { + a.cxlOnFail = &cxlOnFail + return a } -func (r *AmendAlgoOrderRequest) ReqID(reqID string) *AmendAlgoOrderRequest { - r.reqID = &reqID - return r +func (a *AmendAlgoOrderRequest) ReqID(reqID string) *AmendAlgoOrderRequest { + a.reqID = &reqID + return a } -func (r *AmendAlgoOrderRequest) NewSz(newSz string) *AmendAlgoOrderRequest { - r.newSz = &newSz - return r +func (a *AmendAlgoOrderRequest) NewSz(newSz string) *AmendAlgoOrderRequest { + a.newSz = &newSz + return a } -func (r *AmendAlgoOrderRequest) NewTpTriggerPx(newTpTriggerPx string) *AmendAlgoOrderRequest { - r.newTpTriggerPx = &newTpTriggerPx - return r +func (a *AmendAlgoOrderRequest) NewTpTriggerPx(newTpTriggerPx string) *AmendAlgoOrderRequest { + a.newTpTriggerPx = &newTpTriggerPx + return a } -func (r *AmendAlgoOrderRequest) NewTpOrdPx(newTpOrdPx string) *AmendAlgoOrderRequest { - r.newTpOrdPx = &newTpOrdPx - return r +func (a *AmendAlgoOrderRequest) NewTpOrdPx(newTpOrdPx string) *AmendAlgoOrderRequest { + a.newTpOrdPx = &newTpOrdPx + return a } -func (r *AmendAlgoOrderRequest) NewSlTriggerPx(newSlTriggerPx string) *AmendAlgoOrderRequest { - r.newSlTriggerPx = &newSlTriggerPx - return r +func (a *AmendAlgoOrderRequest) NewSlTriggerPx(newSlTriggerPx string) *AmendAlgoOrderRequest { + a.newSlTriggerPx = &newSlTriggerPx + return a } -func (r *AmendAlgoOrderRequest) NewSlOrdPx(newSlOrdPx string) *AmendAlgoOrderRequest { - r.newSlOrdPx = &newSlOrdPx - return r +func (a *AmendAlgoOrderRequest) NewSlOrdPx(newSlOrdPx string) *AmendAlgoOrderRequest { + a.newSlOrdPx = &newSlOrdPx + return a } -func (r *AmendAlgoOrderRequest) NewTpTriggerPxType(newTpTriggerPxType string) *AmendAlgoOrderRequest { - r.newTpTriggerPxType = &newTpTriggerPxType - return r +func (a *AmendAlgoOrderRequest) NewTpTriggerPxType(newTpTriggerPxType string) *AmendAlgoOrderRequest { + a.newTpTriggerPxType = &newTpTriggerPxType + return a } -func (r *AmendAlgoOrderRequest) NewSlTriggerPxType(newSlTriggerPxType string) *AmendAlgoOrderRequest { - r.newSlTriggerPxType = &newSlTriggerPxType - return r +func (a *AmendAlgoOrderRequest) NewSlTriggerPxType(newSlTriggerPxType string) *AmendAlgoOrderRequest { + a.newSlTriggerPxType = &newSlTriggerPxType + return a } // GetQueryParameters builds and checks the query parameters and returns url.Values -func (r *AmendAlgoOrderRequest) GetQueryParameters() (url.Values, error) { +func (a *AmendAlgoOrderRequest) GetQueryParameters() (url.Values, error) { var params = map[string]interface{}{} query := url.Values{} @@ -84,96 +84,96 @@ func (r *AmendAlgoOrderRequest) GetQueryParameters() (url.Values, error) { } // GetParameters builds and checks the parameters and return the result in a map object -func (r *AmendAlgoOrderRequest) GetParameters() (map[string]interface{}, error) { +func (a *AmendAlgoOrderRequest) GetParameters() (map[string]interface{}, error) { var params = map[string]interface{}{} // check instID field -> json key instId - instID := r.instID + instID := a.instID // assign parameter of instID params["instId"] = instID // check algoID field -> json key algoId - if r.algoID != nil { - algoID := *r.algoID + if a.algoID != nil { + algoID := *a.algoID // assign parameter of algoID params["algoId"] = algoID } else { } // check algoClOrdID field -> json key algoClOrdId - if r.algoClOrdID != nil { - algoClOrdID := *r.algoClOrdID + if a.algoClOrdID != nil { + algoClOrdID := *a.algoClOrdID // assign parameter of algoClOrdID params["algoClOrdId"] = algoClOrdID } else { } // check cxlOnFail field -> json key cxlOnFail - if r.cxlOnFail != nil { - cxlOnFail := *r.cxlOnFail + if a.cxlOnFail != nil { + cxlOnFail := *a.cxlOnFail // assign parameter of cxlOnFail params["cxlOnFail"] = cxlOnFail } else { } // check reqID field -> json key reqId - if r.reqID != nil { - reqID := *r.reqID + if a.reqID != nil { + reqID := *a.reqID // assign parameter of reqID params["reqId"] = reqID } else { } // check newSz field -> json key newSz - if r.newSz != nil { - newSz := *r.newSz + if a.newSz != nil { + newSz := *a.newSz // assign parameter of newSz params["newSz"] = newSz } else { } // check newTpTriggerPx field -> json key newTpTriggerPx - if r.newTpTriggerPx != nil { - newTpTriggerPx := *r.newTpTriggerPx + if a.newTpTriggerPx != nil { + newTpTriggerPx := *a.newTpTriggerPx // assign parameter of newTpTriggerPx params["newTpTriggerPx"] = newTpTriggerPx } else { } // check newTpOrdPx field -> json key newTpOrdPx - if r.newTpOrdPx != nil { - newTpOrdPx := *r.newTpOrdPx + if a.newTpOrdPx != nil { + newTpOrdPx := *a.newTpOrdPx // assign parameter of newTpOrdPx params["newTpOrdPx"] = newTpOrdPx } else { } // check newSlTriggerPx field -> json key newSlTriggerPx - if r.newSlTriggerPx != nil { - newSlTriggerPx := *r.newSlTriggerPx + if a.newSlTriggerPx != nil { + newSlTriggerPx := *a.newSlTriggerPx // assign parameter of newSlTriggerPx params["newSlTriggerPx"] = newSlTriggerPx } else { } // check newSlOrdPx field -> json key newSlOrdPx - if r.newSlOrdPx != nil { - newSlOrdPx := *r.newSlOrdPx + if a.newSlOrdPx != nil { + newSlOrdPx := *a.newSlOrdPx // assign parameter of newSlOrdPx params["newSlOrdPx"] = newSlOrdPx } else { } // check newTpTriggerPxType field -> json key newTpTriggerPxType - if r.newTpTriggerPxType != nil { - newTpTriggerPxType := *r.newTpTriggerPxType + if a.newTpTriggerPxType != nil { + newTpTriggerPxType := *a.newTpTriggerPxType // assign parameter of newTpTriggerPxType params["newTpTriggerPxType"] = newTpTriggerPxType } else { } // check newSlTriggerPxType field -> json key newSlTriggerPxType - if r.newSlTriggerPxType != nil { - newSlTriggerPxType := *r.newSlTriggerPxType + if a.newSlTriggerPxType != nil { + newSlTriggerPxType := *a.newSlTriggerPxType // assign parameter of newSlTriggerPxType params["newSlTriggerPxType"] = newSlTriggerPxType @@ -184,17 +184,17 @@ func (r *AmendAlgoOrderRequest) GetParameters() (map[string]interface{}, error) } // GetParametersQuery converts the parameters from GetParameters into the url.Values format -func (r *AmendAlgoOrderRequest) GetParametersQuery() (url.Values, error) { +func (a *AmendAlgoOrderRequest) GetParametersQuery() (url.Values, error) { query := url.Values{} - params, err := r.GetParameters() + params, err := a.GetParameters() if err != nil { return query, err } for _k, _v := range params { - if r.isVarSlice(_v) { - r.iterateSlice(_v, func(it interface{}) { + if a.isVarSlice(_v) { + a.iterateSlice(_v, func(it interface{}) { query.Add(_k+"[]", fmt.Sprintf("%v", it)) }) } else { @@ -206,8 +206,8 @@ func (r *AmendAlgoOrderRequest) GetParametersQuery() (url.Values, error) { } // GetParametersJSON converts the parameters from GetParameters into the JSON format -func (r *AmendAlgoOrderRequest) GetParametersJSON() ([]byte, error) { - params, err := r.GetParameters() +func (a *AmendAlgoOrderRequest) GetParametersJSON() ([]byte, error) { + params, err := a.GetParameters() if err != nil { return nil, err } @@ -216,13 +216,13 @@ func (r *AmendAlgoOrderRequest) GetParametersJSON() ([]byte, error) { } // GetSlugParameters builds and checks the slug parameters and return the result in a map object -func (r *AmendAlgoOrderRequest) GetSlugParameters() (map[string]interface{}, error) { +func (a *AmendAlgoOrderRequest) GetSlugParameters() (map[string]interface{}, error) { var params = map[string]interface{}{} return params, nil } -func (r *AmendAlgoOrderRequest) applySlugsToUrl(url string, slugs map[string]string) string { +func (a *AmendAlgoOrderRequest) applySlugsToUrl(url string, slugs map[string]string) string { for _k, _v := range slugs { needleRE := regexp.MustCompile(":" + _k + "\\b") url = needleRE.ReplaceAllString(url, _v) @@ -231,7 +231,7 @@ func (r *AmendAlgoOrderRequest) applySlugsToUrl(url string, slugs map[string]str return url } -func (r *AmendAlgoOrderRequest) iterateSlice(slice interface{}, _f func(it interface{})) { +func (a *AmendAlgoOrderRequest) iterateSlice(slice interface{}, _f func(it interface{})) { sliceValue := reflect.ValueOf(slice) for _i := 0; _i < sliceValue.Len(); _i++ { it := sliceValue.Index(_i).Interface() @@ -239,7 +239,7 @@ func (r *AmendAlgoOrderRequest) iterateSlice(slice interface{}, _f func(it inter } } -func (r *AmendAlgoOrderRequest) isVarSlice(_v interface{}) bool { +func (a *AmendAlgoOrderRequest) isVarSlice(_v interface{}) bool { rt := reflect.TypeOf(_v) switch rt.Kind() { case reflect.Slice: @@ -248,9 +248,9 @@ func (r *AmendAlgoOrderRequest) isVarSlice(_v interface{}) bool { return false } -func (r *AmendAlgoOrderRequest) GetSlugsMap() (map[string]string, error) { +func (a *AmendAlgoOrderRequest) GetSlugsMap() (map[string]string, error) { slugs := map[string]string{} - params, err := r.GetSlugParameters() + params, err := a.GetSlugParameters() if err != nil { return slugs, nil } @@ -263,14 +263,14 @@ func (r *AmendAlgoOrderRequest) GetSlugsMap() (map[string]string, error) { } // GetPath returns the request path of the API -func (r *AmendAlgoOrderRequest) GetPath() string { +func (a *AmendAlgoOrderRequest) GetPath() string { return "/api/v5/trade/amend-algos" } // Do generates the request object and send the request object to the API endpoint -func (r *AmendAlgoOrderRequest) Do(ctx context.Context) (*AmendAlgoOrderResponse, error) { +func (a *AmendAlgoOrderRequest) Do(ctx context.Context) ([]AmendAlgoOrder, error) { - params, err := r.GetParameters() + params, err := a.GetParameters() if err != nil { return nil, err } @@ -278,19 +278,19 @@ func (r *AmendAlgoOrderRequest) Do(ctx context.Context) (*AmendAlgoOrderResponse var apiURL string - apiURL = r.GetPath() + apiURL = a.GetPath() - req, err := r.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params) + req, err := a.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params) if err != nil { return nil, err } - response, err := r.client.SendRequest(req) + response, err := a.client.SendRequest(req) if err != nil { return nil, err } - var apiResponse AmendAlgoOrderResponse + var apiResponse APIResponse if err := response.DecodeJSON(&apiResponse); err != nil { return nil, err } @@ -304,5 +304,9 @@ func (r *AmendAlgoOrderRequest) Do(ctx context.Context) (*AmendAlgoOrderResponse return nil, err } } - return &apiResponse, nil + var data []AmendAlgoOrder + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil } From a416ed2b13f919effefe2e325fc3c7d52c7ef126 Mon Sep 17 00:00:00 2001 From: Owen Wu Date: Thu, 18 Apr 2024 21:10:56 +0800 Subject: [PATCH 8/8] feat: fixbug for place stoploss order ccy --- pkg/exchange/okex/exchange.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 3f2abfeedf..5e0d256e3f 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -1080,6 +1080,8 @@ func (e *Exchange) PlaceTakeProfitAndStopLossOrder(ctx context.Context, position orderReq.TdMode(okexapi.TradeModeCross) } + orderReq.Ccy(position.Market.QuoteCurrency) + if position.IsLong() { orderReq.Side("sell") orderReq.Sz(position.Market.FormatQuantity(position.Quote.Abs()))