diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 55ede73882..5e0d256e3f 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 ( @@ -325,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) > 0 { - allOrders = append(allOrders, orders...) - } + if len(orders)+len(ocoOrders) > 0 { + allOrders := make([]types.Order, 0) - 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) } @@ -420,11 +427,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) } @@ -558,9 +572,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, }) @@ -1002,3 +1018,110 @@ func (e *Exchange) IsSupportedInterval(interval types.Interval) bool { _, ok := SupportedIntervals[interval] return ok } + +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") + } + + 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) + req.NewSz(position.Market.FormatQuantity(order.Quantity)) + + 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). + WithError(err). + Error("AmendAlgoOrder_fail") + return err + } + + log.WithField("resp", resp). + Info("AmendAlgoOrder_ok") + + return nil + } + + return nil +} + +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) + } else { + orderReq.TdMode(okexapi.TradeModeCross) + } + + orderReq.Ccy(position.Market.QuoteCurrency) + + 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())) + } + + 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.PlaceTakeProfitAndStopLossOrder(ctx, position) +} 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..df979000b0 --- /dev/null +++ b/pkg/exchange/okex/okexapi/amend_algo_order_request.go @@ -0,0 +1,77 @@ +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 AmendAlgoOrder struct { + AlgoID string `json:"algoId"` + AlgoClOrdID string `json:"algoClOrdId"` + ReqID string `json:"reqId"` + SCode string `json:"sCode"` + SMsg string `json:"sMsg"` +} + +//go:generate PostRequest -url "/api/v5/trade/amend-algos" -type AmendAlgoOrderRequest -responseDataType []AmendAlgoOrder +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"` +} + +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..4621644443 --- /dev/null +++ b/pkg/exchange/okex/okexapi/amend_algo_order_request_requestgen.go @@ -0,0 +1,312 @@ +// 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 + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (a *AmendAlgoOrderRequest) InstID(instID string) *AmendAlgoOrderRequest { + a.instID = instID + return a +} + +func (a *AmendAlgoOrderRequest) AlgoID(algoID string) *AmendAlgoOrderRequest { + a.algoID = &algoID + return a +} + +func (a *AmendAlgoOrderRequest) AlgoClOrdID(algoClOrdID string) *AmendAlgoOrderRequest { + a.algoClOrdID = &algoClOrdID + return a +} + +func (a *AmendAlgoOrderRequest) CxlOnFail(cxlOnFail bool) *AmendAlgoOrderRequest { + a.cxlOnFail = &cxlOnFail + return a +} + +func (a *AmendAlgoOrderRequest) ReqID(reqID string) *AmendAlgoOrderRequest { + a.reqID = &reqID + return a +} + +func (a *AmendAlgoOrderRequest) NewSz(newSz string) *AmendAlgoOrderRequest { + a.newSz = &newSz + return a +} + +func (a *AmendAlgoOrderRequest) NewTpTriggerPx(newTpTriggerPx string) *AmendAlgoOrderRequest { + a.newTpTriggerPx = &newTpTriggerPx + return a +} + +func (a *AmendAlgoOrderRequest) NewTpOrdPx(newTpOrdPx string) *AmendAlgoOrderRequest { + a.newTpOrdPx = &newTpOrdPx + return a +} + +func (a *AmendAlgoOrderRequest) NewSlTriggerPx(newSlTriggerPx string) *AmendAlgoOrderRequest { + a.newSlTriggerPx = &newSlTriggerPx + return a +} + +func (a *AmendAlgoOrderRequest) NewSlOrdPx(newSlOrdPx string) *AmendAlgoOrderRequest { + a.newSlOrdPx = &newSlOrdPx + return a +} + +func (a *AmendAlgoOrderRequest) NewTpTriggerPxType(newTpTriggerPxType string) *AmendAlgoOrderRequest { + a.newTpTriggerPxType = &newTpTriggerPxType + return a +} + +func (a *AmendAlgoOrderRequest) NewSlTriggerPxType(newSlTriggerPxType string) *AmendAlgoOrderRequest { + a.newSlTriggerPxType = &newSlTriggerPxType + return a +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (a *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 (a *AmendAlgoOrderRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check instID field -> json key instId + instID := a.instID + + // assign parameter of instID + params["instId"] = instID + // check algoID field -> json key algoId + if a.algoID != nil { + algoID := *a.algoID + + // assign parameter of algoID + params["algoId"] = algoID + } else { + } + // check algoClOrdID field -> json key algoClOrdId + if a.algoClOrdID != nil { + algoClOrdID := *a.algoClOrdID + + // assign parameter of algoClOrdID + params["algoClOrdId"] = algoClOrdID + } else { + } + // check cxlOnFail field -> json key cxlOnFail + if a.cxlOnFail != nil { + cxlOnFail := *a.cxlOnFail + + // assign parameter of cxlOnFail + params["cxlOnFail"] = cxlOnFail + } else { + } + // check reqID field -> json key reqId + if a.reqID != nil { + reqID := *a.reqID + + // assign parameter of reqID + params["reqId"] = reqID + } else { + } + // check newSz field -> json key newSz + if a.newSz != nil { + newSz := *a.newSz + + // assign parameter of newSz + params["newSz"] = newSz + } else { + } + // check newTpTriggerPx field -> json key newTpTriggerPx + if a.newTpTriggerPx != nil { + newTpTriggerPx := *a.newTpTriggerPx + + // assign parameter of newTpTriggerPx + params["newTpTriggerPx"] = newTpTriggerPx + } else { + } + // check newTpOrdPx field -> json key newTpOrdPx + if a.newTpOrdPx != nil { + newTpOrdPx := *a.newTpOrdPx + + // assign parameter of newTpOrdPx + params["newTpOrdPx"] = newTpOrdPx + } else { + } + // check newSlTriggerPx field -> json key newSlTriggerPx + if a.newSlTriggerPx != nil { + newSlTriggerPx := *a.newSlTriggerPx + + // assign parameter of newSlTriggerPx + params["newSlTriggerPx"] = newSlTriggerPx + } else { + } + // check newSlOrdPx field -> json key newSlOrdPx + if a.newSlOrdPx != nil { + newSlOrdPx := *a.newSlOrdPx + + // assign parameter of newSlOrdPx + params["newSlOrdPx"] = newSlOrdPx + } else { + } + // check newTpTriggerPxType field -> json key newTpTriggerPxType + if a.newTpTriggerPxType != nil { + newTpTriggerPxType := *a.newTpTriggerPxType + + // assign parameter of newTpTriggerPxType + params["newTpTriggerPxType"] = newTpTriggerPxType + } else { + } + // check newSlTriggerPxType field -> json key newSlTriggerPxType + if a.newSlTriggerPxType != nil { + newSlTriggerPxType := *a.newSlTriggerPxType + + // assign parameter of newSlTriggerPxType + params["newSlTriggerPxType"] = newSlTriggerPxType + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (a *AmendAlgoOrderRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := a.GetParameters() + if err != nil { + return query, err + } + + for _k, _v := range params { + if a.isVarSlice(_v) { + a.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 (a *AmendAlgoOrderRequest) GetParametersJSON() ([]byte, error) { + params, err := a.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 (a *AmendAlgoOrderRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +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) + } + + return url +} + +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() + _f(it) + } +} + +func (a *AmendAlgoOrderRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (a *AmendAlgoOrderRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := a.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 (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 (a *AmendAlgoOrderRequest) Do(ctx context.Context) ([]AmendAlgoOrder, error) { + + params, err := a.GetParameters() + if err != nil { + return nil, err + } + query := url.Values{} + + var apiURL string + + apiURL = a.GetPath() + + req, err := a.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := a.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 []AmendAlgoOrder + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} 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..377b043785 --- /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 -responseDataType []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..d07c3e0c32 --- /dev/null +++ b/pkg/exchange/okex/okexapi/place_algo_order_request_requestgen.go @@ -0,0 +1,453 @@ +// 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 + +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 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 []PlaceAlgoOrderResponse + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, 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) {