diff --git a/pkg/exchange/okex/convert.go b/pkg/exchange/okex/convert.go index 0551ef684a..126370893b 100644 --- a/pkg/exchange/okex/convert.go +++ b/pkg/exchange/okex/convert.go @@ -149,6 +149,49 @@ func toGlobalTrades(orderDetails []okexapi.OrderDetails) ([]types.Trade, error) return trades, nil } +func openOrderToGlobal(order *okexapi.OpenOrder) (*types.Order, error) { + side := toGlobalSide(order.Side) + + orderType, err := toGlobalOrderType(order.OrderType) + if err != nil { + return nil, err + } + + timeInForce := types.TimeInForceGTC + switch order.OrderType { + case okexapi.OrderTypeFOK: + timeInForce = types.TimeInForceFOK + case okexapi.OrderTypeIOC: + timeInForce = types.TimeInForceIOC + } + + orderStatus, err := toGlobalOrderStatus(order.State) + if err != nil { + return nil, err + } + + return &types.Order{ + SubmitOrder: types.SubmitOrder{ + ClientOrderID: order.ClientOrderId, + Symbol: toGlobalSymbol(order.InstrumentID), + Side: side, + Type: orderType, + Price: order.Price, + Quantity: order.Size, + TimeInForce: timeInForce, + }, + Exchange: types.ExchangeOKEx, + OrderID: uint64(order.OrderId), + UUID: strconv.FormatInt(int64(order.OrderId), 10), + Status: orderStatus, + OriginalStatus: string(order.State), + ExecutedQuantity: order.AccumulatedFillSize, + IsWorking: order.State.IsWorking(), + CreationTime: types.Time(order.CreatedTime), + UpdateTime: types.Time(order.UpdatedTime), + }, nil +} + func toGlobalOrders(orderDetails []okexapi.OrderDetails) ([]types.Order, error) { var orders []types.Order var err error diff --git a/pkg/exchange/okex/convert_test.go b/pkg/exchange/okex/convert_test.go new file mode 100644 index 0000000000..42a44f8c68 --- /dev/null +++ b/pkg/exchange/okex/convert_test.go @@ -0,0 +1,84 @@ +package okex + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/exchange/okex/okexapi" + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) + +func Test_openOrderToGlobal(t *testing.T) { + var ( + assert = assert.New(t) + + orderId = 665576973905014786 + // {"accFillSz":"0","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"","cTime":"1704957916401","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"0","feeCcy":"USDT","fillPx":"","fillSz":"0","fillTime":"","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576973905014786","ordType":"limit","pnl":"0","posSide":"net","px":"48174.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"live","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"","uTime":"1704957916401"} + openOrder = &okexapi.OpenOrder{ + AccumulatedFillSize: fixedpoint.NewFromFloat(0), + AvgPrice: fixedpoint.NewFromFloat(0), + CreatedTime: types.NewMillisecondTimestampFromInt(1704957916401), + Category: "normal", + Currency: "BTC", + ClientOrderId: "", + Fee: fixedpoint.Zero, + FeeCurrency: "USDT", + FillTime: types.NewMillisecondTimestampFromInt(0), + InstrumentID: "BTC-USDT", + InstrumentType: okexapi.InstrumentTypeSpot, + OrderId: types.StrInt64(orderId), + OrderType: okexapi.OrderTypeLimit, + Price: fixedpoint.NewFromFloat(48174.5), + Side: okexapi.SideTypeBuy, + State: okexapi.OrderStateLive, + Size: fixedpoint.NewFromFloat(0.00001), + UpdatedTime: types.NewMillisecondTimestampFromInt(1704957916401), + } + expOrder = &types.Order{ + SubmitOrder: types.SubmitOrder{ + ClientOrderID: openOrder.ClientOrderId, + Symbol: toGlobalSymbol(openOrder.InstrumentID), + Side: types.SideTypeBuy, + Type: types.OrderTypeLimit, + Quantity: fixedpoint.NewFromFloat(0.00001), + Price: fixedpoint.NewFromFloat(48174.5), + AveragePrice: fixedpoint.Zero, + StopPrice: fixedpoint.Zero, + TimeInForce: types.TimeInForceGTC, + }, + Exchange: types.ExchangeOKEx, + OrderID: uint64(orderId), + UUID: fmt.Sprintf("%d", orderId), + Status: types.OrderStatusNew, + OriginalStatus: string(okexapi.OrderStateLive), + ExecutedQuantity: fixedpoint.Zero, + IsWorking: true, + CreationTime: types.Time(types.NewMillisecondTimestampFromInt(1704957916401).Time()), + UpdateTime: types.Time(types.NewMillisecondTimestampFromInt(1704957916401).Time()), + } + ) + + t.Run("succeeds", func(t *testing.T) { + order, err := openOrderToGlobal(openOrder) + assert.NoError(err) + assert.Equal(expOrder, order) + }) + + t.Run("unexpected order status", func(t *testing.T) { + newOrder := *openOrder + newOrder.State = "xxx" + _, err := openOrderToGlobal(&newOrder) + assert.ErrorContains(err, "xxx") + }) + + t.Run("unexpected order type", func(t *testing.T) { + newOrder := *openOrder + newOrder.OrderType = "xxx" + _, err := openOrderToGlobal(&newOrder) + assert.ErrorContains(err, "xxx") + }) + +} diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 0dcb7955c2..4b6381a585 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -30,15 +30,15 @@ var ( queryAccountLimiter = rate.NewLimiter(rate.Every(200*time.Millisecond), 5) placeOrderLimiter = rate.NewLimiter(rate.Every(30*time.Millisecond), 30) batchCancelOrderLimiter = rate.NewLimiter(rate.Every(5*time.Millisecond), 200) + queryOpenOrderLimiter = rate.NewLimiter(rate.Every(30*time.Millisecond), 30) ) -const ID = "okex" +const ( + ID = "okex" -// PlatformToken is the platform currency of OKEx, pre-allocate static string here -const PlatformToken = "OKB" + // PlatformToken is the platform currency of OKEx, pre-allocate static string here + PlatformToken = "OKB" -const ( - // Constant For query limit defaultQueryLimit = 100 ) @@ -295,15 +295,46 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*t */ } +// QueryOpenOrders retrieves the pending orders. The data returned is ordered by createdTime, and we utilized the +// `After` parameter to acquire all orders. func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) { instrumentID := toLocalSymbol(symbol) - req := e.client.NewGetPendingOrderRequest().InstrumentType(okexapi.InstrumentTypeSpot).InstrumentID(instrumentID) - orderDetails, err := req.Do(ctx) - if err != nil { - return orders, err + + nextCursor := int64(0) + for { + if err := queryOpenOrderLimiter.Wait(ctx); err != nil { + return nil, fmt.Errorf("query open orders rate limiter wait error: %w", err) + } + + req := e.client.NewGetOpenOrdersRequest(). + InstrumentID(instrumentID). + After(strconv.FormatInt(nextCursor, 10)) + openOrders, err := req.Do(ctx) + if err != nil { + return nil, fmt.Errorf("failed to query open orders: %w", err) + } + + for _, o := range openOrders { + o, err := openOrderToGlobal(&o) + if err != nil { + return nil, fmt.Errorf("failed to convert order, err: %v", err) + } + + orders = append(orders, *o) + } + + orderLen := len(openOrders) + // a defensive programming to ensure the length of order response is expected. + if orderLen > defaultQueryLimit { + return nil, fmt.Errorf("unexpected open orders length %d", orderLen) + } + + if orderLen < defaultQueryLimit { + break + } + nextCursor = int64(openOrders[orderLen-1].OrderId) } - orders, err = toGlobalOrders(orderDetails) return orders, err } diff --git a/pkg/exchange/okex/okexapi/client.go b/pkg/exchange/okex/okexapi/client.go index 85b00b8072..425339b0f6 100644 --- a/pkg/exchange/okex/okexapi/client.go +++ b/pkg/exchange/okex/okexapi/client.go @@ -58,6 +58,10 @@ const ( OrderStateFilled OrderState = "filled" ) +func (o OrderState) IsWorking() bool { + return o == OrderStateLive || o == OrderStatePartiallyFilled +} + type RestClient struct { requestgen.BaseAPIClient diff --git a/pkg/exchange/okex/okexapi/client_test.go b/pkg/exchange/okex/okexapi/client_test.go index bcc0150272..71a75f87c4 100644 --- a/pkg/exchange/okex/okexapi/client_test.go +++ b/pkg/exchange/okex/okexapi/client_test.go @@ -140,6 +140,26 @@ func TestClient_CancelOrderRequest(t *testing.T) { t.Log(cancelResp) } +func TestClient_OpenOrdersRequest(t *testing.T) { + client := getTestClientOrSkip(t) + ctx := context.Background() + + orders := []OpenOrder{} + beforeId := int64(0) + for { + c := client.NewGetOpenOrdersRequest().InstrumentID("BTC-USDT").Limit("1").After(fmt.Sprintf("%d", beforeId)) + res, err := c.Do(ctx) + assert.NoError(t, err) + if len(res) != 1 { + break + } + orders = append(orders, res...) + beforeId = int64(res[0].OrderId) + } + + t.Log(orders) +} + func TestClient_BatchCancelOrderRequest(t *testing.T) { client := getTestClientOrSkip(t) ctx := context.Background() @@ -170,21 +190,6 @@ func TestClient_BatchCancelOrderRequest(t *testing.T) { t.Log(cancelResp) } -func TestClient_GetPendingOrderRequest(t *testing.T) { - client := getTestClientOrSkip(t) - ctx := context.Background() - req := client.NewGetPendingOrderRequest() - odr_type := []string{string(OrderTypeLimit), string(OrderTypeIOC)} - - pending_order, err := req. - InstrumentID("BTC-USDT"). - OrderTypes(odr_type). - Do(ctx) - assert.NoError(t, err) - assert.NotEmpty(t, pending_order) - t.Logf("pending order: %+v", pending_order) -} - func TestClient_GetOrderDetailsRequest(t *testing.T) { client := getTestClientOrSkip(t) ctx := context.Background() diff --git a/pkg/exchange/okex/okexapi/get_open_orders_request.go b/pkg/exchange/okex/okexapi/get_open_orders_request.go new file mode 100644 index 0000000000..70995c1b38 --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_open_orders_request.go @@ -0,0 +1,112 @@ +package okexapi + +import ( + "time" + + "github.com/c9s/requestgen" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) + +//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data +//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data + +type OpenOrder struct { + AccumulatedFillSize fixedpoint.Value `json:"accFillSz"` + // If none is filled, it will return "". + AvgPrice fixedpoint.Value `json:"avgPx"` + CreatedTime types.MillisecondTimestamp `json:"cTime"` + Category string `json:"category"` + ClientOrderId string `json:"clOrdId"` + Fee fixedpoint.Value `json:"fee"` + FeeCurrency string `json:"feeCcy"` + // Last filled time + FillTime types.MillisecondTimestamp `json:"fillTime"` + InstrumentID string `json:"instId"` + InstrumentType InstrumentType `json:"instType"` + OrderId types.StrInt64 `json:"ordId"` + OrderType OrderType `json:"ordType"` + Price fixedpoint.Value `json:"px"` + Side SideType `json:"side"` + State OrderState `json:"state"` + Size fixedpoint.Value `json:"sz"` + TargetCurrency string `json:"tgtCcy"` + UpdatedTime types.MillisecondTimestamp `json:"uTime"` + + // Margin currency + // Only applicable to cross MARGIN orders in Single-currency margin. + Currency string `json:"ccy"` + TradeId string `json:"tradeId"` + // Last filled price + FillPrice fixedpoint.Value `json:"fillPx"` + // Last filled quantity + FillSize fixedpoint.Value `json:"fillSz"` + // Leverage, from 0.01 to 125. + // Only applicable to MARGIN/FUTURES/SWAP + Lever string `json:"lever"` + // Profit and loss, Applicable to orders which have a trade and aim to close position. It always is 0 in other conditions + Pnl fixedpoint.Value `json:"pnl"` + PositionSide string `json:"posSide"` + // Options price in USDOnly applicable to options; return "" for other instrument types + PriceUsd fixedpoint.Value `json:"pxUsd"` + // Implied volatility of the options orderOnly applicable to options; return "" for other instrument types + PriceVol fixedpoint.Value `json:"pxVol"` + // Price type of options + PriceType string `json:"pxType"` + // Rebate amount, only applicable to spot and margin, the reward of placing orders from the platform (rebate) + // given to user who has reached the specified trading level. If there is no rebate, this field is "". + Rebate fixedpoint.Value `json:"rebate"` + RebateCcy string `json:"rebateCcy"` + // Client-supplied Algo ID when placing order attaching TP/SL. + AttachAlgoClOrdId string `json:"attachAlgoClOrdId"` + SlOrdPx fixedpoint.Value `json:"slOrdPx"` + SlTriggerPx fixedpoint.Value `json:"slTriggerPx"` + SlTriggerPxType string `json:"slTriggerPxType"` + AttachAlgoOrds []interface{} `json:"attachAlgoOrds"` + Source string `json:"source"` + // Self trade prevention ID. Return "" if self trade prevention is not applicable + StpId string `json:"stpId"` + // Self trade prevention mode. Return "" if self trade prevention is not applicable + StpMode string `json:"stpMode"` + Tag string `json:"tag"` + TradeMode string `json:"tdMode"` + TpOrdPx fixedpoint.Value `json:"tpOrdPx"` + TpTriggerPx fixedpoint.Value `json:"tpTriggerPx"` + TpTriggerPxType string `json:"tpTriggerPxType"` + ReduceOnly string `json:"reduceOnly"` + QuickMgnType string `json:"quickMgnType"` + AlgoClOrdId string `json:"algoClOrdId"` + AlgoId string `json:"algoId"` +} + +//go:generate GetRequest -url "/api/v5/trade/orders-pending" -type GetOpenOrdersRequest -responseDataType []OpenOrder +type GetOpenOrdersRequest struct { + client requestgen.AuthenticatedAPIClient + + instrumentID *string `param:"instId,query"` + + instrumentType InstrumentType `param:"instType,query"` + + orderType *OrderType `param:"ordType,query"` + + state *OrderState `param:"state,query"` + category *string `param:"category,query"` + // Pagination of data to return records earlier than the requested ordId + after *string `param:"after,query"` + // Pagination of data to return records newer than the requested ordId + before *string `param:"before,query"` + // Filter with a begin timestamp. Unix timestamp format in milliseconds, e.g. 1597026383085 + begin *time.Time `param:"begin,query"` + + // Filter with an end timestamp. Unix timestamp format in milliseconds, e.g. 1597026383085 + end *time.Time `param:"end,query"` + limit *string `param:"limit,query"` +} + +func (c *RestClient) NewGetOpenOrdersRequest() *GetOpenOrdersRequest { + return &GetOpenOrdersRequest{ + client: c, + instrumentType: InstrumentTypeSpot, + } +} diff --git a/pkg/exchange/okex/okexapi/get_open_orders_request_requestgen.go b/pkg/exchange/okex/okexapi/get_open_orders_request_requestgen.go new file mode 100644 index 0000000000..c97836a323 --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_open_orders_request_requestgen.go @@ -0,0 +1,321 @@ +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/trade/orders-pending -type GetOpenOrdersRequest -responseDataType []OpenOrder"; DO NOT EDIT. + +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" + "time" +) + +func (g *GetOpenOrdersRequest) InstrumentID(instrumentID string) *GetOpenOrdersRequest { + g.instrumentID = &instrumentID + return g +} + +func (g *GetOpenOrdersRequest) InstrumentType(instrumentType InstrumentType) *GetOpenOrdersRequest { + g.instrumentType = instrumentType + return g +} + +func (g *GetOpenOrdersRequest) OrderType(orderType OrderType) *GetOpenOrdersRequest { + g.orderType = &orderType + return g +} + +func (g *GetOpenOrdersRequest) State(state OrderState) *GetOpenOrdersRequest { + g.state = &state + return g +} + +func (g *GetOpenOrdersRequest) Category(category string) *GetOpenOrdersRequest { + g.category = &category + return g +} + +func (g *GetOpenOrdersRequest) After(after string) *GetOpenOrdersRequest { + g.after = &after + return g +} + +func (g *GetOpenOrdersRequest) Before(before string) *GetOpenOrdersRequest { + g.before = &before + return g +} + +func (g *GetOpenOrdersRequest) Begin(begin time.Time) *GetOpenOrdersRequest { + g.begin = &begin + return g +} + +func (g *GetOpenOrdersRequest) End(end time.Time) *GetOpenOrdersRequest { + g.end = &end + return g +} + +func (g *GetOpenOrdersRequest) Limit(limit string) *GetOpenOrdersRequest { + g.limit = &limit + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetOpenOrdersRequest) GetQueryParameters() (url.Values, error) { + var params = map[string]interface{}{} + // check instrumentID field -> json key instId + if g.instrumentID != nil { + instrumentID := *g.instrumentID + + // assign parameter of instrumentID + params["instId"] = instrumentID + } else { + } + // check instrumentType field -> json key instType + instrumentType := g.instrumentType + + // TEMPLATE check-valid-values + switch instrumentType { + case InstrumentTypeSpot, InstrumentTypeSwap, InstrumentTypeFutures, InstrumentTypeOption, InstrumentTypeMARGIN: + params["instType"] = instrumentType + + default: + return nil, fmt.Errorf("instType value %v is invalid", instrumentType) + + } + // END TEMPLATE check-valid-values + + // assign parameter of instrumentType + params["instType"] = instrumentType + // check orderType field -> json key ordType + if g.orderType != nil { + orderType := *g.orderType + + // TEMPLATE check-valid-values + switch orderType { + case OrderTypeMarket, OrderTypeLimit, OrderTypePostOnly, OrderTypeFOK, OrderTypeIOC: + params["ordType"] = orderType + + default: + return nil, fmt.Errorf("ordType value %v is invalid", orderType) + + } + // END TEMPLATE check-valid-values + + // assign parameter of orderType + params["ordType"] = orderType + } else { + } + // check state field -> json key state + if g.state != nil { + state := *g.state + + // TEMPLATE check-valid-values + switch state { + case OrderStateCanceled, OrderStateLive, OrderStatePartiallyFilled, OrderStateFilled: + params["state"] = state + + default: + return nil, fmt.Errorf("state value %v is invalid", state) + + } + // END TEMPLATE check-valid-values + + // assign parameter of state + params["state"] = state + } else { + } + // check category field -> json key category + if g.category != nil { + category := *g.category + + // assign parameter of category + params["category"] = category + } else { + } + // check after field -> json key after + if g.after != nil { + after := *g.after + + // assign parameter of after + params["after"] = after + } else { + } + // check before field -> json key before + if g.before != nil { + before := *g.before + + // assign parameter of before + params["before"] = before + } else { + } + // check begin field -> json key begin + if g.begin != nil { + begin := *g.begin + + // assign parameter of begin + params["begin"] = begin + } else { + } + // check end field -> json key end + if g.end != nil { + end := *g.end + + // assign parameter of end + params["end"] = end + } else { + } + // check limit field -> json key limit + if g.limit != nil { + limit := *g.limit + + // assign parameter of limit + params["limit"] = limit + } else { + } + + query := url.Values{} + for _k, _v := range params { + query.Add(_k, fmt.Sprintf("%v", _v)) + } + + return query, nil +} + +// GetParameters builds and checks the parameters and return the result in a map object +func (g *GetOpenOrdersRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetOpenOrdersRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := g.GetParameters() + if err != nil { + return query, err + } + + for _k, _v := range params { + if g.isVarSlice(_v) { + g.iterateSlice(_v, func(it interface{}) { + query.Add(_k+"[]", fmt.Sprintf("%v", it)) + }) + } else { + query.Add(_k, fmt.Sprintf("%v", _v)) + } + } + + return query, nil +} + +// GetParametersJSON converts the parameters from GetParameters into the JSON format +func (g *GetOpenOrdersRequest) GetParametersJSON() ([]byte, error) { + params, err := g.GetParameters() + if err != nil { + return nil, err + } + + return json.Marshal(params) +} + +// GetSlugParameters builds and checks the slug parameters and return the result in a map object +func (g *GetOpenOrdersRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetOpenOrdersRequest) applySlugsToUrl(url string, slugs map[string]string) string { + for _k, _v := range slugs { + needleRE := regexp.MustCompile(":" + _k + "\\b") + url = needleRE.ReplaceAllString(url, _v) + } + + return url +} + +func (g *GetOpenOrdersRequest) iterateSlice(slice interface{}, _f func(it interface{})) { + sliceValue := reflect.ValueOf(slice) + for _i := 0; _i < sliceValue.Len(); _i++ { + it := sliceValue.Index(_i).Interface() + _f(it) + } +} + +func (g *GetOpenOrdersRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetOpenOrdersRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := g.GetSlugParameters() + if err != nil { + return slugs, nil + } + + for _k, _v := range params { + slugs[_k] = fmt.Sprintf("%v", _v) + } + + return slugs, nil +} + +// GetPath returns the request path of the API +func (g *GetOpenOrdersRequest) GetPath() string { + return "/api/v5/trade/orders-pending" +} + +// Do generates the request object and send the request object to the API endpoint +func (g *GetOpenOrdersRequest) Do(ctx context.Context) ([]OpenOrder, error) { + + // no body params + var params interface{} + query, err := g.GetQueryParameters() + if err != nil { + return nil, err + } + + var apiURL string + + apiURL = g.GetPath() + + req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := g.client.SendRequest(req) + if err != nil { + return nil, err + } + + var apiResponse APIResponse + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + + type responseValidator interface { + Validate() error + } + validator, ok := interface{}(apiResponse).(responseValidator) + if ok { + if err := validator.Validate(); err != nil { + return nil, err + } + } + var data []OpenOrder + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/exchange/okex/okexapi/trade.go b/pkg/exchange/okex/okexapi/trade.go index 19d5c497f0..f9440c80bd 100644 --- a/pkg/exchange/okex/okexapi/trade.go +++ b/pkg/exchange/okex/okexapi/trade.go @@ -4,11 +4,11 @@ import ( "context" "encoding/json" "net/url" - "strings" + + "github.com/pkg/errors" "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" - "github.com/pkg/errors" ) func (c *RestClient) NewBatchPlaceOrderRequest() *BatchPlaceOrderRequest { @@ -29,12 +29,6 @@ func (c *RestClient) NewGetOrderDetailsRequest() *GetOrderDetailsRequest { } } -func (c *RestClient) NewGetPendingOrderRequest() *GetPendingOrderRequest { - return &GetPendingOrderRequest{ - client: c, - } -} - func (c *RestClient) NewGetTransactionDetailsRequest() *GetTransactionDetailsRequest { return &GetTransactionDetailsRequest{ client: c, @@ -249,89 +243,6 @@ func (r *GetOrderDetailsRequest) Do(ctx context.Context) (*OrderDetails, error) return &data[0], nil } -type GetPendingOrderRequest struct { - client *RestClient - - instId *string - - instType *InstrumentType - - orderTypes []string - - state *OrderState -} - -func (r *GetPendingOrderRequest) InstrumentID(instId string) *GetPendingOrderRequest { - r.instId = &instId - return r -} - -func (r *GetPendingOrderRequest) InstrumentType(instType InstrumentType) *GetPendingOrderRequest { - r.instType = &instType - return r -} - -func (r *GetPendingOrderRequest) State(state OrderState) *GetPendingOrderRequest { - r.state = &state - return r -} - -func (r *GetPendingOrderRequest) OrderTypes(orderTypes []string) *GetPendingOrderRequest { - r.orderTypes = orderTypes - return r -} - -func (r *GetPendingOrderRequest) AddOrderTypes(orderTypes ...string) *GetPendingOrderRequest { - r.orderTypes = append(r.orderTypes, orderTypes...) - return r -} - -func (r *GetPendingOrderRequest) Parameters() map[string]interface{} { - var payload = map[string]interface{}{} - - if r.instId != nil { - payload["instId"] = r.instId - } - - if r.instType != nil { - payload["instType"] = r.instType - } - - if r.state != nil { - payload["state"] = r.state - } - - if len(r.orderTypes) > 0 { - payload["ordType"] = strings.Join(r.orderTypes, ",") - } - - return payload -} - -func (r *GetPendingOrderRequest) Do(ctx context.Context) ([]OrderDetails, error) { - payload := r.Parameters() - req, err := r.client.NewAuthenticatedRequest(ctx, "GET", "/api/v5/trade/orders-pending", nil, payload) - if err != nil { - return nil, err - } - - response, err := r.client.SendRequest(req) - if err != nil { - return nil, err - } - - var apiResponse APIResponse - if err := response.DecodeJSON(&apiResponse); err != nil { - return nil, err - } - var data []OrderDetails - if err := json.Unmarshal(apiResponse.Data, &data); err != nil { - return nil, err - } - - return data, nil -} - type GetTransactionDetailsRequest struct { client *RestClient diff --git a/pkg/fixedpoint/convert.go b/pkg/fixedpoint/convert.go index 9a8bf36fe2..d000603a81 100644 --- a/pkg/fixedpoint/convert.go +++ b/pkg/fixedpoint/convert.go @@ -296,7 +296,7 @@ func (v *Value) UnmarshalJSON(data []byte) error { *v = Zero return nil } - if len(data) == 0 { + if len(data) == 0 || bytes.Equal(data, []byte{'"', '"'}) { *v = Zero return nil }