diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index c9a541786c9..8d0f3a92354 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -729,7 +729,7 @@ func (e *Exchange) CancelSingleSpotOrder(ctx context.Context, orderID, currencyP } // GetMySpotTradingHistory retrieves personal trading history -func (e *Exchange) GetMySpotTradingHistory(ctx context.Context, p currency.Pair, orderID string, page, limit uint64, crossMargin bool, from, to time.Time) ([]SpotPersonalTradeHistory, error) { +func (e *Exchange) GetMySpotTradingHistory(ctx context.Context, p currency.Pair, orderID string, page, limit uint64, from, to time.Time) ([]SpotPersonalTradeHistory, error) { params := url.Values{} if p.IsPopulated() { params.Set("currency_pair", p.String()) @@ -743,9 +743,6 @@ func (e *Exchange) GetMySpotTradingHistory(ctx context.Context, p currency.Pair, if page > 0 { params.Set("page", strconv.FormatUint(page, 10)) } - if crossMargin { - params.Set("account", asset.CrossMargin.String()) - } if !from.IsZero() { params.Set("from", strconv.FormatInt(from.Unix(), 10)) } @@ -767,17 +764,6 @@ func (e *Exchange) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, return resp.ServerTime.Time(), nil } -// CountdownCancelorders Countdown cancel orders -// When the timeout set by the user is reached, if there is no cancel or set a new countdown, the related pending orders will be automatically cancelled. -// This endpoint can be called repeatedly to set a new countdown or cancel the countdown. -func (e *Exchange) CountdownCancelorders(ctx context.Context, arg CountdownCancelOrderParam) (*TriggerTimeResponse, error) { - if arg.Timeout <= 0 { - return nil, errInvalidCountdown - } - var response *TriggerTimeResponse - return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, spotCountdownCancelEPL, http.MethodPost, gateioSpotAllCountdown, nil, &arg, &response) -} - // CreatePriceTriggeredOrder create a price-triggered order func (e *Exchange) CreatePriceTriggeredOrder(ctx context.Context, arg *PriceTriggeredOrderParam) (*OrderID, error) { if arg == nil { @@ -2432,16 +2418,30 @@ func (e *Exchange) GetFuturesLiquidationHistory(ctx context.Context, settle curr return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualLiquidationHistoryEPL, http.MethodGet, futuresPath+settle.Item.Lower+"/liquidates", params, nil, &response) } -// CountdownCancelOrders represents a trigger time response -func (e *Exchange) CountdownCancelOrders(ctx context.Context, settle currency.Code, arg CountdownParams) (*TriggerTimeResponse, error) { +// CountdownCancelFuturesOrders represents a trigger time response +func (e *Exchange) CountdownCancelFuturesOrders(ctx context.Context, settle currency.Code, arg CountdownParams) (*TriggerTimeResponse, error) { if settle.IsEmpty() { return nil, errEmptyOrInvalidSettlementCurrency } if arg.Timeout < 0 { return nil, errInvalidTimeout } + return e.sendCountdownCancelOrdersRequest(ctx, perpetualCancelTriggerOrdersEPL, futuresPath+settle.Item.Lower+"/countdown_cancel_all", &arg) +} + +// CountdownCancelSpotOrders Countdown cancel orders +// When the timeout set by the user is reached, if there is no cancel or set a new countdown, the related pending orders will be automatically cancelled. +// This endpoint can be called repeatedly to set a new countdown or cancel the countdown. +func (e *Exchange) CountdownCancelSpotOrders(ctx context.Context, arg CountdownCancelOrderParam) (*TriggerTimeResponse, error) { + if arg.Timeout <= 0 { + return nil, errInvalidCountdown + } + return e.sendCountdownCancelOrdersRequest(ctx, spotCountdownCancelEPL, gateioSpotAllCountdown, &arg) +} + +func (e *Exchange) sendCountdownCancelOrdersRequest(ctx context.Context, epl request.EndpointLimit, path string, arg any) (*TriggerTimeResponse, error) { var response *TriggerTimeResponse - return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualCancelTriggerOrdersEPL, http.MethodPost, futuresPath+settle.Item.Lower+"/countdown_cancel_all", nil, &arg, &response) + return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, epl, http.MethodPost, path, nil, arg, &response) } // CreatePriceTriggeredFuturesOrder create a price-triggered order diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index fa874e0540d..6b9a1fcb6a1 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -367,7 +367,7 @@ func TestCancelSingleSpotOrder(t *testing.T) { func TestGetMySpotTradingHistory(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, e) - _, err := e.GetMySpotTradingHistory(t.Context(), currency.Pair{Base: currency.BTC, Quote: currency.USDT, Delimiter: currency.UnderscoreDelimiter}, "", 0, 0, false, time.Time{}, time.Time{}) + _, err := e.GetMySpotTradingHistory(t.Context(), currency.Pair{Base: currency.BTC, Quote: currency.USDT, Delimiter: currency.UnderscoreDelimiter}, "", 0, 0, time.Time{}, time.Time{}) require.NoError(t, err) } @@ -378,10 +378,10 @@ func TestGetServerTime(t *testing.T) { } } -func TestCountdownCancelorder(t *testing.T) { +func TestCountdownCancelSpotOrders(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders) - if _, err := e.CountdownCancelorders(t.Context(), CountdownCancelOrderParam{ + if _, err := e.CountdownCancelSpotOrders(t.Context(), CountdownCancelOrderParam{ Timeout: 10, CurrencyPair: currency.Pair{Base: currency.BTC, Quote: currency.ETH, Delimiter: currency.UnderscoreDelimiter}, }); err != nil { @@ -1291,13 +1291,13 @@ func TestGetFuturesLiquidationHistory(t *testing.T) { assert.NoError(t, err, "GetFuturesLiquidationHistory should not error") } -func TestCountdownCancelOrders(t *testing.T) { +func TestCountdownCancelFuturesOrders(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders) - _, err := e.CountdownCancelOrders(t.Context(), currency.BTC, CountdownParams{ + _, err := e.CountdownCancelFuturesOrders(t.Context(), currency.BTC, CountdownParams{ Timeout: 8, }) - assert.NoError(t, err, "CountdownCancelOrders should not error") + assert.NoError(t, err, "CountdownCancelFuturesOrders should not error") } func TestCreatePriceTriggeredFuturesOrder(t *testing.T) { @@ -1891,20 +1891,125 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, e) + t.Parallel() + testexch.UpdatePairsOnce(t, e) + type testCase struct { + name string + requiresAuth bool + request order.MultiOrderRequest + expectedErr error + } + + testCases := make([]testCase, 0, len(e.GetAssetTypes(false))*2+1) for _, a := range e.GetAssetTypes(false) { enabledPairs := getPairs(t, a) if len(enabledPairs) > 4 { enabledPairs = enabledPairs[:4] } - multiOrderRequest := order.MultiOrderRequest{ + + withPairs := testCase{ + name: a.String() + "/with_pairs", + requiresAuth: true, + request: order.MultiOrderRequest{ + Type: order.AnyType, + Side: order.Buy, + Pairs: enabledPairs, + AssetType: a, + }, + } + testCases = append(testCases, withPairs) + + noPairs := testCase{ + name: a.String() + "/without_pairs", + requiresAuth: true, + request: order.MultiOrderRequest{ + Type: order.AnyType, + Side: order.Buy, + AssetType: a, + }, + } + if a == asset.Options { + noPairs.requiresAuth = false + noPairs.expectedErr = currency.ErrCurrencyPairsEmpty + } + testCases = append(testCases, noPairs) + } + + testCases = append(testCases, testCase{ + name: "unsupported/default_case_binary", + requiresAuth: false, + request: order.MultiOrderRequest{ Type: order.AnyType, Side: order.Buy, - Pairs: enabledPairs, - AssetType: a, - } - _, err := e.GetOrderHistory(t.Context(), &multiOrderRequest) - assert.NoErrorf(t, err, "GetOrderHistory should not error for %s", a) + AssetType: asset.Binary, + }, + expectedErr: asset.ErrNotSupported, + }) + + for i := range testCases { + t.Run(testCases[i].name, func(t *testing.T) { + t.Parallel() + if testCases[i].requiresAuth { + sharedtestvalues.SkipTestIfCredentialsUnset(t, e) + } + orders, err := e.GetOrderHistory(t.Context(), &testCases[i].request) + if testCases[i].expectedErr != nil { + assert.ErrorIs(t, err, testCases[i].expectedErr) + return + } + assert.NoError(t, err) + for j := range orders { + assert.Equal(t, testCases[i].request.AssetType, orders[j].AssetType) + assert.Equal(t, e.Name, orders[j].Exchange) + assert.True(t, orders[j].Pair.IsPopulated(), "pair should be populated for order history response") + } + }) + } +} + +func TestGetOrderHistoryRequestImmutability(t *testing.T) { + t.Parallel() + testexch.UpdatePairsOnce(t, e) + sharedtestvalues.SkipTestIfCredentialsUnset(t, e) + enabledPairs := getPairs(t, asset.Spot) + if len(enabledPairs) > 2 { + enabledPairs = enabledPairs[:2] + } + + type testCase struct { + name string + request order.MultiOrderRequest + } + + testCases := []testCase{ + { + name: "nil_pairs", + request: order.MultiOrderRequest{ + Type: order.AnyType, + Side: order.Buy, + AssetType: asset.Spot, + }, + }, + { + name: "provided_pairs", + request: order.MultiOrderRequest{ + Type: order.AnyType, + Side: order.Buy, + Pairs: append(currency.Pairs(nil), enabledPairs...), + AssetType: asset.Spot, + }, + }, + } + + for i := range testCases { + tc := testCases[i] + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + expectedPairs := append(currency.Pairs(nil), tc.request.Pairs...) + _, err := e.GetOrderHistory(t.Context(), &tc.request) + assert.NoError(t, err) + assert.Equal(t, expectedPairs, tc.request.Pairs) + }) } } diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index 711c4fd8f9d..abcb129c3b1 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -1519,18 +1519,22 @@ type CancelOrderByIDResponse struct { // SpotPersonalTradeHistory represents personal trading history. type SpotPersonalTradeHistory struct { - TradeID string `json:"id"` - CreateTime types.Time `json:"create_time_ms"` - CurrencyPair string `json:"currency_pair"` - OrderID string `json:"order_id"` - Side string `json:"side"` - Role string `json:"role"` - Amount types.Number `json:"amount"` - Price types.Number `json:"price"` - Fee types.Number `json:"fee"` - FeeCurrency string `json:"fee_currency"` - PointFee string `json:"point_fee"` - GtFee string `json:"gt_fee"` + TradeID string `json:"id"` + CreateTime types.Time `json:"create_time_ms"` + CurrencyPair currency.Pair `json:"currency_pair"` + OrderID string `json:"order_id"` + Side string `json:"side"` + Role string `json:"role"` + Amount types.Number `json:"amount"` + Price types.Number `json:"price"` + Fee types.Number `json:"fee"` + FeeCurrency currency.Code `json:"fee_currency"` + PointFee types.Number `json:"point_fee"` + GtFee types.Number `json:"gt_fee"` + AmendText string `json:"amend_text"` + SequenceID string `json:"sequence_id"` + Text string `json:"text"` + Deal types.Number `json:"deal"` } // CountdownCancelOrderParam represents countdown cancel order params diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index b902c826e53..28fca8f40ab 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -1536,97 +1536,133 @@ func (e *Exchange) GetOrderHistory(ctx context.Context, req *order.MultiOrderReq if err := req.Validate(); err != nil { return nil, err } - var orders []order.Detail - format, err := e.GetPairFormat(req.AssetType, true) - if err != nil { - return nil, err - } + var resp []order.Detail + switch req.AssetType { case asset.Spot, asset.Margin, asset.CrossMargin: - for x := range req.Pairs { - fPair := req.Pairs[x].Format(format) - spotOrders, err := e.GetMySpotTradingHistory(ctx, fPair, req.FromOrderID, 0, 0, req.AssetType == asset.CrossMargin, req.StartTime, req.EndTime) + format, err := e.GetPairFormat(req.AssetType, true) + if err != nil { + return nil, err + } + pairs := req.Pairs + if len(pairs) == 0 { + pairs = currency.Pairs{currency.EMPTYPAIR} + } + for i := range pairs { + fp := pairs[i] + if fp.IsPopulated() { + fp = pairs[i].Format(format) + } + o, err := e.GetMySpotTradingHistory(ctx, fp, req.FromOrderID, 0, 1000, req.StartTime, req.EndTime) if err != nil { return nil, err } - for o := range spotOrders { - var side order.Side - side, err = order.StringToOrderSide(spotOrders[o].Side) + for j := range o { + fp, err = e.MatchSymbolWithAvailablePairs(o[j].CurrencyPair.String(), req.AssetType, true) if err != nil { return nil, err } - detail := order.Detail{ - OrderID: spotOrders[o].OrderID, - Amount: spotOrders[o].Amount.Float64(), - ExecutedAmount: spotOrders[o].Amount.Float64(), - Price: spotOrders[o].Price.Float64(), - Date: spotOrders[o].CreateTime.Time(), + side, err := order.StringToOrderSide(o[j].Side) + if err != nil { + return nil, err + } + if isAvail, err := e.CurrencyPairs.IsPairAvailable(o[j].CurrencyPair, req.AssetType); !isAvail || err != nil { + continue + } + od := order.Detail{ + OrderID: o[j].OrderID, + Amount: o[j].Amount.Float64(), + ExecutedAmount: o[j].Amount.Float64(), + Price: o[j].Price.Float64(), + Date: o[j].CreateTime.Time(), Side: side, Exchange: e.Name, - Pair: fPair, + Pair: o[j].CurrencyPair, AssetType: req.AssetType, - Fee: spotOrders[o].Fee.Float64(), - FeeAsset: currency.NewCode(spotOrders[o].FeeCurrency), + Fee: o[j].Fee.Float64(), + FeeAsset: o[j].FeeCurrency, } - detail.InferCostsAndTimes() - orders = append(orders, detail) + od.InferCostsAndTimes() + resp = append(resp, od) } } case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: - for x := range req.Pairs { - fPair := req.Pairs[x].Format(format) - settle, err := getSettlementCurrency(fPair, req.AssetType) + format, err := e.GetPairFormat(req.AssetType, true) + if err != nil { + return nil, err + } + pairs := req.Pairs + if len(pairs) == 0 { + pairs = currency.Pairs{currency.EMPTYPAIR} + } + for i := range pairs { + fp := pairs[i] + if fp.IsPopulated() { + fp = pairs[i].Format(format) + } + settle, err := getSettlementCurrency(fp, req.AssetType) if err != nil { return nil, err } - var futuresOrder []TradingHistoryItem + var o []TradingHistoryItem if req.AssetType == asset.DeliveryFutures { - futuresOrder, err = e.GetMyDeliveryTradingHistory(ctx, settle, req.FromOrderID, fPair, 0, 0, 0, "") + o, err = e.GetMyDeliveryTradingHistory(ctx, settle, req.FromOrderID, fp, 0, 0, 0, "") } else { - futuresOrder, err = e.GetMyFuturesTradingHistory(ctx, settle, "", req.FromOrderID, fPair, 0, 0, 0) + o, err = e.GetMyFuturesTradingHistory(ctx, settle, "", req.FromOrderID, fp, 0, 0, 0) } if err != nil { return nil, err } - for o := range futuresOrder { - detail := order.Detail{ - OrderID: strconv.FormatInt(futuresOrder[o].ID, 10), - Amount: futuresOrder[o].Size.Float64(), - Price: futuresOrder[o].Price.Float64(), - Date: futuresOrder[o].CreateTime.Time(), + for j := range o { + fp, err = e.MatchSymbolWithAvailablePairs(o[j].Contract, req.AssetType, true) + if err != nil { + return nil, err + } + od := order.Detail{ + OrderID: strconv.FormatInt(o[j].ID, 10), + Amount: o[j].Size.Float64(), + Price: o[j].Price.Float64(), + Date: o[j].CreateTime.Time(), Exchange: e.Name, - Pair: fPair, + Pair: fp, AssetType: req.AssetType, } - detail.InferCostsAndTimes() - orders = append(orders, detail) + od.InferCostsAndTimes() + resp = append(resp, od) } } case asset.Options: - for x := range req.Pairs { - fPair := req.Pairs[x].Format(format) - optionOrders, err := e.GetMyOptionsTradingHistory(ctx, fPair.String(), fPair.Upper(), 0, 0, req.StartTime, req.EndTime) + if len(req.Pairs) == 0 { + return nil, currency.ErrCurrencyPairsEmpty + } + format, err := e.GetPairFormat(req.AssetType, true) + if err != nil { + return nil, err + } + for i := range req.Pairs { + fp := req.Pairs[i].Format(format) + o, err := e.GetMyOptionsTradingHistory(ctx, fp.String(), fp.Upper(), 0, 0, req.StartTime, req.EndTime) if err != nil { return nil, err } - for o := range optionOrders { - detail := order.Detail{ - OrderID: strconv.FormatInt(optionOrders[o].OrderID, 10), - Amount: optionOrders[o].Size.Float64(), - Price: optionOrders[o].Price.Float64(), - Date: optionOrders[o].CreateTime.Time(), + for j := range o { + od := order.Detail{ + OrderID: strconv.FormatInt(o[j].OrderID, 10), + Amount: o[j].Size.Float64(), + Price: o[j].Price.Float64(), + Date: o[j].CreateTime.Time(), Exchange: e.Name, - Pair: fPair, + Pair: fp, AssetType: req.AssetType, } - detail.InferCostsAndTimes() - orders = append(orders, detail) + od.InferCostsAndTimes() + resp = append(resp, od) } } default: return nil, fmt.Errorf("%w asset type: %v", asset.ErrNotSupported, req.AssetType) } - return req.Filter(e.Name, orders), nil + return req.Filter(e.Name, resp), nil } // GetHistoricCandles returns candles between a time period for a set time interval