diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index b89d7740a..d578554fe 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -23,7 +23,7 @@ import ( // Market data limiter means public api, this includes QueryMarkets, QueryTicker, QueryTickers, QueryKLines var ( marketDataLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 5) - tradeRateLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 5) + tradeRateLimiter = rate.NewLimiter(rate.Every(300*time.Millisecond), 5) orderRateLimiter = rate.NewLimiter(rate.Every(300*time.Millisecond), 5) ) @@ -32,6 +32,11 @@ const ID = "okex" // PlatformToken is the platform currency of OKEx, pre-allocate static string here const PlatformToken = "OKB" +const ( + // Constant For query limit + defaultQueryLimit = 100 +) + var log = logrus.WithFields(logrus.Fields{ "exchange": ID, }) @@ -360,7 +365,7 @@ func (e *Exchange) QueryOrder(ctx context.Context, q types.OrderQuery) (*types.O return nil, errors.New("okex.QueryOrder: OrderId or ClientOrderId is required parameter") } req := e.client.NewGetOrderDetailsRequest() - req.InstrumentID(q.Symbol). + req.InstrumentID(toLocalSymbol(q.Symbol)). OrderID(q.OrderID). ClientOrderID(q.ClientOrderID) @@ -380,9 +385,9 @@ func (e *Exchange) QueryOrderTrades(ctx context.Context, q types.OrderQuery) ([] log.Warn("!!!OKEX EXCHANGE API NOTICE!!! Okex does not support searching for trades using OrderClientId.") } - req := e.client.NewGetTransactionHistoriesRequest() + req := e.client.NewGetTransactionHistoryRequest() if len(q.Symbol) != 0 { - req.InstrumentID(q.Symbol) + req.InstrumentID(toLocalSymbol(q.Symbol)) } if len(q.OrderID) != 0 { @@ -411,6 +416,131 @@ func (e *Exchange) QueryOrderTrades(ctx context.Context, q types.OrderQuery) ([] if errs != nil { return nil, errs } - + return trades, nil +} + +/* +QueryClosedOrders can query closed orders in last 3 months, there are no time interval limitations, as long as until >= since. +Please Use lastOrderID as cursor, only return orders later than that order, that order is not included. +If you want to query orders by time range, please just pass since and until. +If you want to query by cursor, please pass lastOrderID. +Because it gets the correct response even when you pass all parameters with the right time interval and invalid lastOrderID, like 0. +Time interval boundary unit is second. +since is inclusive, ex. order created in 1694155903, get response if query since 1694155903, get empty if query since 1694155904 +until is not inclusive, ex. order created in 1694155903, get response if query until 1694155904, get empty if query until 1694155903 +*/ +func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) ([]types.Order, error) { + if symbol == "" { + return nil, ErrSymbolRequired + } + + if err := tradeRateLimiter.Wait(ctx); err != nil { + return nil, fmt.Errorf("query closed order rate limiter wait error: %w", err) + } + + var lastOrder string + if lastOrderID <= 0 { + lastOrder = "" + } else { + lastOrder = strconv.FormatUint(lastOrderID, 10) + } + + res, err := e.client.NewGetOrderHistoryRequest(). + InstrumentID(toLocalSymbol(symbol)). + StartTime(since). + EndTime(until). + Limit(defaultQueryLimit). + Before(lastOrder). + Do(ctx) + + if err != nil { + return nil, fmt.Errorf("failed to call get order histories error: %w", err) + } + + var orders []types.Order + + for _, order := range res { + o, err2 := toGlobalOrder(&order) + if err2 != nil { + err = multierr.Append(err, err2) + continue + } + + orders = append(orders, *o) + } + if err != nil { + return nil, err + } + + return types.SortOrdersAscending(orders), nil +} + +/* +QueryTrades can query trades in last 3 months, there are no time interval limitations, as long as end_time >= start_time. +OKEX do not provide api to query by tradeID, So use /api/v5/trade/orders-history-archive as its official site do. +Please Use LastTradeID as cursor, only return trades later than that trade, that trade is not included. +If you want to query trades by time range, please just pass start_time and end_time. +If you want to query by cursor, please pass LastTradeID. +Because it gets the correct response even when you pass all parameters with the right time interval and invalid LastTradeID, like 0. +*/ +func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) ([]types.Trade, error) { + if symbol == "" { + return nil, ErrSymbolRequired + } + + req := e.client.NewGetOrderHistoryRequest().InstrumentID(toLocalSymbol(symbol)) + + limit := uint64(options.Limit) + if limit > defaultQueryLimit || limit <= 0 { + log.Debugf("limit is exceeded default limit %d or zero, got: %d, Do not pass limit", defaultQueryLimit, options.Limit) + } else { + req.Limit(limit) + } + + if err := tradeRateLimiter.Wait(ctx); err != nil { + return nil, fmt.Errorf("query trades rate limiter wait error: %w", err) + } + + var err error + var response []okexapi.OrderDetails + // query by time interval + if options.StartTime != nil || options.EndTime != nil { + if options.StartTime != nil { + req.StartTime(*options.StartTime) + } + if options.EndTime != nil { + req.EndTime(*options.EndTime) + } + + response, err = req.Do(ctx) + if err != nil { + return nil, fmt.Errorf("failed to call get order histories error: %w", err) + } + } else if options.StartTime == nil && options.EndTime == nil && options.LastTradeID == 0 { // query by no any parameters + response, err = req.Do(ctx) + if err != nil { + return nil, fmt.Errorf("failed to call get order histories error: %w", err) + } + } else { // query by trade id + lastTradeID := strconv.FormatUint(options.LastTradeID, 10) + res, err := req.Do(ctx) + if err != nil { + return nil, fmt.Errorf("failed to call get order histories error: %w", err) + } + for _, trade := range res { + if trade.LastTradeID == lastTradeID { + response, err = req.Before(trade.OrderID).Do(ctx) + if err != nil { + return nil, fmt.Errorf("failed to call get order histories error: %w", err) + } + break + } + } + } + + trades, err := toGlobalTrades(response) + if err != nil { + return nil, fmt.Errorf("failed to trans order detail to trades error: %w", err) + } return trades, nil } diff --git a/pkg/exchange/okex/okexapi/get_order_history_request.go b/pkg/exchange/okex/okexapi/get_order_history_request.go new file mode 100644 index 000000000..00b4eca7a --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_order_history_request.go @@ -0,0 +1,40 @@ +package okexapi + +import ( + "time" + + "github.com/c9s/requestgen" +) + +//go:generate GetRequest -url "/api/v5/trade/orders-history-archive" -type GetOrderHistoryRequest -responseDataType .APIResponse +type GetOrderHistoryRequest struct { + client requestgen.AuthenticatedAPIClient + + instrumentType InstrumentType `param:"instType,query"` + instrumentID *string `param:"instId,query"` + orderType *OrderType `param:"ordType,query"` + // underlying and instrumentFamil Applicable to FUTURES/SWAP/OPTION + underlying *string `param:"uly,query"` + instrumentFamily *string `param:"instFamily,query"` + + state *OrderState `param:"state,query"` + after *string `param:"after,query"` + before *string `param:"before,query"` + startTime *time.Time `param:"begin,query,milliseconds"` + + // endTime for each request, startTime and endTime can be any interval, but should be in last 3 months + endTime *time.Time `param:"end,query,milliseconds"` + + // limit for data size per page. Default: 100 + limit *uint64 `param:"limit,query"` +} + +type OrderList []OrderDetails + +// NewGetOrderHistoriesRequest is descending order by createdTime +func (c *RestClient) NewGetOrderHistoryRequest() *GetOrderHistoryRequest { + return &GetOrderHistoryRequest{ + client: c, + instrumentType: InstrumentTypeSpot, + } +} diff --git a/pkg/exchange/okex/okexapi/get_order_history_request_requestgen.go b/pkg/exchange/okex/okexapi/get_order_history_request_requestgen.go new file mode 100644 index 000000000..b9bc43596 --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_order_history_request_requestgen.go @@ -0,0 +1,319 @@ +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/trade/orders-history-archive -type GetOrderHistoryRequest -responseDataType .OrderList"; DO NOT EDIT. + +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" + "strconv" + "time" +) + +func (g *GetOrderHistoryRequest) InstrumentType(instrumentType InstrumentType) *GetOrderHistoryRequest { + g.instrumentType = instrumentType + return g +} + +func (g *GetOrderHistoryRequest) InstrumentID(instrumentID string) *GetOrderHistoryRequest { + g.instrumentID = &instrumentID + return g +} + +func (g *GetOrderHistoryRequest) OrderType(orderType OrderType) *GetOrderHistoryRequest { + g.orderType = &orderType + return g +} + +func (g *GetOrderHistoryRequest) Underlying(underlying string) *GetOrderHistoryRequest { + g.underlying = &underlying + return g +} + +func (g *GetOrderHistoryRequest) InstrumentFamily(instrumentFamily string) *GetOrderHistoryRequest { + g.instrumentFamily = &instrumentFamily + return g +} + +func (g *GetOrderHistoryRequest) State(state OrderState) *GetOrderHistoryRequest { + g.state = &state + return g +} + +func (g *GetOrderHistoryRequest) After(after string) *GetOrderHistoryRequest { + g.after = &after + return g +} + +func (g *GetOrderHistoryRequest) Before(before string) *GetOrderHistoryRequest { + g.before = &before + return g +} + +func (g *GetOrderHistoryRequest) StartTime(startTime time.Time) *GetOrderHistoryRequest { + g.startTime = &startTime + return g +} + +func (g *GetOrderHistoryRequest) EndTime(endTime time.Time) *GetOrderHistoryRequest { + g.endTime = &endTime + return g +} + +func (g *GetOrderHistoryRequest) Limit(limit uint64) *GetOrderHistoryRequest { + g.limit = &limit + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetOrderHistoryRequest) GetQueryParameters() (url.Values, error) { + var params = map[string]interface{}{} + // 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 instrumentID field -> json key instId + if g.instrumentID != nil { + instrumentID := *g.instrumentID + + // assign parameter of instrumentID + params["instId"] = instrumentID + } else { + } + // 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 underlying field -> json key uly + if g.underlying != nil { + underlying := *g.underlying + + // assign parameter of underlying + params["uly"] = underlying + } else { + } + // check instrumentFamily field -> json key instFamily + if g.instrumentFamily != nil { + instrumentFamily := *g.instrumentFamily + + // assign parameter of instrumentFamily + params["instFamily"] = instrumentFamily + } 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 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 startTime field -> json key begin + if g.startTime != nil { + startTime := *g.startTime + + // assign parameter of startTime + // convert time.Time to milliseconds time stamp + params["begin"] = strconv.FormatInt(startTime.UnixNano()/int64(time.Millisecond), 10) + } else { + } + // check endTime field -> json key end + if g.endTime != nil { + endTime := *g.endTime + + // assign parameter of endTime + // convert time.Time to milliseconds time stamp + params["end"] = strconv.FormatInt(endTime.UnixNano()/int64(time.Millisecond), 10) + } 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 *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetOrderHistoryRequest) 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 +} + +func (g *GetOrderHistoryRequest) Do(ctx context.Context) (OrderList, error) { + + // no body params + var params interface{} + query, err := g.GetQueryParameters() + if err != nil { + return nil, err + } + + apiURL := "/api/v5/trade/orders-history-archive" + + 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 + } + var data OrderList + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/exchange/okex/okexapi/get_transaction_histories_request.go b/pkg/exchange/okex/okexapi/get_transaction_history_request.go similarity index 79% rename from pkg/exchange/okex/okexapi/get_transaction_histories_request.go rename to pkg/exchange/okex/okexapi/get_transaction_history_request.go index 7e16d3b54..23e02fd4a 100644 --- a/pkg/exchange/okex/okexapi/get_transaction_histories_request.go +++ b/pkg/exchange/okex/okexapi/get_transaction_history_request.go @@ -6,8 +6,8 @@ import ( "github.com/c9s/requestgen" ) -//go:generate GetRequest -url "/api/v5/trade/fills-history" -type GetTransactionHistoriesRequest -responseDataType .APIResponse -type GetTransactionHistoriesRequest struct { +//go:generate GetRequest -url "/api/v5/trade/fills-history" -type GetTransactionHistoryRequest -responseDataType .APIResponse +type GetTransactionHistoryRequest struct { client requestgen.AuthenticatedAPIClient instrumentType InstrumentType `param:"instType,query"` @@ -29,11 +29,9 @@ type GetTransactionHistoriesRequest struct { limit *uint64 `param:"limit,query"` } -type OrderList []OrderDetails - // NewGetOrderHistoriesRequest is descending order by createdTime -func (c *RestClient) NewGetTransactionHistoriesRequest() *GetTransactionHistoriesRequest { - return &GetTransactionHistoriesRequest{ +func (c *RestClient) NewGetTransactionHistoryRequest() *GetTransactionHistoryRequest { + return &GetTransactionHistoryRequest{ client: c, instrumentType: InstrumentTypeSpot, } diff --git a/pkg/exchange/okex/okexapi/get_transaction_histories_request_requestgen.go b/pkg/exchange/okex/okexapi/get_transaction_history_request_requestgen.go similarity index 73% rename from pkg/exchange/okex/okexapi/get_transaction_histories_request_requestgen.go rename to pkg/exchange/okex/okexapi/get_transaction_history_request_requestgen.go index f88a68513..00c3d71da 100644 --- a/pkg/exchange/okex/okexapi/get_transaction_histories_request_requestgen.go +++ b/pkg/exchange/okex/okexapi/get_transaction_history_request_requestgen.go @@ -1,4 +1,4 @@ -// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/trade/fills-history -type GetTransactionHistoriesRequest -responseDataType .OrderList"; DO NOT EDIT. +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/trade/fills-history -type GetTransactionHistoryRequest -responseDataType .OrderList"; DO NOT EDIT. package okexapi @@ -13,63 +13,63 @@ import ( "time" ) -func (g *GetTransactionHistoriesRequest) InstrumentType(instrumentType InstrumentType) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) InstrumentType(instrumentType InstrumentType) *GetTransactionHistoryRequest { g.instrumentType = instrumentType return g } -func (g *GetTransactionHistoriesRequest) InstrumentID(instrumentID string) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) InstrumentID(instrumentID string) *GetTransactionHistoryRequest { g.instrumentID = &instrumentID return g } -func (g *GetTransactionHistoriesRequest) OrderType(orderType OrderType) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) OrderType(orderType OrderType) *GetTransactionHistoryRequest { g.orderType = &orderType return g } -func (g *GetTransactionHistoriesRequest) OrderID(orderID string) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) OrderID(orderID string) *GetTransactionHistoryRequest { g.orderID = orderID return g } -func (g *GetTransactionHistoriesRequest) Underlying(underlying string) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) Underlying(underlying string) *GetTransactionHistoryRequest { g.underlying = &underlying return g } -func (g *GetTransactionHistoriesRequest) InstrumentFamily(instrumentFamily string) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) InstrumentFamily(instrumentFamily string) *GetTransactionHistoryRequest { g.instrumentFamily = &instrumentFamily return g } -func (g *GetTransactionHistoriesRequest) After(after string) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) After(after string) *GetTransactionHistoryRequest { g.after = &after return g } -func (g *GetTransactionHistoriesRequest) Before(before string) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) Before(before string) *GetTransactionHistoryRequest { g.before = &before return g } -func (g *GetTransactionHistoriesRequest) StartTime(startTime time.Time) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) StartTime(startTime time.Time) *GetTransactionHistoryRequest { g.startTime = &startTime return g } -func (g *GetTransactionHistoriesRequest) EndTime(endTime time.Time) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) EndTime(endTime time.Time) *GetTransactionHistoryRequest { g.endTime = &endTime return g } -func (g *GetTransactionHistoriesRequest) Limit(limit uint64) *GetTransactionHistoriesRequest { +func (g *GetTransactionHistoryRequest) Limit(limit uint64) *GetTransactionHistoryRequest { g.limit = &limit return g } // GetQueryParameters builds and checks the query parameters and returns url.Values -func (g *GetTransactionHistoriesRequest) GetQueryParameters() (url.Values, error) { +func (g *GetTransactionHistoryRequest) GetQueryParameters() (url.Values, error) { var params = map[string]interface{}{} // check instrumentType field -> json key instType instrumentType := g.instrumentType @@ -187,14 +187,14 @@ func (g *GetTransactionHistoriesRequest) GetQueryParameters() (url.Values, error } // GetParameters builds and checks the parameters and return the result in a map object -func (g *GetTransactionHistoriesRequest) GetParameters() (map[string]interface{}, error) { +func (g *GetTransactionHistoryRequest) 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 *GetTransactionHistoriesRequest) GetParametersQuery() (url.Values, error) { +func (g *GetTransactionHistoryRequest) GetParametersQuery() (url.Values, error) { query := url.Values{} params, err := g.GetParameters() @@ -216,7 +216,7 @@ func (g *GetTransactionHistoriesRequest) GetParametersQuery() (url.Values, error } // GetParametersJSON converts the parameters from GetParameters into the JSON format -func (g *GetTransactionHistoriesRequest) GetParametersJSON() ([]byte, error) { +func (g *GetTransactionHistoryRequest) GetParametersJSON() ([]byte, error) { params, err := g.GetParameters() if err != nil { return nil, err @@ -226,13 +226,13 @@ func (g *GetTransactionHistoriesRequest) GetParametersJSON() ([]byte, error) { } // GetSlugParameters builds and checks the slug parameters and return the result in a map object -func (g *GetTransactionHistoriesRequest) GetSlugParameters() (map[string]interface{}, error) { +func (g *GetTransactionHistoryRequest) GetSlugParameters() (map[string]interface{}, error) { var params = map[string]interface{}{} return params, nil } -func (g *GetTransactionHistoriesRequest) applySlugsToUrl(url string, slugs map[string]string) string { +func (g *GetTransactionHistoryRequest) applySlugsToUrl(url string, slugs map[string]string) string { for _k, _v := range slugs { needleRE := regexp.MustCompile(":" + _k + "\\b") url = needleRE.ReplaceAllString(url, _v) @@ -241,7 +241,7 @@ func (g *GetTransactionHistoriesRequest) applySlugsToUrl(url string, slugs map[s return url } -func (g *GetTransactionHistoriesRequest) iterateSlice(slice interface{}, _f func(it interface{})) { +func (g *GetTransactionHistoryRequest) iterateSlice(slice interface{}, _f func(it interface{})) { sliceValue := reflect.ValueOf(slice) for _i := 0; _i < sliceValue.Len(); _i++ { it := sliceValue.Index(_i).Interface() @@ -249,7 +249,7 @@ func (g *GetTransactionHistoriesRequest) iterateSlice(slice interface{}, _f func } } -func (g *GetTransactionHistoriesRequest) isVarSlice(_v interface{}) bool { +func (g *GetTransactionHistoryRequest) isVarSlice(_v interface{}) bool { rt := reflect.TypeOf(_v) switch rt.Kind() { case reflect.Slice: @@ -258,7 +258,7 @@ func (g *GetTransactionHistoriesRequest) isVarSlice(_v interface{}) bool { return false } -func (g *GetTransactionHistoriesRequest) GetSlugsMap() (map[string]string, error) { +func (g *GetTransactionHistoryRequest) GetSlugsMap() (map[string]string, error) { slugs := map[string]string{} params, err := g.GetSlugParameters() if err != nil { @@ -272,7 +272,7 @@ func (g *GetTransactionHistoriesRequest) GetSlugsMap() (map[string]string, error return slugs, nil } -func (g *GetTransactionHistoriesRequest) Do(ctx context.Context) (OrderList, error) { +func (g *GetTransactionHistoryRequest) Do(ctx context.Context) (OrderList, error) { // no body params var params interface{} diff --git a/pkg/exchange/okex/query_closed_orders_test.go b/pkg/exchange/okex/query_closed_orders_test.go new file mode 100644 index 000000000..9b77e7665 --- /dev/null +++ b/pkg/exchange/okex/query_closed_orders_test.go @@ -0,0 +1,62 @@ +package okex + +import ( + "context" + "testing" + "time" + + "github.com/c9s/bbgo/pkg/testutil" + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +func Test_QueryClosedOrders(t *testing.T) { + + key, secret, passphrase, ok := testutil.IntegrationTestWithPassphraseConfigured(t, types.ExchangeOKEx.String()) + if !ok { + t.Skip("Please configure all credentials about OKEX") + } + + e := New(key, secret, passphrase) + + queryOrder := types.OrderQuery{ + Symbol: "BTCUSDT", + } + + // test by order id as a cursor + closedOrder, err := e.QueryClosedOrders(context.Background(), string(queryOrder.Symbol), time.Time{}, time.Time{}, 609869603774656544) + if assert.NoError(t, err) { + assert.NotEmpty(t, closedOrder) + } + t.Logf("closed order detail: %+v", closedOrder) + // test by time interval + closedOrder, err = e.QueryClosedOrders(context.Background(), string(queryOrder.Symbol), time.Now().Add(-90*24*time.Hour), time.Now(), 0) + if assert.NoError(t, err) { + assert.NotEmpty(t, closedOrder) + } + t.Logf("closed order detail: %+v", closedOrder) + // test by no parameter + closedOrder, err = e.QueryClosedOrders(context.Background(), string(queryOrder.Symbol), time.Time{}, time.Time{}, 0) + if assert.NoError(t, err) { + assert.NotEmpty(t, closedOrder) + } + t.Logf("closed order detail: %+v", closedOrder) + // test by time interval (boundary test) + closedOrder, err = e.QueryClosedOrders(context.Background(), string(queryOrder.Symbol), time.Unix(1694155903, 999), time.Now(), 0) + if assert.NoError(t, err) { + assert.NotEmpty(t, closedOrder) + } + t.Logf("closed order detail: %+v", closedOrder) + // test by time interval (boundary test) + closedOrder, err = e.QueryClosedOrders(context.Background(), string(queryOrder.Symbol), time.Unix(1694154903, 999), time.Unix(1694155904, 0), 0) + if assert.NoError(t, err) { + assert.NotEmpty(t, closedOrder) + } + t.Logf("closed order detail: %+v", closedOrder) + // test by time interval and order id together + closedOrder, err = e.QueryClosedOrders(context.Background(), string(queryOrder.Symbol), time.Unix(1694154903, 999), time.Now(), 609869603774656544) + if assert.NoError(t, err) { + assert.NotEmpty(t, closedOrder) + } + t.Logf("closed order detail: %+v", closedOrder) +} diff --git a/pkg/exchange/okex/query_order_test.go b/pkg/exchange/okex/query_order_test.go index 3c32da40c..03c3af1a4 100644 --- a/pkg/exchange/okex/query_order_test.go +++ b/pkg/exchange/okex/query_order_test.go @@ -25,7 +25,7 @@ func Test_QueryOrder(t *testing.T) { e := New(key, secret, passphrase) queryOrder := types.OrderQuery{ - Symbol: "BTC-USDT", + Symbol: "BTCUSDT", OrderID: "609869603774656544", } orderDetail, err := e.QueryOrder(context.Background(), queryOrder) diff --git a/pkg/exchange/okex/query_trades_test.go b/pkg/exchange/okex/query_trades_test.go new file mode 100644 index 000000000..0058b6bcc --- /dev/null +++ b/pkg/exchange/okex/query_trades_test.go @@ -0,0 +1,57 @@ +package okex + +import ( + "context" + "testing" + "time" + + "github.com/c9s/bbgo/pkg/testutil" + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +func Test_QueryTrades(t *testing.T) { + key, secret, passphrase, ok := testutil.IntegrationTestWithPassphraseConfigured(t, "OKEX") + if !ok { + t.Skip("Please configure all credentials about OKEX") + } + + e := New(key, secret, passphrase) + + queryOrder := types.OrderQuery{ + Symbol: "BTCUSDT", + } + + since := time.Now().AddDate(0, -3, 0) + until := time.Now() + + queryOption := types.TradeQueryOptions{ + StartTime: &since, + EndTime: &until, + Limit: 100, + } + // query by time interval + transactionDetail, err := e.QueryTrades(context.Background(), queryOrder.Symbol, &queryOption) + if assert.NoError(t, err) { + assert.NotEmpty(t, transactionDetail) + } + t.Logf("transaction detail: %+v", transactionDetail) + // query by trade id + transactionDetail, err = e.QueryTrades(context.Background(), queryOrder.Symbol, &types.TradeQueryOptions{LastTradeID: 432044402}) + if assert.NoError(t, err) { + assert.NotEmpty(t, transactionDetail) + } + t.Logf("transaction detail: %+v", transactionDetail) + // query by no time interval and no trade id + transactionDetail, err = e.QueryTrades(context.Background(), queryOrder.Symbol, &types.TradeQueryOptions{}) + if assert.NoError(t, err) { + assert.NotEmpty(t, transactionDetail) + } + t.Logf("transaction detail: %+v", transactionDetail) + // query by limit exceed default value + transactionDetail, err = e.QueryTrades(context.Background(), queryOrder.Symbol, &types.TradeQueryOptions{Limit: 150}) + if assert.NoError(t, err) { + assert.NotEmpty(t, transactionDetail) + } + t.Logf("transaction detail: %+v", transactionDetail) +} diff --git a/pkg/testutil/auth.go b/pkg/testutil/auth.go index 8e5bd43c7..a4fae74b0 100644 --- a/pkg/testutil/auth.go +++ b/pkg/testutil/auth.go @@ -3,6 +3,7 @@ package testutil import ( "os" "regexp" + "strings" "testing" ) @@ -26,6 +27,7 @@ func IntegrationTestConfigured(t *testing.T, prefix string) (key, secret string, func IntegrationTestWithPassphraseConfigured(t *testing.T, prefix string) (key, secret, passphrase string, ok bool) { var hasKey, hasSecret, hasPassphrase bool + prefix = strings.ToUpper(prefix) key, hasKey = os.LookupEnv(prefix + "_API_KEY") secret, hasSecret = os.LookupEnv(prefix + "_API_SECRET") passphrase, hasPassphrase = os.LookupEnv(prefix + "_API_PASSPHRASE")