From 88ac5274c99408ea8ce294c038cd25c5302870e9 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Wed, 30 Apr 2025 07:39:39 +0200 Subject: [PATCH] GateIO: Split futures into USDTM and CoinM futures (#1786) * Config: v5 Split GateIO futures into CoinM and USDT * GateIO: Split asset.Futures into CoinM and USDT * Fix CancelBatchOrders using wrong endpoint for CoinMarginedFutures * Fix TestGetActiveOrders expecting currency.ErrCurrencyPairsEmpty * Config: Add config version continuity step to CI * GateIO: Pin CoinM futures to just BTC/USD Right now we only have a /btc endpoint available, and only BTCUSD is available. If GateIO offers more, we'll need to add a settlement currencies list again --- .github/workflows/config-versions-lint.yml | 16 + config/versions/continuity_test.go | 20 + config/versions/register.go | 2 + config/versions/v1/types.go | 7 +- config/versions/v2/types.go | 27 + config/versions/v7/v7.go | 92 ++ config/versions/v7/v7_test.go | 62 + currency/pair.go | 5 +- exchanges/gateio/gateio.go | 29 +- exchanges/gateio/gateio_test.go | 1356 +++++------------ exchanges/gateio/gateio_types.go | 2 - exchanges/gateio/gateio_websocket_futures.go | 33 +- .../gateio_websocket_request_futures.go | 32 +- .../gateio_websocket_request_futures_test.go | 31 +- exchanges/gateio/gateio_wrapper.go | 1321 +++++++--------- exchanges/gateio/testdata/wsFutures.json | 15 + 16 files changed, 1201 insertions(+), 1849 deletions(-) create mode 100644 .github/workflows/config-versions-lint.yml create mode 100644 config/versions/continuity_test.go create mode 100644 config/versions/v2/types.go create mode 100644 config/versions/v7/v7.go create mode 100644 config/versions/v7/v7_test.go create mode 100644 exchanges/gateio/testdata/wsFutures.json diff --git a/.github/workflows/config-versions-lint.yml b/.github/workflows/config-versions-lint.yml new file mode 100644 index 00000000..c11080bb --- /dev/null +++ b/.github/workflows/config-versions-lint.yml @@ -0,0 +1,16 @@ +name: configs-versions-lint +on: [push, pull_request] +env: + GO_VERSION: 1.24.x +jobs: + lint: + name: config versions lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + - name: Check config versions are continuous + run: go test ./config/versions/ -tags config_versions -run Continuity diff --git a/config/versions/continuity_test.go b/config/versions/continuity_test.go new file mode 100644 index 00000000..e51c2525 --- /dev/null +++ b/config/versions/continuity_test.go @@ -0,0 +1,20 @@ +//go:build config_versions +// +build config_versions + +// This test is run independently from CI for developer convenience when developing out-of-sequence versions +// Called from a separate github workflow to prevent a PR from being merged without failing the main unit tests + +package versions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVersionContinuity(t *testing.T) { + t.Parallel() + for ver, v := range Manager.versions { + assert.NotNilf(t, v, "Version %d should not be empty", ver) + } +} diff --git a/config/versions/register.go b/config/versions/register.go index 0f346b1b..7f8f5ab1 100644 --- a/config/versions/register.go +++ b/config/versions/register.go @@ -8,6 +8,7 @@ import ( v4 "github.com/thrasher-corp/gocryptotrader/config/versions/v4" v5 "github.com/thrasher-corp/gocryptotrader/config/versions/v5" v6 "github.com/thrasher-corp/gocryptotrader/config/versions/v6" + v7 "github.com/thrasher-corp/gocryptotrader/config/versions/v7" ) func init() { @@ -18,4 +19,5 @@ func init() { Manager.registerVersion(4, &v4.Version{}) Manager.registerVersion(5, &v5.Version{}) Manager.registerVersion(6, &v6.Version{}) + Manager.registerVersion(7, &v7.Version{}) } diff --git a/config/versions/v1/types.go b/config/versions/v1/types.go index c072e02d..9c6daf9f 100644 --- a/config/versions/v1/types.go +++ b/config/versions/v1/types.go @@ -12,8 +12,11 @@ type PairsManager struct { Pairs FullStore `json:"pairs"` } -// FullStore contains a pair store by asset name -type FullStore map[string]struct { +// FullStore holds all supported asset types with the enabled and available pairs for an exchange. +type FullStore map[string]*PairStore + +// PairStore contains a pair store +type PairStore struct { Enabled string `json:"enabled"` Available string `json:"available"` RequestFormat *v0.PairFormat `json:"requestFormat,omitempty"` diff --git a/config/versions/v2/types.go b/config/versions/v2/types.go new file mode 100644 index 00000000..85705b40 --- /dev/null +++ b/config/versions/v2/types.go @@ -0,0 +1,27 @@ +package v2 + +import ( + v0 "github.com/thrasher-corp/gocryptotrader/config/versions/v0" +) + +// PairsManager contains exchange pair management config +type PairsManager struct { + BypassConfigFormatUpgrades bool `json:"bypassConfigFormatUpgrades"` + RequestFormat *v0.PairFormat `json:"requestFormat,omitempty"` + ConfigFormat *v0.PairFormat `json:"configFormat,omitempty"` + UseGlobalFormat bool `json:"useGlobalFormat,omitempty"` + LastUpdated int64 `json:"lastUpdated,omitempty"` + Pairs FullStore `json:"pairs"` +} + +// FullStore holds all supported asset types with the enabled and available pairs for an exchange. +type FullStore map[string]*PairStore + +// PairStore contains a pair store +type PairStore struct { + AssetEnabled bool `json:"assetEnabled"` + Enabled string `json:"enabled"` + Available string `json:"available"` + RequestFormat *v0.PairFormat `json:"requestFormat,omitempty"` + ConfigFormat *v0.PairFormat `json:"configFormat,omitempty"` +} diff --git a/config/versions/v7/v7.go b/config/versions/v7/v7.go new file mode 100644 index 00000000..54165dac --- /dev/null +++ b/config/versions/v7/v7.go @@ -0,0 +1,92 @@ +package v7 + +import ( + "context" + "encoding/json" //nolint:depguard // Used instead of gct encoding/json so that we can ensure consistent library functionality between versions + "strings" + + "github.com/buger/jsonparser" + v2 "github.com/thrasher-corp/gocryptotrader/config/versions/v2" +) + +// Version is an ExchangeVersion to split GateIO futures into CoinM and USDT margined futures assets +type Version struct{} + +// Exchanges returns just GateIO +func (v *Version) Exchanges() []string { return []string{"GateIO"} } + +// UpgradeExchange split GateIO futures into CoinM and USDT margined futures assets +func (v *Version) UpgradeExchange(_ context.Context, e []byte) ([]byte, error) { + fs := v2.FullStore{"coinmarginedfutures": {}, "usdtmarginedfutures": {}} + fsJSON, _, _, err := jsonparser.Get(e, "currencyPairs", "pairs") + if err != nil { + return e, err + } + if err := json.Unmarshal(fsJSON, &fs); err != nil { + return e, err + } + f, ok := fs["futures"] + if !ok { + // Version.UpgradeExchange should only split futures into CoinM and USDT + // If the exchange config doesn't have futures, we have nothing to do + return e, nil + } + for p := range strings.SplitSeq(f.Available, ",") { + where := "usdtmarginedfutures" + if strings.HasSuffix(p, "USD") { + where = "coinmarginedfutures" + } + if fs[where].Available != "" { + fs[where].Available += "," + } + fs[where].Available += p + } + for p := range strings.SplitSeq(f.Enabled, ",") { + where := "usdtmarginedfutures" + if strings.HasSuffix(p, "USD") { + where = "coinmarginedfutures" + } + if fs[where].Enabled != "" { + fs[where].Enabled += "," + } + fs[where].Enabled += p + } + fs["usdtmarginedfutures"].AssetEnabled = f.AssetEnabled + fs["coinmarginedfutures"].AssetEnabled = f.AssetEnabled + delete(fs, "futures") + val, err := json.Marshal(fs) + if err == nil { + e, err = jsonparser.Set(e, val, "currencyPairs", "pairs") + } + return e, err +} + +// DowngradeExchange will merge GateIO CoinM and USDT margined futures assets into futures +func (v *Version) DowngradeExchange(_ context.Context, e []byte) ([]byte, error) { + fs := v2.FullStore{"futures": {}, "coinmarginedfutures": {}, "usdtmarginedfutures": {}} + fsJSON, _, _, err := jsonparser.Get(e, "currencyPairs", "pairs") + if err != nil { + return e, err + } + if err := json.Unmarshal(fsJSON, &fs); err != nil { + return e, err + } + fs["futures"].Enabled = fs["coinmarginedfutures"].Enabled + if fs["futures"].Enabled != "" { + fs["futures"].Enabled += "," + } + fs["futures"].Enabled += fs["usdtmarginedfutures"].Enabled + fs["futures"].Available = fs["coinmarginedfutures"].Available + if fs["futures"].Available != "" { + fs["futures"].Available += "," + } + fs["futures"].Available += fs["usdtmarginedfutures"].Available + fs["futures"].AssetEnabled = fs["usdtmarginedfutures"].AssetEnabled || fs["coinmarginedfutures"].AssetEnabled + delete(fs, "coinmarginedfutures") + delete(fs, "usdtmarginedfutures") + val, err := json.Marshal(fs) + if err == nil { + e, err = jsonparser.Set(e, val, "currencyPairs", "pairs") + } + return e, err +} diff --git a/config/versions/v7/v7_test.go b/config/versions/v7/v7_test.go new file mode 100644 index 00000000..5dc48e37 --- /dev/null +++ b/config/versions/v7/v7_test.go @@ -0,0 +1,62 @@ +package v7_test + +import ( + "context" + "encoding/json" //nolint:depguard // Used instead of gct encoding/json so that we can ensure consistent library functionality between versions + "testing" + + "github.com/buger/jsonparser" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v7 "github.com/thrasher-corp/gocryptotrader/config/versions/v7" +) + +func TestExchanges(t *testing.T) { + t.Parallel() + assert.Equal(t, []string{"GateIO"}, new(v7.Version).Exchanges()) +} + +func TestUpgrade(t *testing.T) { + t.Parallel() + + in := []byte(`{"name":"GateIO","currencyPairs":{}}`) + _, err := new(v7.Version).UpgradeExchange(context.Background(), in) + require.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) + + in = []byte(`{"name":"GateIO","currencyPairs":{"pairs":14}}`) + _, err = new(v7.Version).UpgradeExchange(context.Background(), in) + require.Error(t, err) + var jsonErr *json.UnmarshalTypeError + assert.ErrorAs(t, err, &jsonErr, "UpgradeExchange should return a json.UnmarshalTypeError on bad type for pairs") + + in = []byte(`{"name":"GateIO","currencyPairs":{"pairs":{"spot":{"assetEnabled":true,"enabled":"BTC-USDT","available":"BTC-USDT"},"futures":{"assetEnabled":true,"enabled":"BTC_USD,BTC_USDT,ETH_USDT","available":"BTC_USD,BTC_USDT,ETH_USDT,LTC_USDT"}}}}`) + out, err := new(v7.Version).UpgradeExchange(context.Background(), in) + require.NoError(t, err) + exp := `{"name":"GateIO","currencyPairs":{"pairs":{"coinmarginedfutures":{"assetEnabled":true,"enabled":"BTC_USD","available":"BTC_USD"},"spot":{"assetEnabled":true,"enabled":"BTC-USDT","available":"BTC-USDT"},"usdtmarginedfutures":{"assetEnabled":true,"enabled":"BTC_USDT,ETH_USDT","available":"BTC_USDT,ETH_USDT,LTC_USDT"}}}}` + assert.Equal(t, exp, string(out)) + + out, err = new(v7.Version).UpgradeExchange(context.Background(), out) + require.NoError(t, err) + assert.Equal(t, exp, string(out), "UpgradeExchange without futures should not alter the new entries") +} + +func TestDowngrade(t *testing.T) { + t.Parallel() + + in := []byte(`{"name":"GateIO","currencyPairs":{}}`) + _, err := new(v7.Version).DowngradeExchange(context.Background(), in) + require.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) + + in = []byte(`{"name":"GateIO","currencyPairs":{"pairs":14}}`) + _, err = new(v7.Version).DowngradeExchange(context.Background(), in) + require.Error(t, err) + var jsonErr *json.UnmarshalTypeError + assert.ErrorAs(t, err, &jsonErr) + + in = []byte(`{"name":"GateIO","currencyPairs":{"pairs":{"spot":{"assetEnabled":true,"enabled":"BTC-USDT","available":"BTC-USDT,WIF-USDT"},"coinmarginedfutures":{"assetEnabled":true,"enabled":"BTC_USD","available":"BTC_USD"},"usdtmarginedfutures":{"assetEnabled":true,"enabled":"BTC_USDT,ETH_USDT","available":"BTC_USDT,ETH_USDT,LTC_USDT"}}}}`) + out, err := new(v7.Version).DowngradeExchange(context.Background(), in) + require.NoError(t, err) + + exp := `{"name":"GateIO","currencyPairs":{"pairs":{"futures":{"assetEnabled":true,"enabled":"BTC_USD,BTC_USDT,ETH_USDT","available":"BTC_USD,BTC_USDT,ETH_USDT,LTC_USDT"},"spot":{"assetEnabled":true,"enabled":"BTC-USDT","available":"BTC-USDT,WIF-USDT"}}}}` + assert.Equal(t, exp, string(out)) +} diff --git a/currency/pair.go b/currency/pair.go index 3355731f..f6829836 100644 --- a/currency/pair.go +++ b/currency/pair.go @@ -70,10 +70,7 @@ func NewPairWithDelimiter(base, quote, delimiter string) Pair { // with or without delimiter func NewPairFromString(currencyPair string) (Pair, error) { if len(currencyPair) < 3 { - return EMPTYPAIR, - fmt.Errorf("%w from %s string too short to be a currency pair", - errCannotCreatePair, - currencyPair) + return EMPTYPAIR, fmt.Errorf("%w from %s string too short to be a currency pair", errCannotCreatePair, currencyPair) } for x := range currencyPair { diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index 00517675..e5cbff13 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -29,9 +29,6 @@ const ( gateioFuturesLiveTradingAlternative = "https://fx-api.gateio.ws/" + gateioAPIVersion gateioAPIVersion = "api/v4/" tradeBaseURL = "https://www.gate.io/" - tradeSpot = "trade/" - tradeFutures = "futures/usdt/" - tradeDelivery = "futures-delivery/usdt/" // SubAccount Endpoints subAccounts = "sub_accounts" @@ -140,7 +137,7 @@ var ( errInvalidOrderSize = errors.New("invalid order size") errInvalidOrderID = errors.New("invalid order id") errInvalidAmount = errors.New("invalid amount") - errInvalidOrEmptySubaccount = errors.New("invalid or empty subaccount") + errInvalidSubAccount = errors.New("invalid or empty subaccount") errInvalidTransferDirection = errors.New("invalid transfer direction") errDifferentAccount = errors.New("account type must be identical for all orders") errInvalidPrice = errors.New("invalid price") @@ -165,7 +162,8 @@ var ( errMultipleOrders = errors.New("multiple orders passed") errMissingWithdrawalID = errors.New("missing withdrawal ID") errInvalidSubAccountUserID = errors.New("sub-account user id is required") - errCannotParseSettlementCurrency = errors.New("cannot derive settlement currency") + errInvalidSettlementQuote = errors.New("symbol quote currency does not match asset settlement currency") + errInvalidSettlementBase = errors.New("symbol base currency does not match asset settlement currency") errMissingAPIKey = errors.New("missing API key information") errInvalidTextValue = errors.New("invalid text value, requires prefix `t-`") ) @@ -1185,7 +1183,7 @@ func (g *Gateio) SubAccountTransfer(ctx context.Context, arg SubAccountTransferP return currency.ErrCurrencyCodeEmpty } if arg.SubAccount == "" { - return errInvalidOrEmptySubaccount + return errInvalidSubAccount } arg.Direction = strings.ToLower(arg.Direction) if arg.Direction != "to" && arg.Direction != "from" { @@ -1194,8 +1192,10 @@ func (g *Gateio) SubAccountTransfer(ctx context.Context, arg SubAccountTransferP if arg.Amount <= 0 { return errInvalidAmount } - if arg.SubAccountType != "" && arg.SubAccountType != asset.Spot.String() && arg.SubAccountType != asset.Futures.String() && arg.SubAccountType != asset.CrossMargin.String() { - return fmt.Errorf("%v; only %v,%v, and %v are allowed", asset.ErrNotSupported, asset.Spot, asset.Futures, asset.CrossMargin) + switch arg.SubAccountType { + case "", "spot", "futures", "delivery": + default: + return fmt.Errorf("%w `%s` for SubAccountTransfer; Supported: [spot, futures, delivery]", asset.ErrNotSupported, arg.SubAccountType) } return g.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, walletSubAccountTransferEPL, http.MethodPost, walletSubAccountTransfer, nil, &arg, nil) } @@ -3696,19 +3696,6 @@ func (g *Gateio) GetUnderlyingFromCurrencyPair(p currency.Pair) (currency.Pair, return currency.Pair{Base: currency.NewCode(ccies[0]), Delimiter: currency.UnderscoreDelimiter, Quote: currency.NewCode(ccies[1])}, nil } -func getSettlementFromCurrency(currencyPair currency.Pair) (settlement currency.Code, err error) { - quote := currencyPair.Quote.Upper().String() - - switch { - case strings.HasPrefix(quote, currency.USDT.String()): - return currency.USDT, nil - case strings.HasPrefix(quote, currency.USD.String()): - return currency.BTC, nil - default: - return currency.EMPTYCODE, fmt.Errorf("%w %v", errCannotParseSettlementCurrency, currencyPair) - } -} - // GetAccountDetails retrieves account details func (g *Gateio) GetAccountDetails(ctx context.Context) (*AccountDetails, error) { var resp *AccountDetails diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index be56e3e9..e4c2e503 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -8,7 +8,6 @@ import ( "log" "os" "slices" - "strconv" "sync" "testing" "time" @@ -69,104 +68,40 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) _, err := g.CancelAllOrders(t.Context(), nil) - if !errors.Is(err, order.ErrCancelOrderIsNil) { - t.Error(err) + require.ErrorIs(t, err, order.ErrCancelOrderIsNil) + + r := &order.Cancel{ + OrderID: "1", + AccountID: "1", } - orderCancellation := &order.Cancel{ - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.Options), - AssetType: asset.Options, - } - _, err = g.CancelAllOrders(t.Context(), orderCancellation) - if err != nil { - t.Error(err) - } - orderCancellation.AssetType = asset.Spot - orderCancellation.Pair = getPair(t, asset.Spot) - _, err = g.CancelAllOrders(t.Context(), orderCancellation) - if err != nil { - t.Error(err) - } - orderCancellation.Pair = currency.EMPTYPAIR - orderCancellation.AssetType = asset.Margin - _, err = g.CancelAllOrders(t.Context(), orderCancellation) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Error(err) - } - orderCancellation.Pair = getPair(t, asset.Margin) - _, err = g.CancelAllOrders(t.Context(), orderCancellation) - if err != nil { - t.Error(err) - } - orderCancellation.Pair = currency.EMPTYPAIR - orderCancellation.AssetType = asset.CrossMargin - _, err = g.CancelAllOrders(t.Context(), orderCancellation) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Error(err) - } - orderCancellation.Pair = getPair(t, asset.CrossMargin) - _, err = g.CancelAllOrders(t.Context(), orderCancellation) - if err != nil { - t.Error(err) - } - orderCancellation.Pair = currency.EMPTYPAIR - orderCancellation.AssetType = asset.Futures - _, err = g.CancelAllOrders(t.Context(), orderCancellation) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Error(err) - } - orderCancellation.Pair = getPair(t, asset.Futures) - _, err = g.CancelAllOrders(t.Context(), orderCancellation) - if err != nil { - t.Error(err) - } - orderCancellation.Pair = currency.EMPTYPAIR - orderCancellation.AssetType = asset.DeliveryFutures - _, err = g.CancelAllOrders(t.Context(), orderCancellation) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Error(err) - } - orderCancellation.Pair = getPair(t, asset.DeliveryFutures) - _, err = g.CancelAllOrders(t.Context(), orderCancellation) - if err != nil { - t.Error(err) + + for _, a := range g.GetAssetTypes(false) { + r.AssetType = a + r.Pair = currency.EMPTYPAIR + _, err = g.CancelAllOrders(t.Context(), r) + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) + + r.Pair = getPair(t, a) + _, err = g.CancelAllOrders(t.Context(), r) + require.NoError(t, err) } } func TestGetAccountInfo(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - _, err := g.UpdateAccountInfo(t.Context(), asset.Spot) - if err != nil { - t.Error("GetAccountInfo() error", err) - } - if _, err := g.UpdateAccountInfo(t.Context(), asset.Margin); err != nil { - t.Errorf("%s UpdateAccountInfo() error %v", g.Name, err) - } - if _, err := g.UpdateAccountInfo(t.Context(), asset.CrossMargin); err != nil { - t.Errorf("%s UpdateAccountInfo() error %v", g.Name, err) - } - if _, err := g.UpdateAccountInfo(t.Context(), asset.Options); err != nil { - t.Errorf("%s UpdateAccountInfo() error %v", g.Name, err) - } - if _, err := g.UpdateAccountInfo(t.Context(), asset.Futures); err != nil { - t.Errorf("%s UpdateAccountInfo() error %v", g.Name, err) - } - if _, err := g.UpdateAccountInfo(t.Context(), asset.DeliveryFutures); err != nil { - t.Errorf("%s UpdateAccountInfo() error %v", g.Name, err) + for _, a := range g.GetAssetTypes(false) { + _, err := g.UpdateAccountInfo(t.Context(), a) + assert.NoErrorf(t, err, "UpdateAccountInfo should not error for asset %s", a) } } func TestWithdraw(t *testing.T) { t.Parallel() + sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) cryptocurrencyChains, err := g.GetAvailableTransferChains(t.Context(), currency.BTC) - if err != nil { - t.Fatal(err) - } else if len(cryptocurrencyChains) == 0 { - t.Fatal("no crypto currency chain available") - } + require.NoError(t, err, "GetAvailableTransferChains must not error") + require.NotEmpty(t, cryptocurrencyChains, "GetAvailableTransferChains must return some chains") withdrawCryptoRequest := withdraw.Request{ Exchange: g.Name, Amount: 1, @@ -177,39 +112,16 @@ func TestWithdraw(t *testing.T) { Chain: cryptocurrencyChains[0], }, } - sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - if _, err = g.WithdrawCryptocurrencyFunds(t.Context(), &withdrawCryptoRequest); err != nil { - t.Errorf("%s WithdrawCryptocurrencyFunds() error: %v", g.Name, err) - } + _, err = g.WithdrawCryptocurrencyFunds(t.Context(), &withdrawCryptoRequest) + require.NoError(t, err) } func TestGetOrderInfo(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - _, err := g.GetOrderInfo(t.Context(), - "917591554", getPair(t, asset.Spot), asset.Spot) - if err != nil { - t.Errorf("GetOrderInfo() %v", err) - } - _, err = g.GetOrderInfo(t.Context(), "917591554", getPair(t, asset.Options), asset.Options) - if err != nil { - t.Errorf("GetOrderInfo() %v", err) - } - _, err = g.GetOrderInfo(t.Context(), "917591554", getPair(t, asset.Margin), asset.Margin) - if err != nil { - t.Errorf("GetOrderInfo() %v", err) - } - _, err = g.GetOrderInfo(t.Context(), "917591554", getPair(t, asset.CrossMargin), asset.CrossMargin) - if err != nil { - t.Errorf("GetOrderInfo() %v", err) - } - _, err = g.GetOrderInfo(t.Context(), "917591554", getPair(t, asset.Futures), asset.Futures) - if err != nil { - t.Errorf("GetOrderInfo() %v", err) - } - _, err = g.GetOrderInfo(t.Context(), "917591554", getPair(t, asset.DeliveryFutures), asset.DeliveryFutures) - if err != nil { - t.Errorf("GetOrderInfo() %v", err) + for _, a := range g.GetAssetTypes(false) { + _, err := g.GetOrderInfo(t.Context(), "917591554", getPair(t, a), a) + require.NoErrorf(t, err, "GetOrderInfo must not error for asset %s", a) } } @@ -267,16 +179,6 @@ func TestGetOrderbook(t *testing.T) { t.Parallel() _, err := g.GetOrderbook(t.Context(), getPair(t, asset.Spot).String(), "0.1", 10, false) assert.NoError(t, err, "GetOrderbook should not error") - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - assert.NoError(t, err, "getSettlementFromCurrency should not error") - _, err = g.GetFuturesOrderbook(t.Context(), settle, getPair(t, asset.Futures).String(), "", 10, false) - assert.NoError(t, err, "GetFuturesOrderbook should not error") - settle, err = getSettlementFromCurrency(getPair(t, asset.DeliveryFutures)) - assert.NoError(t, err, "getSettlementFromCurrency should not error") - _, err = g.GetDeliveryOrderbook(t.Context(), settle, "0.1", getPair(t, asset.DeliveryFutures), 10, false) - assert.NoError(t, err, "GetDeliveryOrderbook should not error") - _, err = g.GetOptionsOrderbook(t.Context(), getPair(t, asset.Options), "0.1", 10, false) - assert.NoError(t, err, "GetOptionsOrderbook should not error") } func TestGetMarketTrades(t *testing.T) { @@ -844,15 +746,20 @@ func TestTransferCurrency(t *testing.T) { func TestSubAccountTransfer(t *testing.T) { t.Parallel() + ctx := t.Context() + req := SubAccountTransferParam{SubAccountType: "index"} + require.ErrorIs(t, g.SubAccountTransfer(ctx, req), currency.ErrCurrencyCodeEmpty) + req.Currency = currency.BTC + require.ErrorIs(t, g.SubAccountTransfer(ctx, req), errInvalidSubAccount) + req.SubAccount = "1337" + require.ErrorIs(t, g.SubAccountTransfer(ctx, req), errInvalidTransferDirection) + req.Direction = "to" + require.ErrorIs(t, g.SubAccountTransfer(ctx, req), errInvalidAmount) + req.Amount = 1.337 + require.ErrorIs(t, g.SubAccountTransfer(ctx, req), asset.ErrNotSupported) sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - if err := g.SubAccountTransfer(t.Context(), SubAccountTransferParam{ - Currency: currency.BTC, - SubAccount: "12222", - Direction: "to", - Amount: 1, - }); err != nil { - t.Errorf("%s SubAccountTransfer() error %v", g.Name, err) - } + req.SubAccountType = "spot" + require.NoError(t, g.SubAccountTransfer(ctx, req)) } func TestGetSubAccountTransferHistory(t *testing.T) { @@ -963,67 +870,67 @@ func TestGetOrderbookOfLendingLoans(t *testing.T) { func TestGetAllFutureContracts(t *testing.T) { t.Parallel() - for _, settlementCurrency := range settlementCurrencies { - if _, err := g.GetAllFutureContracts(t.Context(), settlementCurrency); err != nil { - assert.Errorf(t, err, "GetAllFutureContracts %s should not error", settlementCurrency) - } + + for _, c := range []currency.Code{currency.BTC, currency.USDT} { + _, err := g.GetAllFutureContracts(t.Context(), c) + assert.NoErrorf(t, err, "GetAllFutureContracts %s should not error", c) } } func TestGetFuturesContract(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetFuturesContract(t.Context(), settle, getPair(t, asset.Futures).String()) + _, err := g.GetFuturesContract(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures).String()) + assert.NoError(t, err, "GetFuturesContract should not error") + _, err = g.GetFuturesContract(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures).String()) assert.NoError(t, err, "GetFuturesContract should not error") } func TestGetFuturesOrderbook(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetFuturesOrderbook(t.Context(), settle, getPair(t, asset.Futures).String(), "", 0, false) - assert.NoError(t, err, "GetFuturesOrderbook should not error") + _, err := g.GetFuturesOrderbook(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures).String(), "", 10, false) + assert.NoError(t, err, "GetFuturesOrderbook should not error for CoinMarginedFutures") + _, err = g.GetFuturesOrderbook(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures).String(), "", 10, false) + assert.NoError(t, err, "GetFuturesOrderbook should not error for USDTMarginedFutures") } func TestGetFuturesTradingHistory(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetFuturesTradingHistory(t.Context(), settle, getPair(t, asset.Futures), 0, 0, "", time.Time{}, time.Time{}) - assert.NoError(t, err, "GetFuturesTradingHistory should not error") + _, err := g.GetFuturesTradingHistory(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), 0, 0, "", time.Time{}, time.Time{}) + assert.NoError(t, err, "GetFuturesTradingHistory should not error for CoinMarginedFutures") + _, err = g.GetFuturesTradingHistory(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures), 0, 0, "", time.Time{}, time.Time{}) + assert.NoError(t, err, "GetFuturesTradingHistory should not error for USDTMarginedFutures") } func TestGetFuturesCandlesticks(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetFuturesCandlesticks(t.Context(), settle, getPair(t, asset.Futures).String(), time.Time{}, time.Time{}, 0, kline.OneWeek) - assert.NoError(t, err, "GetFuturesCandlesticks should not error") + _, err := g.GetFuturesCandlesticks(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures).String(), time.Time{}, time.Time{}, 0, kline.OneWeek) + assert.NoError(t, err, "GetFuturesCandlesticks should not error for CoinMarginedFutures") + _, err = g.GetFuturesCandlesticks(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures).String(), time.Time{}, time.Time{}, 0, kline.OneWeek) + assert.NoError(t, err, "GetFuturesCandlesticks should not error for USDTMarginedFutures") } func TestPremiumIndexKLine(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.PremiumIndexKLine(t.Context(), settle, getPair(t, asset.Futures), time.Time{}, time.Time{}, 0, kline.OneWeek) - assert.NoError(t, err, "PremiumIndexKLine should not error") + _, err := g.PremiumIndexKLine(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), time.Time{}, time.Time{}, 0, kline.OneWeek) + assert.NoError(t, err, "PremiumIndexKLine should not error for CoinMarginedFutures") + _, err = g.PremiumIndexKLine(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures), time.Time{}, time.Time{}, 0, kline.OneWeek) + assert.NoError(t, err, "PremiumIndexKLine should not error for USDTMarginedFutures") } func TestGetFutureTickers(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetFuturesTickers(t.Context(), settle, getPair(t, asset.Futures)) - assert.NoError(t, err, "GetFutureTickers should not error") + _, err := g.GetFuturesTickers(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures)) + assert.NoError(t, err, "GetFutureTickers should not error for CoinMarginedFutures") + _, err = g.GetFuturesTickers(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures)) + assert.NoError(t, err, "GetFutureTickers should not error for USDTMarginedFutures") } func TestGetFutureFundingRates(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetFutureFundingRates(t.Context(), settle, getPair(t, asset.Futures), 0) - assert.NoError(t, err, "GetFutureFundingRates should not error") + _, err := g.GetFutureFundingRates(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), 0) + assert.NoError(t, err, "GetFutureFundingRates should not error for CoinMarginedFutures") + _, err = g.GetFutureFundingRates(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures), 0) + assert.NoError(t, err, "GetFutureFundingRates should not error for USDTMarginedFutures") } func TestGetFuturesInsuranceBalanceHistory(t *testing.T) { @@ -1034,10 +941,10 @@ func TestGetFuturesInsuranceBalanceHistory(t *testing.T) { func TestGetFutureStats(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetFutureStats(t.Context(), settle, getPair(t, asset.Futures), time.Time{}, 0, 0) - assert.NoError(t, err, "GetFutureStats should not error") + _, err := g.GetFutureStats(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), time.Time{}, 0, 0) + assert.NoError(t, err, "GetFutureStats should not error for CoinMarginedFutures") + _, err = g.GetFutureStats(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures), time.Time{}, 0, 0) + assert.NoError(t, err, "GetFutureStats should not error for USDTMarginedFutures") } func TestGetIndexConstituent(t *testing.T) { @@ -1048,10 +955,10 @@ func TestGetIndexConstituent(t *testing.T) { func TestGetLiquidationHistory(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetLiquidationHistory(t.Context(), settle, getPair(t, asset.Futures), time.Time{}, time.Time{}, 0) - assert.NoError(t, err, "GetLiquidationHistory should not error") + _, err := g.GetLiquidationHistory(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), time.Time{}, time.Time{}, 0) + assert.NoError(t, err, "GetLiquidationHistory should not error for CoinMarginedFutures") + _, err = g.GetLiquidationHistory(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures), time.Time{}, time.Time{}, 0) + assert.NoError(t, err, "GetLiquidationHistory should not error for USDTMarginedFutures") } func TestQueryFuturesAccount(t *testing.T) { @@ -1085,42 +992,40 @@ func TestGetSinglePosition(t *testing.T) { func TestUpdateFuturesPositionMargin(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.UpdateFuturesPositionMargin(t.Context(), settle, 0.01, getPair(t, asset.Futures)) - assert.NoError(t, err, "UpdateFuturesPositionMargin should not error") + _, err := g.UpdateFuturesPositionMargin(t.Context(), currency.BTC, 0.01, getPair(t, asset.CoinMarginedFutures)) + assert.NoError(t, err, "UpdateFuturesPositionMargin should not error for CoinMarginedFutures") + _, err = g.UpdateFuturesPositionMargin(t.Context(), currency.USDT, 0.01, getPair(t, asset.USDTMarginedFutures)) + assert.NoError(t, err, "UpdateFuturesPositionMargin should not error for USDTMarginedFutures") } func TestUpdateFuturesPositionLeverage(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.UpdateFuturesPositionLeverage(t.Context(), settle, getPair(t, asset.Futures), 1, 0) - assert.NoError(t, err, "UpdateFuturesPositionLeverage should not error") + _, err := g.UpdateFuturesPositionLeverage(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), 1, 0) + assert.NoError(t, err, "UpdateFuturesPositionLeverage should not error for CoinMarginedFutures") + _, err = g.UpdateFuturesPositionLeverage(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures), 1, 0) + assert.NoError(t, err, "UpdateFuturesPositionLeverage should not error for USDTMarginedFutures") } func TestUpdateFuturesPositionRiskLimit(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.UpdateFuturesPositionRiskLimit(t.Context(), settle, getPair(t, asset.Futures), 10) - assert.NoError(t, err, "UpdateFuturesPositionRiskLimit should not error") + _, err := g.UpdateFuturesPositionRiskLimit(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), 10) + assert.NoError(t, err, "UpdateFuturesPositionRiskLimit should not error for CoinMarginedFutures") + _, err = g.UpdateFuturesPositionRiskLimit(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures), 10) + assert.NoError(t, err, "UpdateFuturesPositionRiskLimit should not error for USDTMarginedFutures") } func TestPlaceDeliveryOrder(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.DeliveryFutures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.PlaceDeliveryOrder(t.Context(), &ContractOrderCreateParams{ + _, err := g.PlaceDeliveryOrder(t.Context(), &ContractOrderCreateParams{ Contract: getPair(t, asset.DeliveryFutures), Size: 6024, Iceberg: 0, Price: "3765", Text: "t-my-custom-id", - Settle: settle, + Settle: currency.USDT, TimeInForce: gtcTIF, }) assert.NoError(t, err, "CreateDeliveryOrder should not error") @@ -1129,18 +1034,14 @@ func TestPlaceDeliveryOrder(t *testing.T) { func TestGetDeliveryOrders(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - settle, err := getSettlementFromCurrency(getPair(t, asset.DeliveryFutures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetDeliveryOrders(t.Context(), getPair(t, asset.DeliveryFutures), statusOpen, settle, "", 0, 0, 1) + _, err := g.GetDeliveryOrders(t.Context(), getPair(t, asset.DeliveryFutures), "open", currency.USDT, "", 0, 0, 1) assert.NoError(t, err, "GetDeliveryOrders should not error") } func TestCancelMultipleDeliveryOrders(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.DeliveryFutures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.CancelMultipleDeliveryOrders(t.Context(), getPair(t, asset.DeliveryFutures), "ask", settle) + _, err := g.CancelMultipleDeliveryOrders(t.Context(), getPair(t, asset.DeliveryFutures), "ask", currency.USDT) assert.NoError(t, err, "CancelMultipleDeliveryOrders should not error") } @@ -1209,16 +1110,14 @@ func TestGetDeliveryPriceTriggeredOrder(t *testing.T) { func TestGetDeliveryAllAutoOrder(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - _, err := g.GetDeliveryAllAutoOrder(t.Context(), statusOpen, currency.USDT, getPair(t, asset.DeliveryFutures), 0, 1) + _, err := g.GetDeliveryAllAutoOrder(t.Context(), "open", currency.USDT, getPair(t, asset.DeliveryFutures), 0, 1) assert.NoError(t, err, "GetDeliveryAllAutoOrder should not error") } func TestCancelAllDeliveryPriceTriggeredOrder(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.DeliveryFutures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.CancelAllDeliveryPriceTriggeredOrder(t.Context(), settle, getPair(t, asset.DeliveryFutures)) + _, err := g.CancelAllDeliveryPriceTriggeredOrder(t.Context(), currency.USDT, getPair(t, asset.DeliveryFutures)) assert.NoError(t, err, "CancelAllDeliveryPriceTriggeredOrder should not error") } @@ -1246,52 +1145,50 @@ func TestEnableOrDisableDualMode(t *testing.T) { func TestRetrivePositionDetailInDualMode(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.RetrivePositionDetailInDualMode(t.Context(), settle, getPair(t, asset.Futures)) - assert.NoError(t, err, "RetrivePositionDetailInDualMode should not error") + _, err := g.RetrivePositionDetailInDualMode(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures)) + assert.NoError(t, err, "RetrivePositionDetailInDualMode should not error for CoinMarginedFutures") + _, err = g.RetrivePositionDetailInDualMode(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures)) + assert.NoError(t, err, "RetrivePositionDetailInDualMode should not error for USDTMarginedFutures") } func TestUpdatePositionMarginInDualMode(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.UpdatePositionMarginInDualMode(t.Context(), settle, getPair(t, asset.Futures), 0.001, "dual_long") - assert.NoError(t, err, "UpdatePositionMarginInDualMode should not error") + _, err := g.UpdatePositionMarginInDualMode(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), 0.001, "dual_long") + assert.NoError(t, err, "UpdatePositionMarginInDualMode should not error for CoinMarginedFutures") + _, err = g.UpdatePositionMarginInDualMode(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures), 0.001, "dual_long") + assert.NoError(t, err, "UpdatePositionMarginInDualMode should not error for USDTMarginedFutures") } func TestUpdatePositionLeverageInDualMode(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.UpdatePositionLeverageInDualMode(t.Context(), settle, getPair(t, asset.Futures), 0.001, 0.001) - assert.NoError(t, err, "UpdatePositionLeverageInDualMode should not error") + _, err := g.UpdatePositionLeverageInDualMode(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), 0.001, 0.001) + assert.NoError(t, err, "UpdatePositionLeverageInDualMode should not error for CoinMarginedFutures") + _, err = g.UpdatePositionLeverageInDualMode(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures), 0.001, 0.001) + assert.NoError(t, err, "UpdatePositionLeverageInDualMode should not error for USDTMarginedFutures") } func TestUpdatePositionRiskLimitInDualMode(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.UpdatePositionRiskLimitInDualMode(t.Context(), settle, getPair(t, asset.Futures), 10) - assert.NoError(t, err, "UpdatePositionRiskLimitInDualMode should not error") + _, err := g.UpdatePositionRiskLimitInDualMode(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), 10) + assert.NoError(t, err, "UpdatePositionRiskLimitInDualMode should not error for CoinMarginedFutures") + _, err = g.UpdatePositionRiskLimitInDualMode(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures), 10) + assert.NoError(t, err, "UpdatePositionRiskLimitInDualMode should not error for USDTMarginedFutures") } func TestPlaceFuturesOrder(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.PlaceFuturesOrder(t.Context(), &ContractOrderCreateParams{ - Contract: getPair(t, asset.Futures), + _, err := g.PlaceFuturesOrder(t.Context(), &ContractOrderCreateParams{ + Contract: getPair(t, asset.CoinMarginedFutures), Size: 6024, Iceberg: 0, Price: "3765", TimeInForce: "gtc", Text: "t-my-custom-id", - Settle: settle, + Settle: currency.BTC, }) assert.NoError(t, err, "PlaceFuturesOrder should not error") } @@ -1299,14 +1196,14 @@ func TestPlaceFuturesOrder(t *testing.T) { func TestGetFuturesOrders(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - _, err := g.GetFuturesOrders(t.Context(), currency.NewPair(currency.BTC, currency.USD), statusOpen, "", currency.BTC, 0, 0, 1) + _, err := g.GetFuturesOrders(t.Context(), currency.NewPair(currency.BTC, currency.USD), "open", "", currency.BTC, 0, 0, 1) assert.NoError(t, err, "GetFuturesOrders should not error") } func TestCancelMultipleFuturesOpenOrders(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - _, err := g.CancelMultipleFuturesOpenOrders(t.Context(), getPair(t, asset.Futures), "ask", currency.USDT) + _, err := g.CancelMultipleFuturesOpenOrders(t.Context(), getPair(t, asset.USDTMarginedFutures), "ask", currency.USDT) assert.NoError(t, err, "CancelMultipleFuturesOpenOrders should not error") } @@ -1327,20 +1224,18 @@ func TestCancelFuturesPriceTriggeredOrder(t *testing.T) { func TestPlaceBatchFuturesOrders(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.PlaceBatchFuturesOrders(t.Context(), currency.BTC, []ContractOrderCreateParams{ + _, err := g.PlaceBatchFuturesOrders(t.Context(), currency.BTC, []ContractOrderCreateParams{ { - Contract: getPair(t, asset.Futures), + Contract: getPair(t, asset.CoinMarginedFutures), Size: 6024, Iceberg: 0, Price: "3765", TimeInForce: "gtc", Text: "t-my-custom-id", - Settle: settle, + Settle: currency.BTC, }, { - Contract: currency.NewPair(currency.BTC, currency.USDT), + Contract: getPair(t, asset.CoinMarginedFutures), Size: 232, Iceberg: 0, Price: "376225", @@ -1378,21 +1273,21 @@ func TestAmendFuturesOrder(t *testing.T) { func TestGetMyFuturesTradingHistory(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - _, err := g.GetMyFuturesTradingHistory(t.Context(), currency.BTC, "", "", getPair(t, asset.Futures), 0, 0, 0) + _, err := g.GetMyFuturesTradingHistory(t.Context(), currency.BTC, "", "", getPair(t, asset.CoinMarginedFutures), 0, 0, 0) assert.NoError(t, err, "GetMyFuturesTradingHistory should not error") } func TestGetFuturesPositionCloseHistory(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - _, err := g.GetFuturesPositionCloseHistory(t.Context(), currency.BTC, getPair(t, asset.Futures), 0, 0, time.Time{}, time.Time{}) + _, err := g.GetFuturesPositionCloseHistory(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), 0, 0, time.Time{}, time.Time{}) assert.NoError(t, err, "GetFuturesPositionCloseHistory should not error") } func TestGetFuturesLiquidationHistory(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - _, err := g.GetFuturesLiquidationHistory(t.Context(), currency.BTC, getPair(t, asset.Futures), 0, time.Time{}) + _, err := g.GetFuturesLiquidationHistory(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures), 0, time.Time{}) assert.NoError(t, err, "GetFuturesLiquidationHistory should not error") } @@ -1408,47 +1303,42 @@ func TestCountdownCancelOrders(t *testing.T) { func TestCreatePriceTriggeredFuturesOrder(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.CreatePriceTriggeredFuturesOrder(t.Context(), settle, &FuturesPriceTriggeredOrderParam{ - Initial: FuturesInitial{ - Price: 1234., - Size: 2, - Contract: getPair(t, asset.Futures), - }, - Trigger: FuturesTrigger{ - Rule: 1, - OrderType: "close-short-position", - }, - }) - assert.NoError(t, err, "CreatePriceTriggeredFuturesOrder should not error") - _, err = g.CreatePriceTriggeredFuturesOrder(t.Context(), settle, &FuturesPriceTriggeredOrderParam{ - Initial: FuturesInitial{ - Price: 1234., - Size: 1, - Contract: getPair(t, asset.Futures), - }, - Trigger: FuturesTrigger{ - Rule: 1, - }, - }) - assert.NoError(t, err, "CreatePriceTriggeredFuturesOrder should not error") + for _, tc := range []struct { + c currency.Code + a asset.Item + }{ + {currency.BTC, asset.CoinMarginedFutures}, + {currency.USDT, asset.USDTMarginedFutures}, + } { + _, err := g.CreatePriceTriggeredFuturesOrder(t.Context(), tc.c, &FuturesPriceTriggeredOrderParam{ + Initial: FuturesInitial{ + Price: 1234., + Size: 2, + Contract: getPair(t, tc.a), + }, + Trigger: FuturesTrigger{ + Rule: 1, + OrderType: "close-short-position", + }, + }) + assert.NoErrorf(t, err, "CreatePriceTriggeredFuturesOrder should not error for settlement currency: %s, asset: %s", tc.c, tc.a) + } } func TestListAllFuturesAutoOrders(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - _, err := g.ListAllFuturesAutoOrders(t.Context(), statusOpen, currency.BTC, currency.EMPTYPAIR, 0, 0) + _, err := g.ListAllFuturesAutoOrders(t.Context(), "open", currency.BTC, currency.EMPTYPAIR, 0, 0) assert.NoError(t, err, "ListAllFuturesAutoOrders should not error") } func TestCancelAllFuturesOpenOrders(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.Futures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.CancelAllFuturesOpenOrders(t.Context(), settle, getPair(t, asset.Futures)) - assert.NoError(t, err, "CancelAllFuturesOpenOrders should not error") + _, err := g.CancelAllFuturesOpenOrders(t.Context(), currency.BTC, getPair(t, asset.CoinMarginedFutures)) + assert.NoError(t, err, "CancelAllFuturesOpenOrders should not error for CoinMarginedFutures") + _, err = g.CancelAllFuturesOpenOrders(t.Context(), currency.USDT, getPair(t, asset.USDTMarginedFutures)) + assert.NoError(t, err, "CancelAllFuturesOpenOrders should not error for USDTMarginedFutures") } func TestGetAllDeliveryContracts(t *testing.T) { @@ -1466,9 +1356,7 @@ func TestGetAllDeliveryContracts(t *testing.T) { func TestGetDeliveryContract(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.DeliveryFutures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetDeliveryContract(t.Context(), settle, getPair(t, asset.DeliveryFutures)) + _, err := g.GetDeliveryContract(t.Context(), currency.USDT, getPair(t, asset.DeliveryFutures)) assert.NoError(t, err, "GetDeliveryContract should not error") } @@ -1480,25 +1368,19 @@ func TestGetDeliveryOrderbook(t *testing.T) { func TestGetDeliveryTradingHistory(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.DeliveryFutures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetDeliveryTradingHistory(t.Context(), settle, "", getPair(t, asset.DeliveryFutures), 0, time.Time{}, time.Time{}) + _, err := g.GetDeliveryTradingHistory(t.Context(), currency.USDT, "", getPair(t, asset.DeliveryFutures), 0, time.Time{}, time.Time{}) assert.NoError(t, err, "GetDeliveryTradingHistory should not error") } func TestGetDeliveryFuturesCandlesticks(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.DeliveryFutures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetDeliveryFuturesCandlesticks(t.Context(), settle, getPair(t, asset.DeliveryFutures), time.Time{}, time.Time{}, 0, kline.OneWeek) + _, err := g.GetDeliveryFuturesCandlesticks(t.Context(), currency.USDT, getPair(t, asset.DeliveryFutures), time.Time{}, time.Time{}, 0, kline.OneWeek) assert.NoError(t, err, "GetDeliveryFuturesCandlesticks should not error") } func TestGetDeliveryFutureTickers(t *testing.T) { t.Parallel() - settle, err := getSettlementFromCurrency(getPair(t, asset.DeliveryFutures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.GetDeliveryFutureTickers(t.Context(), settle, getPair(t, asset.DeliveryFutures)) + _, err := g.GetDeliveryFutureTickers(t.Context(), currency.USDT, getPair(t, asset.DeliveryFutures)) assert.NoError(t, err, "GetDeliveryFutureTickers should not error") } @@ -1541,9 +1423,7 @@ func TestUpdateDeliveryPositionMargin(t *testing.T) { _, err := g.UpdateDeliveryPositionMargin(t.Context(), currency.EMPTYCODE, 0.001, currency.Pair{}) assert.ErrorIs(t, err, errEmptyOrInvalidSettlementCurrency) sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - settle, err := getSettlementFromCurrency(getPair(t, asset.DeliveryFutures)) - require.NoError(t, err, "getSettlementFromCurrency must not error") - _, err = g.UpdateDeliveryPositionMargin(t.Context(), settle, 0.001, getPair(t, asset.DeliveryFutures)) + _, err = g.UpdateDeliveryPositionMargin(t.Context(), currency.USDT, 0.001, getPair(t, asset.DeliveryFutures)) assert.NoError(t, err, "UpdateDeliveryPositionMargin should not error") } @@ -1812,9 +1692,8 @@ func TestCancelWithdrawalWithSpecifiedID(t *testing.T) { func TestGetOptionsOrderbook(t *testing.T) { t.Parallel() - if _, err := g.GetOptionsOrderbook(t.Context(), getPair(t, asset.Options), "0.1", 9, true); err != nil { - t.Errorf("%s GetOptionsFuturesOrderbooks() error %v", g.Name, err) - } + _, err := g.GetOptionsOrderbook(t.Context(), getPair(t, asset.Options), "0.1", 9, true) + assert.NoError(t, err, "GetOptionsOrderbook should not error") } func TestGetOptionsTickers(t *testing.T) { @@ -1884,75 +1763,32 @@ func TestGetSingleSubAccount(t *testing.T) { func TestFetchTradablePairs(t *testing.T) { t.Parallel() - _, err := g.FetchTradablePairs(t.Context(), asset.DeliveryFutures) - if err != nil { - t.Errorf("%s FetchTradablePairs() error %v", g.Name, err) - } - if _, err = g.FetchTradablePairs(t.Context(), asset.Options); err != nil { - t.Errorf("%s FetchTradablePairs() error %v", g.Name, err) - } - _, err = g.FetchTradablePairs(t.Context(), asset.Futures) - if err != nil { - t.Errorf("%s FetchTradablePairs() error %v", g.Name, err) - } - if _, err = g.FetchTradablePairs(t.Context(), asset.Margin); err != nil { - t.Errorf("%s FetchTradablePairs() error %v", g.Name, err) - } - _, err = g.FetchTradablePairs(t.Context(), asset.CrossMargin) - if err != nil { - t.Errorf("%s FetchTradablePairs() error %v", g.Name, err) - } - _, err = g.FetchTradablePairs(t.Context(), asset.Spot) - if err != nil { - t.Errorf("%s FetchTradablePairs() error %v", g.Name, err) + for _, a := range g.GetAssetTypes(false) { + pairs, err := g.FetchTradablePairs(t.Context(), a) + require.NoErrorf(t, err, "FetchTradablePairs must not error for %s", a) + require.NotEmptyf(t, pairs, "FetchTradablePairs must return some pairs for %s", a) + if a == asset.USDTMarginedFutures || a == asset.CoinMarginedFutures { + for _, p := range pairs { + _, err := getSettlementCurrency(p, a) + require.NoErrorf(t, err, "Fetched pair %s %s must not error on getSettlementCurrency", a, p) + } + } } } func TestUpdateTickers(t *testing.T) { t.Parallel() - if err := g.UpdateTickers(t.Context(), asset.DeliveryFutures); err != nil { - t.Errorf("%s UpdateTickers() error %v", g.Name, err) - } - if err := g.UpdateTickers(t.Context(), asset.Futures); err != nil { - t.Errorf("%s UpdateTickers() error %v", g.Name, err) - } - if err := g.UpdateTickers(t.Context(), asset.Spot); err != nil { - t.Errorf("%s UpdateTickers() error %v", g.Name, err) - } - if err := g.UpdateTickers(t.Context(), asset.Options); err != nil { - t.Errorf("%s UpdateTickers() error %v", g.Name, err) - } - if err := g.UpdateTickers(t.Context(), asset.CrossMargin); err != nil { - t.Errorf("%s UpdateTickers() error %v", g.Name, err) - } - if err := g.UpdateTickers(t.Context(), asset.Margin); err != nil { - t.Errorf("%s UpdateTickers() error %v", g.Name, err) + for _, a := range g.GetAssetTypes(false) { + err := g.UpdateTickers(t.Context(), a) + assert.NoErrorf(t, err, "UpdateTickers should not error for %s", a) } } func TestUpdateOrderbook(t *testing.T) { t.Parallel() - _, err := g.UpdateOrderbook(t.Context(), getPair(t, asset.Spot), asset.Spot) - if err != nil { - t.Errorf("%s UpdateOrderbook() error %v", g.Name, err) - } - _, err = g.UpdateOrderbook(t.Context(), getPair(t, asset.Margin), asset.Margin) - if err != nil { - t.Errorf("%s UpdateOrderbook() error %v", g.Name, err) - } - _, err = g.UpdateOrderbook(t.Context(), getPair(t, asset.CrossMargin), asset.CrossMargin) - if err != nil { - t.Errorf("%s UpdateOrderbook() error %v", g.Name, err) - } - _, err = g.UpdateOrderbook(t.Context(), getPair(t, asset.Futures), asset.Futures) - if err != nil { - t.Errorf("%s UpdateOrderbook() error %v", g.Name, err) - } - if _, err = g.UpdateOrderbook(t.Context(), getPair(t, asset.DeliveryFutures), asset.DeliveryFutures); err != nil { - t.Errorf("%s UpdateOrderbook() error %v", g.Name, err) - } - if _, err = g.UpdateOrderbook(t.Context(), getPair(t, asset.Options), asset.Options); err != nil { - t.Errorf("%s UpdateOrderbook() error %v", g.Name, err) + for _, a := range g.GetAssetTypes(false) { + _, err := g.UpdateOrderbook(t.Context(), getPair(t, a), a) + assert.NoErrorf(t, err, "UpdateOrderbook should not error for %s", a) } } @@ -1966,239 +1802,61 @@ func TestGetWithdrawalsHistory(t *testing.T) { func TestGetRecentTrades(t *testing.T) { t.Parallel() - _, err := g.GetRecentTrades(t.Context(), getPair(t, asset.Spot), asset.Spot) - if err != nil { - t.Error(err) - } - _, err = g.GetRecentTrades(t.Context(), getPair(t, asset.Margin), asset.Margin) - if err != nil { - t.Error(err) - } - _, err = g.GetRecentTrades(t.Context(), getPair(t, asset.CrossMargin), asset.CrossMargin) - if err != nil { - t.Error(err) - } - _, err = g.GetRecentTrades(t.Context(), getPair(t, asset.DeliveryFutures), asset.DeliveryFutures) - if err != nil { - t.Error(err) - } - _, err = g.GetRecentTrades(t.Context(), getPair(t, asset.Futures), asset.Futures) - if err != nil { - t.Error(err) - } - _, err = g.GetRecentTrades(t.Context(), getPair(t, asset.Options), asset.Options) - if err != nil { - t.Error(err) + for _, a := range g.GetAssetTypes(false) { + if a != asset.CoinMarginedFutures { + _, err := g.GetRecentTrades(t.Context(), getPair(t, a), a) + assert.NoErrorf(t, err, "GetRecentTrades should not error for %s", a) + } } } func TestSubmitOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - _, err := g.SubmitOrder(t.Context(), &order.Submit{ - Exchange: g.Name, - Pair: getPair(t, asset.CrossMargin), - Side: order.Buy, - Type: order.Limit, - Price: 1, - Amount: 1, - AssetType: asset.CrossMargin, - }) - if err != nil { - t.Errorf("Order failed to be placed: %v", err) - } - _, err = g.SubmitOrder(t.Context(), &order.Submit{ - Exchange: g.Name, - Pair: getPair(t, asset.Spot), - Side: order.Buy, - Type: order.Limit, - Price: 1, - Amount: 1, - AssetType: asset.Spot, - }) - if err != nil { - t.Errorf("Order failed to be placed: %v", err) - } - _, err = g.SubmitOrder(t.Context(), &order.Submit{ - Exchange: g.Name, - Pair: getPair(t, asset.Options), - Side: order.Buy, - Type: order.Limit, - Price: 1, - Amount: 1, - AssetType: asset.Options, - }) - if err != nil { - t.Errorf("Order failed to be placed: %v", err) - } - _, err = g.SubmitOrder(t.Context(), &order.Submit{ - Exchange: g.Name, - Pair: getPair(t, asset.DeliveryFutures), - Side: order.Buy, - Type: order.Limit, - Price: 1, - Amount: 1, - AssetType: asset.DeliveryFutures, - }) - if err != nil { - t.Errorf("Order failed to be placed: %v", err) - } - _, err = g.SubmitOrder(t.Context(), &order.Submit{ - Exchange: g.Name, - Pair: getPair(t, asset.Futures), - Side: order.Buy, - Type: order.Limit, - Price: 1, - Amount: 1, - AssetType: asset.Futures, - }) - if err != nil { - t.Errorf("Order failed to be placed: %v", err) - } - _, err = g.SubmitOrder(t.Context(), &order.Submit{ - Exchange: g.Name, - Pair: getPair(t, asset.Margin), - Side: order.Buy, - Type: order.Limit, - Price: 1, - Amount: 1, - AssetType: asset.Margin, - }) - if err != nil { - t.Errorf("Order failed to be placed: %v", err) + for _, a := range g.GetAssetTypes(false) { + _, err := g.SubmitOrder(t.Context(), &order.Submit{ + Exchange: g.Name, + Pair: getPair(t, a), + Side: order.Buy, + Type: order.Limit, + Price: 1, + Amount: 1, + AssetType: a, + }) + assert.NoErrorf(t, err, "SubmitOrder should not error for %s", a) } } func TestCancelExchangeOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - orderCancellation := &order.Cancel{ - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: currency.NewPair(currency.LTC, currency.BTC), - AssetType: asset.Spot, - } - err := g.CancelOrder(t.Context(), orderCancellation) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) - } - orderCancellation.AssetType = asset.Margin - err = g.CancelOrder(t.Context(), orderCancellation) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) - } - orderCancellation.AssetType = asset.CrossMargin - err = g.CancelOrder(t.Context(), orderCancellation) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) - } - orderCancellation.AssetType = asset.Options - err = g.CancelOrder(t.Context(), orderCancellation) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) - } - orderCancellation.AssetType = asset.Futures - err = g.CancelOrder(t.Context(), orderCancellation) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) - } - orderCancellation.AssetType = asset.DeliveryFutures - err = g.CancelOrder(t.Context(), orderCancellation) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) + for _, a := range g.GetAssetTypes(false) { + orderCancellation := &order.Cancel{ + OrderID: "1", + AccountID: "1", + Pair: getPair(t, a), + AssetType: a, + } + err := g.CancelOrder(t.Context(), orderCancellation) + assert.NoErrorf(t, err, "CancelOrder should not error for %s", a) } } func TestCancelBatchOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) - _, err := g.CancelBatchOrders(t.Context(), []order.Cancel{ - { - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.Spot), - AssetType: asset.Spot, - }, { - OrderID: "2", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.Spot), - AssetType: asset.Spot, - }, - }) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) - } - _, err = g.CancelBatchOrders(t.Context(), []order.Cancel{ - { - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.Futures), - AssetType: asset.Futures, - }, { - OrderID: "2", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.Futures), - AssetType: asset.Futures, - }, - }) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) - } - _, err = g.CancelBatchOrders(t.Context(), []order.Cancel{ - { - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.DeliveryFutures), - AssetType: asset.DeliveryFutures, - }, { - OrderID: "2", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.DeliveryFutures), - AssetType: asset.DeliveryFutures, - }, - }) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) - } - _, err = g.CancelBatchOrders(t.Context(), []order.Cancel{ - { - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.Options), - AssetType: asset.Options, - }, { - OrderID: "2", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.Options), - AssetType: asset.Options, - }, - }) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) - } - _, err = g.CancelBatchOrders(t.Context(), []order.Cancel{ - { - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.Margin), - AssetType: asset.Margin, - }, { - OrderID: "2", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: getPair(t, asset.Margin), - AssetType: asset.Margin, - }, - }) - if err != nil { - t.Errorf("%s CancelOrder error: %v", g.Name, err) + for _, a := range g.GetAssetTypes(false) { + _, err := g.CancelBatchOrders(t.Context(), []order.Cancel{ + { + OrderID: "1", + AccountID: "1", + Pair: getPair(t, a), + AssetType: a, + }, { + OrderID: "2", + AccountID: "1", + Pair: getPair(t, a), + AssetType: a, + }, + }) + assert.NoErrorf(t, err, "CancelBatchOrders should not error for %s", a) } } @@ -2218,188 +1876,62 @@ func TestGetDepositAddress(t *testing.T) { func TestGetActiveOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - enabledPairs, err := g.GetEnabledPairs(asset.Spot) - if err != nil { - t.Error(err) - } - _, err = g.GetActiveOrders(t.Context(), &order.MultiOrderRequest{ - Pairs: enabledPairs[:2], - Type: order.AnyType, - Side: order.AnySide, - AssetType: asset.Spot, - }) - if err != nil { - t.Errorf(" %s GetActiveOrders() error: %v", g.Name, err) - } - cp, err := currency.NewPairFromString("BTC_USDT") - if err != nil { - t.Error(err) - } - _, err = g.GetActiveOrders(t.Context(), &order.MultiOrderRequest{ - Pairs: []currency.Pair{cp}, - Type: order.AnyType, - Side: order.AnySide, - AssetType: asset.Futures, - }) - if err != nil { - t.Errorf(" %s GetActiveOrders() error: %v", g.Name, err) - } - _, err = g.GetActiveOrders(t.Context(), &order.MultiOrderRequest{ - Pairs: enabledPairs[:2], - Type: order.AnyType, - Side: order.AnySide, - AssetType: asset.Margin, - }) - if err != nil { - t.Errorf(" %s GetActiveOrders() error: %v", g.Name, err) - } - _, err = g.GetActiveOrders(t.Context(), &order.MultiOrderRequest{ - Pairs: enabledPairs[:2], - Type: order.AnyType, - Side: order.AnySide, - AssetType: asset.CrossMargin, - }) - if err != nil { - t.Errorf(" %s GetActiveOrders() error: %v", g.Name, err) - } - _, err = g.GetActiveOrders(t.Context(), &order.MultiOrderRequest{ - Pairs: currency.Pairs{getPair(t, asset.Futures)}, - Type: order.AnyType, - Side: order.AnySide, - AssetType: asset.Futures, - }) - if err != nil { - t.Errorf(" %s GetActiveOrders() error: %v", g.Name, err) - } - _, err = g.GetActiveOrders(t.Context(), &order.MultiOrderRequest{ - Pairs: currency.Pairs{getPair(t, asset.DeliveryFutures)}, - Type: order.AnyType, - Side: order.AnySide, - AssetType: asset.DeliveryFutures, - }) - if err != nil { - t.Errorf(" %s GetActiveOrders() error: %v", g.Name, err) - } - _, err = g.GetActiveOrders(t.Context(), &order.MultiOrderRequest{ - Pairs: currency.Pairs{getPair(t, asset.Options)}, - Type: order.AnyType, - Side: order.AnySide, - AssetType: asset.Options, - }) - if err != nil { - t.Errorf(" %s GetActiveOrders() error: %v", g.Name, err) - } - if _, err = g.GetActiveOrders(t.Context(), &order.MultiOrderRequest{ - Pairs: currency.Pairs{}, - Type: order.AnyType, - Side: order.AnySide, - AssetType: asset.DeliveryFutures, - }); !errors.Is(err, currency.ErrCurrencyPairsEmpty) { - t.Errorf("%s GetActiveOrders() expecting error %v, but found %v", g.Name, currency.ErrCurrencyPairsEmpty, err) + for _, a := range g.GetAssetTypes(false) { + enabledPairs := getPairs(t, a) + if len(enabledPairs) > 2 { + enabledPairs = enabledPairs[:2] + } + _, err := g.GetActiveOrders(t.Context(), &order.MultiOrderRequest{ + Pairs: enabledPairs, + Type: order.AnyType, + Side: order.AnySide, + AssetType: a, + }) + assert.NoErrorf(t, err, "GetActiveOrders should not error for %s", a) } } func TestGetOrderHistory(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, g) - multiOrderRequest := order.MultiOrderRequest{ - Type: order.AnyType, - AssetType: asset.Spot, - Side: order.Buy, - } - enabledPairs, err := g.GetEnabledPairs(asset.Spot) - if err != nil { - t.Fatal(err) - } - multiOrderRequest.Pairs = enabledPairs[:3] - _, err = g.GetOrderHistory(t.Context(), &multiOrderRequest) - if err != nil { - t.Errorf("%s GetOrderhistory() error: %v", g.Name, err) - } - multiOrderRequest.AssetType = asset.Futures - multiOrderRequest.Pairs, err = g.GetEnabledPairs(asset.Futures) - if err != nil { - t.Fatal(err) - } - multiOrderRequest.Pairs = multiOrderRequest.Pairs[len(multiOrderRequest.Pairs)-4:] - _, err = g.GetOrderHistory(t.Context(), &multiOrderRequest) - if err != nil { - t.Errorf("%s GetOrderhistory() error: %v", g.Name, err) - } - multiOrderRequest.AssetType = asset.DeliveryFutures - multiOrderRequest.Pairs, err = g.GetEnabledPairs(asset.DeliveryFutures) - if err != nil { - t.Fatal(err) - } - _, err = g.GetOrderHistory(t.Context(), &multiOrderRequest) - if err != nil { - t.Errorf("%s GetOrderhistory() error: %v", g.Name, err) - } - multiOrderRequest.AssetType = asset.Options - multiOrderRequest.Pairs, err = g.GetEnabledPairs(asset.Options) - if err != nil { - t.Fatal(err) - } - _, err = g.GetOrderHistory(t.Context(), &multiOrderRequest) - if err != nil { - t.Errorf("%s GetOrderhistory() error: %v", g.Name, err) + for _, a := range g.GetAssetTypes(false) { + enabledPairs := getPairs(t, a) + if len(enabledPairs) > 4 { + enabledPairs = enabledPairs[:4] + } + multiOrderRequest := order.MultiOrderRequest{ + Type: order.AnyType, + Side: order.Buy, + Pairs: enabledPairs, + AssetType: a, + } + _, err := g.GetOrderHistory(t.Context(), &multiOrderRequest) + assert.NoErrorf(t, err, "GetOrderHistory should not error for %s", a) } } func TestGetHistoricCandles(t *testing.T) { t.Parallel() startTime := time.Now().Add(-time.Hour * 10) - if _, err := g.GetHistoricCandles(t.Context(), getPair(t, asset.Spot), asset.Spot, kline.OneDay, startTime, time.Now()); err != nil { - t.Errorf("%s GetHistoricCandles() error: %v", g.Name, err) - } - if _, err := g.GetHistoricCandles(t.Context(), getPair(t, asset.Margin), asset.Margin, kline.OneDay, startTime, time.Now()); err != nil { - t.Errorf("%s GetHistoricCandles() error: %v", g.Name, err) - } - if _, err := g.GetHistoricCandles(t.Context(), getPair(t, asset.CrossMargin), asset.CrossMargin, kline.OneDay, startTime, time.Now()); err != nil { - t.Errorf("%s GetHistoricCandles() error: %v", g.Name, err) - } - if _, err := g.GetHistoricCandles(t.Context(), getPair(t, asset.Futures), asset.Futures, kline.OneDay, startTime, time.Now()); err != nil { - t.Errorf("%s GetHistoricCandles() error: %v", g.Name, err) - } - if _, err := g.GetHistoricCandles(t.Context(), getPair(t, asset.DeliveryFutures), asset.DeliveryFutures, kline.OneDay, startTime, time.Now()); err != nil { - t.Errorf("%s GetHistoricCandles() error: %v", g.Name, err) - } - if _, err := g.GetHistoricCandles(t.Context(), getPair(t, asset.Options), asset.Options, kline.OneDay, startTime, time.Now()); !errors.Is(err, asset.ErrNotSupported) { - t.Errorf("%s GetHistoricCandles() expecting: %v, but found %v", g.Name, asset.ErrNotSupported, err) - } - if _, err := g.GetHistoricCandles(t.Context(), getPair(t, asset.Options), asset.Options, kline.OneDay, startTime, time.Now()); !errors.Is(err, asset.ErrNotSupported) { - t.Errorf("%s GetHistoricCandles() expecting: %v, but found %v", g.Name, asset.ErrNotSupported, err) + for _, a := range g.GetAssetTypes(false) { + _, err := g.GetHistoricCandles(t.Context(), getPair(t, a), a, kline.OneDay, startTime, time.Now()) + if a == asset.Options { + assert.ErrorIs(t, err, asset.ErrNotSupported, "GetHistoricCandles should error correctly for options") + } else { + assert.NoErrorf(t, err, "GetHistoricCandles should not error for %s", a) + } } } func TestGetHistoricCandlesExtended(t *testing.T) { t.Parallel() startTime := time.Now().Add(-time.Hour * 5) - _, err := g.GetHistoricCandlesExtended(t.Context(), - getPair(t, asset.Spot), asset.Spot, kline.OneMin, startTime, time.Now()) - if err != nil { - t.Fatal(err) - } - _, err = g.GetHistoricCandlesExtended(t.Context(), - getPair(t, asset.Margin), asset.Margin, kline.OneMin, startTime, time.Now()) - if err != nil { - t.Fatal(err) - } - _, err = g.GetHistoricCandlesExtended(t.Context(), - getPair(t, asset.DeliveryFutures), asset.DeliveryFutures, kline.OneMin, time.Now().Add(-time.Hour*5), time.Now()) - if err != nil { - t.Error(err) - } - _, err = g.GetHistoricCandlesExtended(t.Context(), getPair(t, asset.Futures), asset.Futures, kline.OneMin, startTime, time.Now()) - if err != nil { - t.Error(err) - } - _, err = g.GetHistoricCandlesExtended(t.Context(), - getPair(t, asset.CrossMargin), asset.CrossMargin, kline.OneMin, startTime, time.Now()) - if err != nil { - t.Error(err) - } - if _, err = g.GetHistoricCandlesExtended(t.Context(), getPair(t, asset.Options), asset.Options, kline.OneDay, startTime, time.Now()); !errors.Is(err, asset.ErrNotSupported) { - t.Errorf("%s GetHistoricCandlesExtended() expecting: %v, but found %v", g.Name, asset.ErrNotSupported, err) + for _, a := range g.GetAssetTypes(false) { + _, err := g.GetHistoricCandlesExtended(t.Context(), getPair(t, a), a, kline.OneMin, startTime, time.Now()) + if a == asset.Options { + assert.ErrorIs(t, err, asset.ErrNotSupported, "GetHistoricCandlesExtended should error correctly for options") + } else { + assert.NoErrorf(t, err, "GetHistoricCandlesExtended should not error for %s", a) + } } } @@ -2526,113 +2058,18 @@ func TestCrossMarginBalanceLoan(t *testing.T) { } } -const wsFuturesTickerPushDataJSON = `{"time": 1541659086, "channel": "futures.tickers","event": "update", "error": null, "result": [ { "contract": "BTC_USD","last": "118.4","change_percentage": "0.77","funding_rate": "-0.000114","funding_rate_indicative": "0.01875","mark_price": "118.35","index_price": "118.36","total_size": "73648","volume_24h": "745487577","volume_24h_btc": "117", "volume_24h_usd": "419950", "quanto_base_rate": "", "volume_24h_quote": "1665006","volume_24h_settle": "178","volume_24h_base": "5526","low_24h": "99.2","high_24h": "132.5"} ]}` - -func TestFuturesTicker(t *testing.T) { +// TestFuturesDataHandler ensures that messages from various futures channels do not error +func TestFuturesDataHandler(t *testing.T) { t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesTickerPushDataJSON), asset.Futures); err != nil { - t.Errorf("%s websocket push data error: %v", g.Name, err) - } -} - -const wsFuturesTradesPushDataJSON = `{"channel": "futures.trades","event": "update", "time": 1541503698, "result": [{"size": -108,"id": 27753479,"create_time": 1545136464,"create_time_ms": 1545136464123,"price": "96.4","contract": "BTC_USD"}]}` - -func TestFuturesTrades(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesTradesPushDataJSON), asset.Futures); err != nil { - t.Errorf("%s websocket push data error: %v", g.Name, err) - } -} - -const ( - wsFuturesOrderbookTickerJSON = `{ "time": 1615366379, "channel": "futures.book_ticker", "event": "update", "error": null, "result": { "t": 1615366379123, "u": 2517661076, "s": "BTC_USD", "b": "54696.6", "B": 37000, "a": "54696.7", "A": 47061 }}` -) - -func TestOrderbookData(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesOrderbookTickerJSON), asset.Futures); err != nil { - t.Errorf("%s websocket orderbook ticker push data error: %v", g.Name, err) - } -} - -const wsFuturesOrderPushDataJSON = `{ "channel": "futures.orders", "event": "update", "time": 1541505434, "result": [ { "contract": "BTC_USD", "create_time": 1628736847, "create_time_ms": 1628736847325, "fill_price": 40000.4, "finish_as": "filled", "finish_time": 1628736848, "finish_time_ms": 1628736848321, "iceberg": 0, "id": 4872460, "is_close": false, "is_liq": false, "is_reduce_only": false, "left": 0, "mkfr": -0.00025, "price": 40000.4, "refr": 0, "refu": 0, "size": 1, "status": "finished", "text": "-", "tif": "gtc", "tkfr": 0.0005, "user": "110xxxxx" } ]}` - -func TestFuturesOrderPushData(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesOrderPushDataJSON), asset.Futures); err != nil { - t.Errorf("%s websocket futures order push data error: %v", g.Name, err) - } -} - -const wsFuturesUsertradesPushDataJSON = `{"time": 1543205083, "channel": "futures.usertrades","event": "update", "error": null, "result": [{"id": "3335259","create_time": 1628736848,"create_time_ms": 1628736848321,"contract": "BTC_USD","order_id": "4872460","size": 1,"price": "40000.4","role": "maker","text": "api","fee": 0.0009290592,"point_fee": 0}]}` - -func TestFuturesUserTrades(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesUsertradesPushDataJSON), asset.Futures); err != nil { - t.Errorf("%s websocket futures user trades push data error: %v", g.Name, err) - } -} - -const wsFuturesLiquidationPushDataJSON = `{"channel": "futures.liquidates", "event": "update", "time": 1541505434, "result": [{"entry_price": 209,"fill_price": 215.1,"left": 0,"leverage": 0.0,"liq_price": 213,"margin": 0.007816722941,"mark_price": 213,"order_id": 4093362,"order_price": 215.1,"size": -124,"time": 1541486601,"time_ms": 1541486601123,"contract": "BTC_USD","user": "1040xxxx"} ]}` - -func TestFuturesLiquidationPushData(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesLiquidationPushDataJSON), asset.Futures); err != nil { - t.Errorf("%s websocket futures liquidation push data error: %v", g.Name, err) - } -} - -const wsFuturesAutoDelevergesNotification = `{"channel": "futures.auto_deleverages", "event": "update", "time": 1541505434, "result": [{"entry_price": 209,"fill_price": 215.1,"position_size": 10,"trade_size": 10,"time": 1541486601,"time_ms": 1541486601123,"contract": "BTC_USD","user": "1040"} ]}` - -func TestFuturesAutoDeleverges(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesAutoDelevergesNotification), asset.Futures); err != nil { - t.Errorf("%s websocket futures auto deleverge push data error: %v", g.Name, err) - } -} - -const wsFuturesPositionClosePushDataJSON = ` {"channel": "futures.position_closes", "event": "update", "time": 1541505434, "result": [ { "contract": "BTC_USD", "pnl": -0.000624354791, "side": "long", "text": "web", "time": 1547198562, "time_ms": 1547198562123, "user": "211xxxx" } ]}` - -func TestPositionClosePushData(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesPositionClosePushDataJSON), asset.Futures); err != nil { - t.Errorf("%s websocket futures position close push data error: %v", g.Name, err) - } -} - -const wsFuturesBalanceNotificationPushDataJSON = `{"channel": "futures.balances", "event": "update", "time": 1541505434, "result": [ { "balance": 9.998739899488, "change": -0.000002074115, "text": "BTC_USD:3914424", "time": 1547199246, "time_ms": 1547199246123, "type": "fee", "user": "211xxx" } ]}` - -func TestFuturesBalanceNotification(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesBalanceNotificationPushDataJSON), asset.Futures); err != nil { - t.Errorf("%s websocket futures balance notification push data error: %v", g.Name, err) - } -} - -const wsFuturesReduceRiskLimitNotificationPushDataJSON = `{"time": 1551858330, "channel": "futures.reduce_risk_limits", "event": "update", "error": null, "result": [ { "cancel_orders": 0, "contract": "ETH_USD", "leverage_max": 10, "liq_price": 136.53, "maintenance_rate": 0.09, "risk_limit": 450, "time": 1551858330, "time_ms": 1551858330123, "user": "20011" } ]}` - -func TestFuturesReduceRiskLimitPushData(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesReduceRiskLimitNotificationPushDataJSON), asset.Futures); err != nil { - t.Errorf("%s websocket futures reduce risk limit notification push data error: %v", g.Name, err) - } -} - -const wsFuturesPositionsNotificationPushDataJSON = `{"time": 1588212926,"channel": "futures.positions", "event": "update", "error": null, "result": [ { "contract": "BTC_USD", "cross_leverage_limit": 0, "entry_price": 40000.36666661111, "history_pnl": -0.000108569505, "history_point": 0, "last_close_pnl": -0.000050123368,"leverage": 0,"leverage_max": 100,"liq_price": 0.1,"maintenance_rate": 0.005,"margin": 49.999890611186,"mode": "single","realised_pnl": -1.25e-8,"realised_point": 0,"risk_limit": 100,"size": 3,"time": 1628736848,"time_ms": 1628736848321,"user": "110xxxxx"}]}` - -func TestFuturesPositionsNotification(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesPositionsNotificationPushDataJSON), asset.Futures); err != nil { - t.Errorf("%s websocket futures positions change notification push data error: %v", g.Name, err) - } -} - -const wsFuturesAutoOrdersPushDataJSON = `{"time": 1596798126,"channel": "futures.autoorders", "event": "update", "error": null, "result": [ { "user": 123456, "trigger": { "strategy_type": 0, "price_type": 0, "price": "10000", "rule": 2, "expiration": 86400 }, "initial": { "contract": "BTC_USDT", "size": 10, "price": "10000", "tif": "gtc", "text": "web", "iceberg": 0, "is_close": false, "is_reduce_only": false }, "id": 9256, "trade_id": 0, "status": "open", "reason": "", "create_time": 1596798126, "name": "price_autoorders", "is_stop_order": false, "stop_trigger": { "rule": 0, "trigger_price": "", "order_price": "" } } ]}` - -func TestFuturesAutoOrderPushData(t *testing.T) { - t.Parallel() - if err := g.WsHandleFuturesData(t.Context(), []byte(wsFuturesAutoOrdersPushDataJSON), asset.Futures); err != nil { - t.Errorf("%s websocket futures auto orders push data error: %v", g.Name, err) + g := new(Gateio) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes + require.NoError(t, testexch.Setup(g), "Test instance Setup must not error") + testexch.FixtureToDataHandler(t, "testdata/wsFutures.json", func(m []byte) error { return g.WsHandleFuturesData(t.Context(), m, asset.CoinMarginedFutures) }) + close(g.Websocket.DataHandler) + assert.Len(t, g.Websocket.DataHandler, 14, "Should see the correct number of messages") + for resp := range g.Websocket.DataHandler { + if err, isErr := resp.(error); isErr { + assert.NoError(t, err, "Should not get any errors down the data handler") + } } } @@ -2734,11 +2171,9 @@ const ( func TestOptionsOrderbookPushData(t *testing.T) { t.Parallel() - testexch.UpdatePairsOnce(t, g) + p := getPair(t, asset.Options) assert.NoError(t, g.WsHandleOptionsData(t.Context(), []byte(optionsOrderbookTickerPushDataJSON))) - avail, err := g.GetAvailablePairs(asset.Options) - require.NoError(t, err, "GetAvailablePairs must not error") - assert.NoError(t, g.WsHandleOptionsData(t.Context(), fmt.Appendf(nil, optionsOrderbookUpdatePushDataJSON, avail[0].Upper().String()))) + assert.NoError(t, g.WsHandleOptionsData(t.Context(), fmt.Appendf(nil, optionsOrderbookUpdatePushDataJSON, p.Upper().String()))) assert.NoError(t, g.WsHandleOptionsData(t.Context(), []byte(optionsOrderbookSnapshotPushDataJSON))) assert.NoError(t, g.WsHandleOptionsData(t.Context(), []byte(optionsOrderbookSnapshotUpdateEventPushDataJSON))) } @@ -2806,33 +2241,6 @@ func TestOptionsPositionPushData(t *testing.T) { } } -const ( - futuresOrderbookPushData = `{"time": 1678468497, "time_ms": 1678468497232, "channel": "futures.order_book", "event": "all", "result": { "t": 1678468497168, "id": 4010394406, "contract": "BTC_USD", "asks": [ { "p": "19909", "s": 3100 }, { "p": "19909.1", "s": 5000 }, { "p": "19910", "s": 3100 }, { "p": "19914.4", "s": 4400 }, { "p": "19916.6", "s": 5000 }, { "p": "19917.2", "s": 8255 }, { "p": "19919.2", "s": 5000 }, { "p": "19920.3", "s": 11967 }, { "p": "19922.2", "s": 5000 }, { "p": "19924.2", "s": 5000 }, { "p": "19927.1", "s": 17129 }, { "p": "19927.2", "s": 5000 }, { "p": "19929", "s": 20864 }, { "p": "19929.3", "s": 5000 }, { "p": "19929.7", "s": 24683 }, { "p": "19930.3", "s": 750 }, { "p": "19931.4", "s": 5000 }, { "p": "19931.5", "s": 1 }, { "p": "19934.2", "s": 5000 }, { "p": "19935.4", "s": 1 } ], "bids": [ { "p": "19901.2", "s": 5000 }, { "p": "19900.3", "s": 3100 }, { "p": "19900.2", "s": 5000 }, { "p": "19899.3", "s": 2983 }, { "p": "19899.2", "s": 6035 }, { "p": "19897.2", "s": 5000 }, { "p": "19895.7", "s": 5984 }, { "p": "19895", "s": 5000 }, { "p": "19892.9", "s": 195 }, { "p": "19892.8", "s": 5000 }, { "p": "19889.4", "s": 5000 }, { "p": "19889", "s": 8800 }, { "p": "19888.5", "s": 11968 }, { "p": "19887.1", "s": 5000 }, { "p": "19886.4", "s": 24683 }, { "p": "19885.7", "s": 1 }, { "p": "19883.8", "s": 5000 }, { "p": "19880.2", "s": 5000 }, { "p": "19878.2", "s": 5000 }, { "p": "19876.8", "s": 1 } ] } }` - futuresOrderbookUpdatePushData = `{"time": 1678469222, "time_ms": 1678469222982, "channel": "futures.order_book_update", "event": "update", "result": { "t": 1678469222617, "s": "BTC_USD", "U": 4010424331, "u": 4010424361, "b": [ { "p": "19860.7", "s": 5984 }, { "p": "19858.6", "s": 5000 }, { "p": "19845.4", "s": 20864 }, { "p": "19859.1", "s": 0 }, { "p": "19862.5", "s": 0 }, { "p": "19358", "s": 0 }, { "p": "19864.5", "s": 5000 }, { "p": "19840.7", "s": 0 }, { "p": "19863.6", "s": 3100 }, { "p": "19839.3", "s": 0 }, { "p": "19851.5", "s": 8800 }, { "p": "19720", "s": 0 }, { "p": "19333", "s": 0 }, { "p": "19852.7", "s": 5000 }, { "p": "19861.5", "s": 0 }, { "p": "19860.6", "s": 3100 }, { "p": "19833.6", "s": 0 }, { "p": "19360", "s": 0 }, { "p": "19863.5", "s": 5000 }, { "p": "19736.9", "s": 0 }, { "p": "19838.5", "s": 0 }, { "p": "19841.3", "s": 0 }, { "p": "19858.1", "s": 3100 }, { "p": "19710.9", "s": 0 }, { "p": "19342", "s": 0 }, { "p": "19852.1", "s": 11967 }, { "p": "19343", "s": 0 }, { "p": "19705", "s": 0 }, { "p": "19836.5", "s": 0 }, { "p": "19862.6", "s": 3100 }, { "p": "19729.6", "s": 0 }, { "p": "19849.9", "s": 5000 } ], "a": [ { "p": "19900.5", "s": 0 }, { "p": "19883.1", "s": 11967 }, { "p": "19910.9", "s": 0 }, { "p": "19897.7", "s": 5000 }, { "p": "19875.9", "s": 5984 }, { "p": "19899.6", "s": 0 }, { "p": "19878", "s": 4400 }, { "p": "19877.6", "s": 0 }, { "p": "19889.5", "s": 5000 }, { "p": "19875.5", "s": 3100 }, { "p": "19875.3", "s": 0 }, { "p": "19878.5", "s": 0 }, { "p": "19895.2", "s": 0 }, { "p": "20284.6", "s": 0 }, { "p": "19880.7", "s": 5000 }, { "p": "19875.4", "s": 0 }, { "p": "19985.8", "s": 0 }, { "p": "19887.1", "s": 5000 }, { "p": "19896", "s": 1 }, { "p": "19869.3", "s": 0 }, { "p": "19900", "s": 0 }, { "p": "19875.6", "s": 5000 }, { "p": "19980.6", "s": 0 }, { "p": "19885.1", "s": 5000 }, { "p": "19877.7", "s": 5000 }, { "p": "20000", "s": 0 }, { "p": "19892.2", "s": 8255 }, { "p": "19886.8", "s": 0 }, { "p": "20257.4", "s": 0 }, { "p": "20280", "s": 0 }, { "p": "20002.5", "s": 0 }, { "p": "20263.1", "s": 0 }, { "p": "19900.2", "s": 0 } ] } }` -) - -func TestFuturesOrderbookPushData(t *testing.T) { - t.Parallel() - err := g.WsHandleFuturesData(t.Context(), []byte(futuresOrderbookPushData), asset.Futures) - if err != nil { - t.Error(err) - } - err = g.WsHandleFuturesData(t.Context(), []byte(futuresOrderbookUpdatePushData), asset.Futures) - if err != nil { - t.Error(err) - } -} - -const futuresCandlesticksPushData = `{"time": 1678469467, "time_ms": 1678469467981, "channel": "futures.candlesticks", "event": "update", "result": [ { "t": 1678469460, "v": 0, "c": "19896", "h": "19896", "l": "19896", "o": "19896", "n": "1m_BTC_USD" } ] }` - -func TestFuturesCandlestickPushData(t *testing.T) { - t.Parallel() - err := g.WsHandleFuturesData(t.Context(), []byte(futuresCandlesticksPushData), asset.Futures) - if err != nil { - t.Error(err) - } -} - func TestGenerateSubscriptionsSpot(t *testing.T) { t.Parallel() @@ -2899,14 +2307,18 @@ func TestGenerateDeliveryFuturesDefaultSubscriptions(t *testing.T) { func TestGenerateFuturesDefaultSubscriptions(t *testing.T) { t.Parallel() - subs, err := g.GenerateFuturesDefaultSubscriptions(currency.USDT) + g := new(Gateio) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes + require.NoError(t, testexch.Setup(g), "Test instance Setup must not error") + subs, err := g.GenerateFuturesDefaultSubscriptions(asset.USDTMarginedFutures) require.NoError(t, err) require.NotEmpty(t, subs) - subs, err = g.GenerateFuturesDefaultSubscriptions(currency.BTC) + subs, err = g.GenerateFuturesDefaultSubscriptions(asset.CoinMarginedFutures) require.NoError(t, err) require.NotEmpty(t, subs) - _, err = g.GenerateFuturesDefaultSubscriptions(currency.TABOO) - require.Error(t, err) + require.NoError(t, g.CurrencyPairs.SetAssetEnabled(asset.USDTMarginedFutures, false), "SetAssetEnabled must not error") + subs, err = g.GenerateFuturesDefaultSubscriptions(asset.USDTMarginedFutures) + require.NoError(t, err, "Disabled asset must not error") + require.Empty(t, subs, "Disabled asset must return no pairs") } func TestGenerateOptionsDefaultSubscriptions(t *testing.T) { @@ -3028,23 +2440,6 @@ func TestUnlockSubAccount(t *testing.T) { } } -func TestGetSettlementFromCurrency(t *testing.T) { - t.Parallel() - g := new(Gateio) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes - require.NoError(t, testexch.Setup(g), "Setup must not error") - for _, assetType := range []asset.Item{asset.Futures, asset.DeliveryFutures, asset.Options} { - availPairs, err := g.GetAvailablePairs(assetType) - require.NoErrorf(t, err, "GetAvailablePairs for asset %s must not error", assetType) - for i, pair := range availPairs { - t.Run(strconv.Itoa(i)+":"+assetType.String(), func(t *testing.T) { - t.Parallel() - _, err := getSettlementFromCurrency(pair) - assert.NoErrorf(t, err, "getSettlementFromCurrency should not error for pair %s and asset %s", pair, assetType) - }) - } - } -} - func TestParseGateioMilliSecTimeUnmarshal(t *testing.T) { t.Parallel() var timeWhenTesting int64 = 1684981731098 @@ -3209,9 +2604,11 @@ func TestGetFuturesContractDetails(t *testing.T) { require.NoError(t, err, "GetFuturesContractDetails must not error for DeliveryFutures") assert.Equal(t, len(exp), len(c), "GetFuturesContractDetails should return same number of Delivery contracts as exist") - c, err = g.GetFuturesContractDetails(t.Context(), asset.Futures) - require.NoError(t, err, "GetFuturesContractDetails must not error for DeliveryFutures") - assert.NotEmpty(t, c, "GetFuturesContractDetails should return same number of Future contracts as exist") + for _, a := range []asset.Item{asset.CoinMarginedFutures, asset.USDTMarginedFutures} { + c, err = g.GetFuturesContractDetails(t.Context(), a) + require.NoErrorf(t, err, "GetFuturesContractDetails must not error for %s", a) + assert.NotEmptyf(t, c, "GetFuturesContractDetails should return some contracts for %s", a) + } } func TestGetLatestFundingRates(t *testing.T) { @@ -3221,117 +2618,94 @@ func TestGetLatestFundingRates(t *testing.T) { Pair: currency.NewPair(currency.BTC, currency.USDT), IncludePredictedRate: true, }) - if !errors.Is(err, asset.ErrNotSupported) { - t.Error(err) - } + assert.NoError(t, err) + _, err = g.GetLatestFundingRates(t.Context(), &fundingrate.LatestRateRequest{ - Asset: asset.Futures, + Asset: asset.CoinMarginedFutures, Pair: currency.NewPair(currency.BTC, currency.USD), }) - if err != nil { - t.Error(err) - } - _, err = g.GetLatestFundingRates(t.Context(), &fundingrate.LatestRateRequest{ - Asset: asset.Futures, - }) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) + + _, err = g.GetLatestFundingRates(t.Context(), &fundingrate.LatestRateRequest{Asset: asset.CoinMarginedFutures}) + assert.NoError(t, err) + _, err = g.GetLatestFundingRates(t.Context(), &fundingrate.LatestRateRequest{Asset: asset.USDTMarginedFutures}) + assert.NoError(t, err) } func TestGetHistoricalFundingRates(t *testing.T) { t.Parallel() _, err := g.GetHistoricalFundingRates(t.Context(), nil) - if !errors.Is(err, common.ErrNilPointer) { - t.Fatalf("received: %v, expected: %v", err, common.ErrNilPointer) - } + assert.ErrorIs(t, err, common.ErrNilPointer) _, err = g.GetHistoricalFundingRates(t.Context(), &fundingrate.HistoricalRatesRequest{}) - if !errors.Is(err, asset.ErrNotSupported) { - t.Fatalf("received: %v, expected: %v", err, asset.ErrNotSupported) - } + assert.ErrorIs(t, err, asset.ErrNotSupported) + + _, err = g.GetHistoricalFundingRates(t.Context(), &fundingrate.HistoricalRatesRequest{Asset: asset.CoinMarginedFutures}) + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) + + _, err = g.GetHistoricalFundingRates(t.Context(), &fundingrate.HistoricalRatesRequest{Asset: asset.Futures}) + assert.ErrorIs(t, err, asset.ErrNotSupported) _, err = g.GetHistoricalFundingRates(t.Context(), &fundingrate.HistoricalRatesRequest{ - Asset: asset.Futures, - }) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Fatalf("received: %v, expected: %v", err, currency.ErrCurrencyPairEmpty) - } - - _, err = g.GetHistoricalFundingRates(t.Context(), &fundingrate.HistoricalRatesRequest{ - Asset: asset.Futures, + Asset: asset.USDTMarginedFutures, Pair: currency.NewPair(currency.ENJ, currency.USDT), }) - if !errors.Is(err, fundingrate.ErrPaymentCurrencyCannotBeEmpty) { - t.Fatalf("received: %v, expected: %v", err, fundingrate.ErrPaymentCurrencyCannotBeEmpty) - } + assert.ErrorIs(t, err, fundingrate.ErrPaymentCurrencyCannotBeEmpty) _, err = g.GetHistoricalFundingRates(t.Context(), &fundingrate.HistoricalRatesRequest{ - Asset: asset.Futures, - Pair: currency.NewPair(currency.ENJ, currency.USDT), - PaymentCurrency: currency.USDT, - IncludePayments: true, - IncludePredictedRate: true, + Asset: asset.USDTMarginedFutures, + Pair: currency.NewPair(currency.ENJ, currency.USDT), + PaymentCurrency: currency.USDT, + IncludePayments: true, }) - if !errors.Is(err, common.ErrNotYetImplemented) { - t.Fatalf("received: %v, expected: %v", err, common.ErrNotYetImplemented) - } + assert.ErrorIs(t, err, common.ErrNotYetImplemented) _, err = g.GetHistoricalFundingRates(t.Context(), &fundingrate.HistoricalRatesRequest{ - Asset: asset.Futures, + Asset: asset.USDTMarginedFutures, Pair: currency.NewPair(currency.ENJ, currency.USDT), PaymentCurrency: currency.USDT, IncludePredictedRate: true, }) - if !errors.Is(err, common.ErrNotYetImplemented) { - t.Fatalf("received: %v, expected: %v", err, common.ErrNotYetImplemented) - } + assert.ErrorIs(t, err, common.ErrNotYetImplemented) _, err = g.GetHistoricalFundingRates(t.Context(), &fundingrate.HistoricalRatesRequest{ - Asset: asset.Futures, + Asset: asset.USDTMarginedFutures, Pair: currency.NewPair(currency.ENJ, currency.USDT), PaymentCurrency: currency.USDT, StartDate: time.Now().Add(time.Hour * 16), EndDate: time.Now(), }) - if !errors.Is(err, common.ErrStartAfterEnd) { - t.Fatalf("received: %v, expected: %v", err, common.ErrStartAfterEnd) - } + assert.ErrorIs(t, err, common.ErrStartAfterEnd) _, err = g.GetHistoricalFundingRates(t.Context(), &fundingrate.HistoricalRatesRequest{ - Asset: asset.Futures, + Asset: asset.USDTMarginedFutures, Pair: currency.NewPair(currency.ENJ, currency.USDT), PaymentCurrency: currency.USDT, StartDate: time.Now().Add(-time.Hour * 8008), EndDate: time.Now(), }) - if !errors.Is(err, fundingrate.ErrFundingRateOutsideLimits) { - t.Fatalf("received: %v, expected: %v", err, fundingrate.ErrFundingRateOutsideLimits) - } + assert.ErrorIs(t, err, fundingrate.ErrFundingRateOutsideLimits) history, err := g.GetHistoricalFundingRates(t.Context(), &fundingrate.HistoricalRatesRequest{ - Asset: asset.Futures, + Asset: asset.USDTMarginedFutures, Pair: currency.NewPair(currency.ENJ, currency.USDT), PaymentCurrency: currency.USDT, }) - if !errors.Is(err, nil) { - t.Fatalf("received: %v, expected: %v", err, nil) - } - - assert.NotEmpty(t, history, "should return values") + require.NoError(t, err) + assert.NotEmpty(t, history) } func TestGetOpenInterest(t *testing.T) { t.Parallel() _, err := g.GetOpenInterest(t.Context(), key.PairAsset{ - Base: currency.ETH.Item, + Base: currency.NewCode("GOLDFISH").Item, Quote: currency.USDT.Item, Asset: asset.USDTMarginedFutures, }) - assert.ErrorIs(t, err, asset.ErrNotSupported, "GetOpenInterest should error correctly") + assert.ErrorIs(t, err, currency.ErrPairNotFound, "GetOpenInterest should error correctly") var resp []futures.OpenInterest - for _, a := range []asset.Item{asset.Futures, asset.DeliveryFutures} { + for _, a := range []asset.Item{asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures} { p := getPair(t, a) resp, err = g.GetOpenInterest(t.Context(), key.PairAsset{ Base: p.Base.Item, @@ -3347,39 +2721,6 @@ func TestGetOpenInterest(t *testing.T) { assert.NotEmpty(t, resp, "GetOpenInterest should return some items") } -var pairMap = map[asset.Item]currency.Pair{ - asset.Spot: currency.NewPairWithDelimiter("BTC", "USDT", "_"), -} - -var pairsGuard sync.RWMutex - -func getPair(tb testing.TB, a asset.Item) currency.Pair { - tb.Helper() - pairsGuard.RLock() - p, ok := pairMap[a] - pairsGuard.RUnlock() - if ok { - return p - } - pairsGuard.Lock() - defer pairsGuard.Unlock() - p, ok = pairMap[a] // Protect Race if we blocked on Lock and another RW populated - if ok { - return p - } - - testexch.UpdatePairsOnce(tb, g) - enabledPairs, err := g.GetEnabledPairs(a) - assert.NoErrorf(tb, err, "%s GetEnabledPairs should not error", a) - if !assert.NotEmpty(tb, enabledPairs, "%s GetEnabledPairs should not be empty", a) { - tb.Fatalf("No pair available for asset %s", a) - return currency.EMPTYPAIR - } - pairMap[a] = enabledPairs[0] - - return pairMap[a] -} - func TestGetClientOrderIDFromText(t *testing.T) { t.Parallel() assert.Empty(t, getClientOrderIDFromText("api"), "should not return anything") @@ -3475,7 +2816,7 @@ func TestProcessFuturesOrdersPushData(t *testing.T) { for _, tc := range testCases { t.Run("", func(t *testing.T) { t.Parallel() - processed, err := g.processFuturesOrdersPushData([]byte(tc.incoming), asset.Futures) + processed, err := g.processFuturesOrdersPushData([]byte(tc.incoming), asset.CoinMarginedFutures) require.NoError(t, err) require.NotNil(t, processed) for i := range processed { @@ -3511,6 +2852,36 @@ func TestGetUnifiedAccount(t *testing.T) { require.NotEmpty(t, payload) } +func TestGetSettlementCurrency(t *testing.T) { + t.Parallel() + for _, tt := range []struct { + a asset.Item + p currency.Pair + exp currency.Code + err error + }{ + {asset.Futures, currency.EMPTYPAIR, currency.EMPTYCODE, asset.ErrNotSupported}, + {asset.DeliveryFutures, currency.EMPTYPAIR, currency.USDT, nil}, + {asset.DeliveryFutures, getPair(t, asset.DeliveryFutures), currency.USDT, nil}, + {asset.USDTMarginedFutures, currency.EMPTYPAIR, currency.USDT, nil}, + {asset.USDTMarginedFutures, getPair(t, asset.USDTMarginedFutures), currency.USDT, nil}, + {asset.USDTMarginedFutures, getPair(t, asset.CoinMarginedFutures), currency.EMPTYCODE, errInvalidSettlementQuote}, + {asset.CoinMarginedFutures, currency.EMPTYPAIR, currency.BTC, nil}, + {asset.CoinMarginedFutures, getPair(t, asset.CoinMarginedFutures), currency.BTC, nil}, + {asset.CoinMarginedFutures, getPair(t, asset.USDTMarginedFutures), currency.EMPTYCODE, errInvalidSettlementBase}, + {asset.CoinMarginedFutures, currency.Pair{Base: currency.ETH, Quote: currency.USD}, currency.EMPTYCODE, errInvalidSettlementBase}, + {asset.CoinMarginedFutures, currency.NewBTCUSDT(), currency.EMPTYCODE, errInvalidSettlementQuote}, + } { + c, err := getSettlementCurrency(tt.p, tt.a) + if tt.err == nil { + require.NoErrorf(t, err, "getSettlementCurrency must not error for %s %s", tt.a, tt.p) + } else { + assert.ErrorIsf(t, err, tt.err, "getSettlementCurrency should return correct error for %s %s", tt.a, tt.p) + } + assert.Equalf(t, tt.exp, c, "getSettlementCurrency should return correct settlement currency for %s %s", tt.a, tt.p) + } +} + func TestGenerateWebsocketMessageID(t *testing.T) { t.Parallel() require.NotEmpty(t, g.GenerateWebsocketMessageID(false)) @@ -3914,3 +3285,42 @@ func TestGetUserTransactionRateLimitInfo(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, got) } + +var pairMap = map[asset.Item]currency.Pairs{} + +var pairsGuard sync.RWMutex + +func getPair(tb testing.TB, a asset.Item) currency.Pair { + tb.Helper() + if p := getPairs(tb, a); len(p) != 0 { + return p[0] + } + return currency.EMPTYPAIR +} + +func getPairs(tb testing.TB, a asset.Item) currency.Pairs { + tb.Helper() + pairsGuard.RLock() + p, ok := pairMap[a] + pairsGuard.RUnlock() + if ok { + return p + } + pairsGuard.Lock() + defer pairsGuard.Unlock() + p, ok = pairMap[a] // Protect Race if we blocked on Lock and another RW populated + if ok { + return p + } + + testexch.UpdatePairsOnce(tb, g) + enabledPairs, err := g.GetEnabledPairs(a) + assert.NoErrorf(tb, err, "%s GetEnabledPairs should not error", a) + if !assert.NotEmptyf(tb, enabledPairs, "%s GetEnabledPairs should not be empty", a) { + tb.Fatalf("No pair available for asset %s", a) + return nil + } + pairMap[a] = enabledPairs + + return enabledPairs +} diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index 1c0d174d..d4cac557 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -27,8 +27,6 @@ const ( sideBorrow = "borrow" ) -var settlementCurrencies = []currency.Code{currency.BTC, currency.USDT} - // WithdrawalFees the large list of predefined withdrawal fees // Prone to change var WithdrawalFees = map[currency.Code]float64{ diff --git a/exchanges/gateio/gateio_websocket_futures.go b/exchanges/gateio/gateio_websocket_futures.go index 2f4c8993..5f96808f 100644 --- a/exchanges/gateio/gateio_websocket_futures.go +++ b/exchanges/gateio/gateio_websocket_futures.go @@ -3,9 +3,7 @@ package gateio import ( "context" "errors" - "fmt" "net/http" - "slices" "strconv" "strings" "time" @@ -26,8 +24,8 @@ import ( ) const ( - futuresWebsocketBtcURL = "wss://fx-ws.gateio.ws/v4/ws/btc" - futuresWebsocketUsdtURL = "wss://fx-ws.gateio.ws/v4/ws/usdt" + btcFuturesWebsocketURL = "wss://fx-ws.gateio.ws/v4/ws/btc" + usdtFuturesWebsocketURL = "wss://fx-ws.gateio.ws/v4/ws/usdt" futuresPingChannel = "futures.ping" futuresTickersChannel = "futures.tickers" @@ -59,12 +57,14 @@ var defaultFuturesSubscriptions = []string{ // WsFuturesConnect initiates a websocket connection for futures account func (g *Gateio) WsFuturesConnect(ctx context.Context, conn websocket.Connection) error { - err := g.CurrencyPairs.IsAssetEnabled(asset.Futures) - if err != nil { + a := asset.USDTMarginedFutures + if conn.GetURL() == btcFuturesWebsocketURL { + a = asset.CoinMarginedFutures + } + if err := g.CurrencyPairs.IsAssetEnabled(a); err != nil { return err } - err = conn.DialContext(ctx, &gws.Dialer{}, http.Header{}) - if err != nil { + if err := conn.DialContext(ctx, &gws.Dialer{}, http.Header{}); err != nil { return err } pingMessage, err := json.Marshal(WsInput{ @@ -85,13 +85,13 @@ func (g *Gateio) WsFuturesConnect(ctx context.Context, conn websocket.Connection } // GenerateFuturesDefaultSubscriptions returns default subscriptions information. -func (g *Gateio) GenerateFuturesDefaultSubscriptions(settlement currency.Code) (subscription.List, error) { +func (g *Gateio) GenerateFuturesDefaultSubscriptions(a asset.Item) (subscription.List, error) { channelsToSubscribe := defaultFuturesSubscriptions if g.Websocket.CanUseAuthenticatedEndpoints() { channelsToSubscribe = append(channelsToSubscribe, futuresOrdersChannel, futuresUserTradesChannel, futuresBalancesChannel) } - pairs, err := g.GetEnabledPairs(asset.Futures) + pairs, err := g.GetEnabledPairs(a) if err != nil { if errors.Is(err, asset.ErrNotEnabled) { return nil, nil // no enabled pairs, subscriptions require an associated pair. @@ -99,15 +99,6 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions(settlement currency.Code) ( return nil, err } - switch { - case settlement.Equal(currency.USDT): - pairs = slices.DeleteFunc(pairs, func(p currency.Pair) bool { return !p.Quote.Equal(currency.USDT) }) - case settlement.Equal(currency.BTC): - pairs = slices.DeleteFunc(pairs, func(p currency.Pair) bool { return p.Quote.Equal(currency.USDT) }) - default: - return nil, fmt.Errorf("settlement currency %s not supported", settlement) - } - var subscriptions subscription.List for i := range channelsToSubscribe { for j := range pairs { @@ -122,7 +113,7 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions(settlement currency.Code) ( params["frequency"] = kline.ThousandMilliseconds params["level"] = "100" } - fPair, err := g.FormatExchangeCurrency(pairs[j], asset.Futures) + fPair, err := g.FormatExchangeCurrency(pairs[j], a) if err != nil { return nil, err } @@ -130,7 +121,7 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions(settlement currency.Code) ( Channel: channelsToSubscribe[i], Pairs: currency.Pairs{fPair.Upper()}, Params: params, - Asset: asset.Futures, + Asset: a, }) } } diff --git a/exchanges/gateio/gateio_websocket_request_futures.go b/exchanges/gateio/gateio_websocket_request_futures.go index 06e77b02..f5aa78dd 100644 --- a/exchanges/gateio/gateio_websocket_request_futures.go +++ b/exchanges/gateio/gateio_websocket_request_futures.go @@ -168,37 +168,11 @@ func (g *Gateio) WebsocketFuturesGetOrderStatus(ctx context.Context, contract cu return &resp, g.SendWebsocketRequest(ctx, perpetualFetchOrderEPL, "futures.order_status", a, params, &resp, 1) } -func getAssetFromFuturesPair(pair currency.Pair) (asset.Item, error) { - if pair.IsEmpty() { - return asset.Empty, currency.ErrCurrencyPairEmpty - } - switch pair.Quote.Item { - case currency.USDT.Item: - return asset.USDTMarginedFutures, nil - case currency.USD.Item: - return asset.CoinMarginedFutures, nil - default: - return asset.Empty, fmt.Errorf("%w futures pair: `%v`", asset.ErrNotSupported, pair) - } -} - -// validateFuturesPairAsset enforces the asset.Item to be either USDT or Coin margined futures in relation to the pair -// for correct routing. +// validateFuturesPairAsset enforces that a futures pair's quote currency matches the given asset func validateFuturesPairAsset(pair currency.Pair, a asset.Item) error { if pair.IsEmpty() { return currency.ErrCurrencyPairEmpty } - switch a { - case asset.USDTMarginedFutures: - if pair.Quote.Item != currency.USDT.Item { - return fmt.Errorf("%w: '%v' for pair '%v'", asset.ErrNotSupported, a, pair) - } - case asset.CoinMarginedFutures: - if pair.Quote.Item != currency.USD.Item { - return fmt.Errorf("%w: '%v' for pair '%v'", asset.ErrNotSupported, a, pair) - } - default: - return fmt.Errorf("%w: '%v' for pair '%v'", asset.ErrNotSupported, a, pair) - } - return nil + _, err := getSettlementCurrency(pair, a) + return err } diff --git a/exchanges/gateio/gateio_websocket_request_futures_test.go b/exchanges/gateio/gateio_websocket_request_futures_test.go index 80ad1354..9929c0a8 100644 --- a/exchanges/gateio/gateio_websocket_request_futures_test.go +++ b/exchanges/gateio/gateio_websocket_request_futures_test.go @@ -93,7 +93,7 @@ func TestWebsocketFuturesCancelOrder(t *testing.T) { _, err = g.WebsocketFuturesCancelOrder(t.Context(), "42069", currency.EMPTYPAIR, asset.Empty) require.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) - _, err = g.WebsocketFuturesCancelOrder(t.Context(), "42069", BTCUSDT, asset.CoinMarginedFutures) + _, err = g.WebsocketFuturesCancelOrder(t.Context(), "42069", BTCUSDT, asset.Empty) require.ErrorIs(t, err, asset.ErrNotSupported) sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders) @@ -204,32 +204,3 @@ func TestWebsocketFuturesGetOrderStatus(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, got) } - -func TestGetAssetFromFuturesPair(t *testing.T) { - t.Parallel() - _, err := getAssetFromFuturesPair(currency.Pair{}) - require.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) - - _, err = getAssetFromFuturesPair(currency.NewPair(currency.BTC, currency.USDC)) - require.ErrorIs(t, err, asset.ErrNotSupported) - - a, err := getAssetFromFuturesPair(BTCUSDT) - require.NoError(t, err) - require.Equal(t, asset.USDTMarginedFutures, a) - - a, err = getAssetFromFuturesPair(BTCUSD) - require.NoError(t, err) - require.Equal(t, asset.CoinMarginedFutures, a) -} - -func TestValidateFuturesPairAsset(t *testing.T) { - t.Parallel() - err := validateFuturesPairAsset(currency.Pair{}, asset.USDTMarginedFutures) - require.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) - - err = validateFuturesPairAsset(BTCUSDT, asset.USDTMarginedFutures) - require.NoError(t, err) - - err = validateFuturesPairAsset(BTCUSD, asset.USDTMarginedFutures) - require.ErrorIs(t, err, asset.ErrNotSupported) -} diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 00fb5139..fe0a5905 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "math" + "slices" "sort" "strconv" "strings" @@ -35,11 +36,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/types" ) -// unfundedFuturesAccount defines an error string when an account associated -// with a settlement currency has not been funded. Use specific pairs to avoid -// this error. -const unfundedFuturesAccount = `please transfer funds first to create futures account` - // SetDefaults sets default values for the exchange func (g *Gateio) SetDefaults() { g.Name = "GateIO" @@ -50,7 +46,7 @@ func (g *Gateio) SetDefaults() { requestFmt := ¤cy.PairFormat{Delimiter: currency.UnderscoreDelimiter, Uppercase: true} configFmt := ¤cy.PairFormat{Delimiter: currency.UnderscoreDelimiter, Uppercase: true} - err := g.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot, asset.Futures, asset.Margin, asset.CrossMargin, asset.DeliveryFutures, asset.Options) + err := g.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot, asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.Margin, asset.CrossMargin, asset.DeliveryFutures, asset.Options) if err != nil { log.Errorln(log.ExchangeSys, err) } @@ -113,7 +109,8 @@ func (g *Gateio) SetDefaults() { kline.EightHour: true, }, FundingRateBatching: map[asset.Item]bool{ - asset.Futures: true, + asset.USDTMarginedFutures: true, + asset.CoinMarginedFutures: true, }, OpenInterest: exchange.OpenInterestSupport{ Supported: true, @@ -227,15 +224,17 @@ func (g *Gateio) Setup(exch *config.Exchange) error { } // Futures connection - USDT margined err = g.Websocket.SetupNewConnection(&websocket.ConnectionSetup{ - URL: futuresWebsocketUsdtURL, + URL: usdtFuturesWebsocketURL, ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, Handler: func(ctx context.Context, incoming []byte) error { - return g.WsHandleFuturesData(ctx, incoming, asset.Futures) + return g.WsHandleFuturesData(ctx, incoming, asset.USDTMarginedFutures) + }, + Subscriber: g.FuturesSubscribe, + Unsubscriber: g.FuturesUnsubscribe, + GenerateSubscriptions: func() (subscription.List, error) { + return g.GenerateFuturesDefaultSubscriptions(asset.USDTMarginedFutures) }, - Subscriber: g.FuturesSubscribe, - Unsubscriber: g.FuturesUnsubscribe, - GenerateSubscriptions: func() (subscription.List, error) { return g.GenerateFuturesDefaultSubscriptions(currency.USDT) }, Connector: g.WsFuturesConnect, Authenticate: g.authenticateFutures, MessageFilter: asset.USDTMarginedFutures, @@ -247,15 +246,17 @@ func (g *Gateio) Setup(exch *config.Exchange) error { // Futures connection - BTC margined err = g.Websocket.SetupNewConnection(&websocket.ConnectionSetup{ - URL: futuresWebsocketBtcURL, + URL: btcFuturesWebsocketURL, ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, Handler: func(ctx context.Context, incoming []byte) error { - return g.WsHandleFuturesData(ctx, incoming, asset.Futures) + return g.WsHandleFuturesData(ctx, incoming, asset.CoinMarginedFutures) + }, + Subscriber: g.FuturesSubscribe, + Unsubscriber: g.FuturesUnsubscribe, + GenerateSubscriptions: func() (subscription.List, error) { + return g.GenerateFuturesDefaultSubscriptions(asset.CoinMarginedFutures) }, - Subscriber: g.FuturesSubscribe, - Unsubscriber: g.FuturesUnsubscribe, - GenerateSubscriptions: func() (subscription.List, error) { return g.GenerateFuturesDefaultSubscriptions(currency.BTC) }, Connector: g.WsFuturesConnect, MessageFilter: asset.CoinMarginedFutures, BespokeGenerateMessageID: g.GenerateWebsocketMessageID, @@ -315,16 +316,14 @@ func (g *Gateio) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item var tickerData *ticker.Price switch a { case asset.Margin, asset.Spot, asset.CrossMargin: - var available bool - available, err = g.checkInstrumentAvailabilityInSpot(fPair) + available, err := g.checkInstrumentAvailabilityInSpot(fPair) if err != nil { return nil, err } if a != asset.Spot && !available { return nil, fmt.Errorf("%v instrument %v does not have ticker data", a, fPair) } - var tickerNew *Ticker - tickerNew, err = g.GetTicker(ctx, fPair.String(), "") + tickerNew, err := g.GetTicker(ctx, fPair.String(), "") if err != nil { return nil, err } @@ -338,34 +337,30 @@ func (g *Gateio) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item ExchangeName: g.Name, AssetType: a, } - case asset.Futures: - var settle currency.Code - settle, err = getSettlementFromCurrency(fPair) + case asset.USDTMarginedFutures, asset.CoinMarginedFutures, asset.DeliveryFutures: + settle, err := getSettlementCurrency(fPair, a) if err != nil { return nil, err } var tickers []FuturesTicker - tickers, err = g.GetFuturesTickers(ctx, settle, fPair) + if a == asset.DeliveryFutures { + tickers, err = g.GetDeliveryFutureTickers(ctx, settle, fPair) + } else { + tickers, err = g.GetFuturesTickers(ctx, settle, fPair) + } if err != nil { return nil, err } - var tick *FuturesTicker - for x := range tickers { - if tickers[x].Contract == fPair.String() { - tick = &tickers[x] - break - } - } - if tick == nil { + if len(tickers) != 1 { return nil, errNoTickerData } tickerData = &ticker.Price{ Pair: fPair, - Low: tick.Low24H.Float64(), - High: tick.High24H.Float64(), - Last: tick.Last.Float64(), - Volume: tick.Volume24HBase.Float64(), - QuoteVolume: tick.Volume24HQuote.Float64(), + Low: tickers[0].Low24H.Float64(), + High: tickers[0].High24H.Float64(), + Last: tickers[0].Last.Float64(), + Volume: tickers[0].Volume24HBase.Float64(), + QuoteVolume: tickers[0].Volume24HQuote.Float64(), ExchangeName: g.Name, AssetType: a, } @@ -386,9 +381,6 @@ func (g *Gateio) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item } cleanQuote := strings.ReplaceAll(tickers[x].Name.Quote.String(), currency.UnderscoreDelimiter, currency.DashDelimiter) tickers[x].Name.Quote = currency.NewCode(cleanQuote) - if err != nil { - return nil, err - } tickerData = &ticker.Price{ Pair: tickers[x].Name, Last: tickers[x].LastPrice.Float64(), @@ -405,35 +397,8 @@ func (g *Gateio) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item } } return ticker.GetTicker(g.Name, fPair, a) - case asset.DeliveryFutures: - var settle currency.Code - settle, err = getSettlementFromCurrency(fPair) - if err != nil { - return nil, err - } - var tickers []FuturesTicker - tickers, err = g.GetDeliveryFutureTickers(ctx, settle, fPair) - if err != nil { - return nil, err - } - for x := range tickers { - if tickers[x].Contract == fPair.Upper().String() { - tickerData = &ticker.Price{ - Pair: fPair, - Last: tickers[x].Last.Float64(), - High: tickers[x].High24H.Float64(), - Low: tickers[x].Low24H.Float64(), - Volume: tickers[x].Volume24H.Float64(), - QuoteVolume: tickers[x].Volume24HQuote.Float64(), - ExchangeName: g.Name, - AssetType: a, - } - break - } - } } - err = ticker.ProcessTicker(tickerData) - if err != nil { + if err := ticker.ProcessTicker(tickerData); err != nil { return nil, err } return ticker.GetTicker(g.Name, fPair, a) @@ -487,22 +452,21 @@ func (g *Gateio) FetchTradablePairs(ctx context.Context, a asset.Item) (currency pairs = append(pairs, cp) } return pairs, nil - case asset.Futures: - btcContracts, err := g.GetAllFutureContracts(ctx, currency.BTC) + case asset.CoinMarginedFutures, asset.USDTMarginedFutures: + settle, err := getSettlementCurrency(currency.EMPTYPAIR, a) if err != nil { return nil, err } - usdtContracts, err := g.GetAllFutureContracts(ctx, currency.USDT) + contracts, err := g.GetAllFutureContracts(ctx, settle) if err != nil { return nil, err } - btcContracts = append(btcContracts, usdtContracts...) - pairs := make([]currency.Pair, 0, len(btcContracts)) - for x := range btcContracts { - if btcContracts[x].InDelisting { + pairs := make([]currency.Pair, 0, len(contracts)) + for i := range contracts { + if contracts[i].InDelisting { continue } - p := strings.ToUpper(btcContracts[x].Name) + p := strings.ToUpper(contracts[i].Name) if !g.IsValidPairString(p) { continue } @@ -512,18 +476,18 @@ func (g *Gateio) FetchTradablePairs(ctx context.Context, a asset.Item) (currency } pairs = append(pairs, cp) } - return pairs, nil + return slices.Clip(pairs), nil case asset.DeliveryFutures: - usdtContracts, err := g.GetAllDeliveryContracts(ctx, currency.USDT) + contracts, err := g.GetAllDeliveryContracts(ctx, currency.USDT) if err != nil { return nil, err } - pairs := make([]currency.Pair, 0, len(usdtContracts)) - for x := range usdtContracts { - if usdtContracts[x].InDelisting { + pairs := make([]currency.Pair, 0, len(contracts)) + for i := range contracts { + if contracts[i].InDelisting { continue } - p := strings.ToUpper(usdtContracts[x].Name) + p := strings.ToUpper(contracts[i].Name) if !g.IsValidPairString(p) { continue } @@ -533,7 +497,7 @@ func (g *Gateio) FetchTradablePairs(ctx context.Context, a asset.Item) (currency } pairs = append(pairs, cp) } - return pairs, nil + return slices.Clip(pairs), nil case asset.Options: underlyings, err := g.GetAllOptionsUnderlyings(ctx) if err != nil { @@ -588,11 +552,9 @@ func (g *Gateio) UpdateTickers(ctx context.Context, a asset.Item) error { if !g.SupportsAsset(a) { return fmt.Errorf("%w asset type: %v", asset.ErrNotSupported, a) } - var err error switch a { case asset.Spot, asset.Margin, asset.CrossMargin: - var tickers []Ticker - tickers, err = g.GetTickers(ctx, currency.EMPTYPAIR.String(), "") + tickers, err := g.GetTickers(ctx, currency.EMPTYPAIR.String(), "") if err != nil { return err } @@ -618,44 +580,37 @@ func (g *Gateio) UpdateTickers(ctx context.Context, a asset.Item) error { return err } } - case asset.Futures, asset.DeliveryFutures: + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: + settle, errs := getSettlementCurrency(currency.EMPTYPAIR, a) + if errs != nil { + return errs + } var tickers []FuturesTicker - var ticks []FuturesTicker - for _, settle := range settlementCurrencies { - // All delivery futures are settled in USDT only, despite the API accepting a settlement currency parameter for all delivery futures endpoints - if a == asset.DeliveryFutures && !settle.Equal(currency.USDT) { + if a == asset.DeliveryFutures { + tickers, errs = g.GetDeliveryFutureTickers(ctx, settle, currency.EMPTYPAIR) + } else { + tickers, errs = g.GetFuturesTickers(ctx, settle, currency.EMPTYPAIR) + } + for i := range tickers { + currencyPair, err := currency.NewPairFromString(tickers[i].Contract) + if err != nil { + errs = common.AppendError(errs, err) continue } - - if a == asset.Futures { - ticks, err = g.GetFuturesTickers(ctx, settle, currency.EMPTYPAIR) - } else { - ticks, err = g.GetDeliveryFutureTickers(ctx, settle, currency.EMPTYPAIR) - } - if err != nil { - return err - } - tickers = append(tickers, ticks...) - } - for x := range tickers { - currencyPair, err := currency.NewPairFromString(tickers[x].Contract) - if err != nil { - return err - } - err = ticker.ProcessTicker(&ticker.Price{ - Last: tickers[x].Last.Float64(), - High: tickers[x].High24H.Float64(), - Low: tickers[x].Low24H.Float64(), - Volume: tickers[x].Volume24H.Float64(), - QuoteVolume: tickers[x].Volume24HQuote.Float64(), + if err = ticker.ProcessTicker(&ticker.Price{ + Last: tickers[i].Last.Float64(), + High: tickers[i].High24H.Float64(), + Low: tickers[i].Low24H.Float64(), + Volume: tickers[i].Volume24H.Float64(), + QuoteVolume: tickers[i].Volume24HQuote.Float64(), ExchangeName: g.Name, Pair: currencyPair, AssetType: a, - }) - if err != nil { - return err + }); err != nil { + errs = common.AppendError(errs, err) } } + return errs case asset.Options: pairs, err := g.GetEnabledPairs(a) if err != nil { @@ -698,7 +653,7 @@ func (g *Gateio) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset.I if err != nil { return nil, err } - var orderbookNew *Orderbook + var o *Orderbook switch a { case asset.Spot, asset.Margin, asset.CrossMargin: var available bool @@ -709,23 +664,18 @@ func (g *Gateio) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset.I if a != asset.Spot && !available { return nil, fmt.Errorf("%v instrument %v does not have orderbook data", a, p) } - orderbookNew, err = g.GetOrderbook(ctx, p.String(), "", 0, true) - case asset.Futures: + o, err = g.GetOrderbook(ctx, p.String(), "", 0, true) + case asset.CoinMarginedFutures, asset.USDTMarginedFutures: var settle currency.Code - settle, err = getSettlementFromCurrency(p) + settle, err = getSettlementCurrency(p, a) if err != nil { return nil, err } - orderbookNew, err = g.GetFuturesOrderbook(ctx, settle, p.String(), "", 0, true) + o, err = g.GetFuturesOrderbook(ctx, settle, p.String(), "", 0, true) case asset.DeliveryFutures: - var settle currency.Code - settle, err = getSettlementFromCurrency(p.Upper()) - if err != nil { - return nil, err - } - orderbookNew, err = g.GetDeliveryOrderbook(ctx, settle, "", p, 0, true) + o, err = g.GetDeliveryOrderbook(ctx, currency.USDT, "", p, 0, true) case asset.Options: - orderbookNew, err = g.GetOptionsOrderbook(ctx, p, "", 0, true) + o, err = g.GetOptionsOrderbook(ctx, p, "", 0, true) default: return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a) } @@ -737,21 +687,21 @@ func (g *Gateio) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset.I Asset: a, VerifyOrderbook: g.CanVerifyOrderbook, Pair: p.Upper(), - LastUpdateID: orderbookNew.ID, - LastUpdated: orderbookNew.Update.Time(), + LastUpdateID: o.ID, + LastUpdated: o.Update.Time(), } - book.Bids = make(orderbook.Tranches, len(orderbookNew.Bids)) - for x := range orderbookNew.Bids { + book.Bids = make(orderbook.Tranches, len(o.Bids)) + for x := range o.Bids { book.Bids[x] = orderbook.Tranche{ - Amount: orderbookNew.Bids[x].Amount, - Price: orderbookNew.Bids[x].Price.Float64(), + Amount: o.Bids[x].Amount, + Price: o.Bids[x].Price.Float64(), } } - book.Asks = make(orderbook.Tranches, len(orderbookNew.Asks)) - for x := range orderbookNew.Asks { + book.Asks = make(orderbook.Tranches, len(o.Asks)) + for x := range o.Asks { book.Asks[x] = orderbook.Tranche{ - Amount: orderbookNew.Asks[x].Amount, - Price: orderbookNew.Asks[x].Price.Float64(), + Amount: o.Asks[x].Amount, + Price: o.Asks[x].Price.Float64(), } } err = book.Process() @@ -763,116 +713,89 @@ func (g *Gateio) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset.I // UpdateAccountInfo retrieves balances for all enabled currencies for the func (g *Gateio) UpdateAccountInfo(ctx context.Context, a asset.Item) (account.Holdings, error) { - var info account.Holdings - info.Exchange = g.Name - var err error + info := account.Holdings{ + Exchange: g.Name, + Accounts: []account.SubAccount{{ + AssetType: a, + }}, + } switch a { case asset.Spot: - var balances []SpotAccount - balances, err = g.GetSpotAccounts(ctx, currency.EMPTYCODE) + balances, err := g.GetSpotAccounts(ctx, currency.EMPTYCODE) + if err != nil { + return info, err + } currencies := make([]account.Balance, len(balances)) - if err != nil { - return info, err - } - for x := range balances { - currencies[x] = account.Balance{ - Currency: currency.NewCode(balances[x].Currency), - Total: balances[x].Available.Float64() - balances[x].Locked.Float64(), - Hold: balances[x].Locked.Float64(), - Free: balances[x].Available.Float64(), + for i := range balances { + currencies[i] = account.Balance{ + Currency: currency.NewCode(balances[i].Currency), + Total: balances[i].Available.Float64() + balances[i].Locked.Float64(), + Hold: balances[i].Locked.Float64(), + Free: balances[i].Available.Float64(), } } - info.Accounts = append(info.Accounts, account.SubAccount{ - AssetType: a, - Currencies: currencies, - }) + info.Accounts[0].Currencies = currencies case asset.Margin, asset.CrossMargin: - var balances []MarginAccountItem - balances, err = g.GetMarginAccountList(ctx, currency.EMPTYPAIR) + balances, err := g.GetMarginAccountList(ctx, currency.EMPTYPAIR) if err != nil { return info, err } - var currencies []account.Balance - for x := range balances { - currencies = append(currencies, account.Balance{ - Currency: currency.NewCode(balances[x].Base.Currency), - Total: balances[x].Base.Available.Float64() + balances[x].Base.LockedAmount.Float64(), - Hold: balances[x].Base.LockedAmount.Float64(), - Free: balances[x].Base.Available.Float64(), - }, account.Balance{ - Currency: currency.NewCode(balances[x].Quote.Currency), - Total: balances[x].Quote.Available.Float64() + balances[x].Quote.LockedAmount.Float64(), - Hold: balances[x].Quote.LockedAmount.Float64(), - Free: balances[x].Quote.Available.Float64(), - }) - } - info.Accounts = append(info.Accounts, account.SubAccount{ - AssetType: a, - Currencies: currencies, - }) - case asset.Futures, asset.DeliveryFutures: - currencies := make([]account.Balance, 0, 2) - for x := range settlementCurrencies { - // All delivery futures are settled in USDT only, despite the API accepting a settlement currency parameter for all delivery futures endpoints - if a == asset.DeliveryFutures && !settlementCurrencies[x].Equal(currency.USDT) { - continue - } - - var balance *FuturesAccount - if a == asset.Futures { - balance, err = g.QueryFuturesAccount(ctx, settlementCurrencies[x]) - } else { - balance, err = g.GetDeliveryFuturesAccounts(ctx, settlementCurrencies[x]) - } - if err != nil { - if strings.Contains(err.Error(), unfundedFuturesAccount) { - if g.Verbose { - log.Warnf(log.ExchangeSys, "%s %v for settlement: %v", g.Name, err, settlementCurrencies[x]) - } - continue - } - return info, err - } - currencies = append(currencies, account.Balance{ - Currency: currency.NewCode(balance.Currency), - Total: balance.Total.Float64(), - Hold: balance.Total.Float64() - balance.Available.Float64(), - Free: balance.Available.Float64(), - }) - } - info.Accounts = append(info.Accounts, account.SubAccount{ - AssetType: a, - Currencies: currencies, - }) - case asset.Options: - var balance *OptionAccount - balance, err = g.GetOptionAccounts(ctx) - if err != nil { - return info, err - } - info.Accounts = append(info.Accounts, account.SubAccount{ - AssetType: a, - Currencies: []account.Balance{ - { - Currency: currency.NewCode(balance.Currency), - Total: balance.Total.Float64(), - Hold: balance.Total.Float64() - balance.Available.Float64(), - Free: balance.Available.Float64(), + currencies := make([]account.Balance, 0, 2*len(balances)) + for i := range balances { + currencies = append(currencies, + account.Balance{ + Currency: currency.NewCode(balances[i].Base.Currency), + Total: balances[i].Base.Available.Float64() + balances[i].Base.LockedAmount.Float64(), + Hold: balances[i].Base.LockedAmount.Float64(), + Free: balances[i].Base.Available.Float64(), }, - }, - }) + account.Balance{ + Currency: currency.NewCode(balances[i].Quote.Currency), + Total: balances[i].Quote.Available.Float64() + balances[i].Quote.LockedAmount.Float64(), + Hold: balances[i].Quote.LockedAmount.Float64(), + Free: balances[i].Quote.Available.Float64(), + }) + } + info.Accounts[0].Currencies = currencies + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: + settle, err := getSettlementCurrency(currency.EMPTYPAIR, a) + if err != nil { + return info, err + } + var acc *FuturesAccount + if a == asset.DeliveryFutures { + acc, err = g.GetDeliveryFuturesAccounts(ctx, settle) + } else { + acc, err = g.QueryFuturesAccount(ctx, settle) + } + if err != nil { + return info, err + } + info.Accounts[0].Currencies = []account.Balance{{ + Currency: currency.NewCode(acc.Currency), + Total: acc.Total.Float64(), + Hold: acc.Total.Float64() - acc.Available.Float64(), + Free: acc.Available.Float64(), + }} + case asset.Options: + balance, err := g.GetOptionAccounts(ctx) + if err != nil { + return info, err + } + info.Accounts[0].Currencies = []account.Balance{{ + Currency: currency.NewCode(balance.Currency), + Total: balance.Total.Float64(), + Hold: balance.Total.Float64() - balance.Available.Float64(), + Free: balance.Available.Float64(), + }} default: return info, fmt.Errorf("%w asset type: %v", asset.ErrNotSupported, a) } creds, err := g.GetCredentials(ctx) - if err != nil { - return info, err + if err == nil { + err = account.Process(&info, creds) } - err = account.Process(&info, creds) - if err != nil { - return info, err - } - return info, nil + return info, err } // GetAccountFundingHistory returns funding history, deposits and @@ -935,12 +858,17 @@ func (g *Gateio) GetRecentTrades(ctx context.Context, p currency.Pair, a asset.I Timestamp: tradeData[i].CreateTime.Time(), } } - case asset.Futures: - settle, err := getSettlementFromCurrency(p) + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: + settle, err := getSettlementCurrency(p, a) if err != nil { return nil, err } - futuresTrades, err := g.GetFuturesTradingHistory(ctx, settle, p, 0, 0, "", time.Time{}, time.Time{}) + var futuresTrades []TradingHistoryItem + if a == asset.DeliveryFutures { + futuresTrades, err = g.GetDeliveryTradingHistory(ctx, settle, "", p.Upper(), 0, time.Time{}, time.Time{}) + } else { + futuresTrades, err = g.GetFuturesTradingHistory(ctx, settle, p, 0, 0, "", time.Time{}, time.Time{}) + } if err != nil { return nil, err } @@ -956,27 +884,6 @@ func (g *Gateio) GetRecentTrades(ctx context.Context, p currency.Pair, a asset.I Timestamp: futuresTrades[i].CreateTime.Time(), } } - case asset.DeliveryFutures: - settle, err := getSettlementFromCurrency(p) - if err != nil { - return nil, err - } - deliveryTrades, err := g.GetDeliveryTradingHistory(ctx, settle, "", p.Upper(), 0, time.Time{}, time.Time{}) - if err != nil { - return nil, err - } - resp = make([]trade.Data, len(deliveryTrades)) - for i := range deliveryTrades { - resp[i] = trade.Data{ - TID: strconv.FormatInt(deliveryTrades[i].ID, 10), - Exchange: g.Name, - CurrencyPair: p, - AssetType: a, - Price: deliveryTrades[i].Price.Float64(), - Amount: deliveryTrades[i].Size, - Timestamp: deliveryTrades[i].CreateTime.Time(), - } - } case asset.Options: trades, err := g.GetOptionsTradeHistory(ctx, p.Upper(), "", 0, 0, time.Time{}, time.Time{}) if err != nil { @@ -1056,26 +963,24 @@ func (g *Gateio) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi response.Date = sOrder.CreateTime.Time() response.LastUpdated = sOrder.UpdateTime.Time() return response, nil - case asset.Futures: + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: // TODO: See https://www.gate.io/docs/developers/apiv4/en/#create-a-futures-order // * iceberg orders // * auto_size (close_long, close_short) // * stp_act (self trade prevention) - settle, err := getSettlementFromCurrency(s.Pair) + amountWithDirection, err := getFutureOrderSize(s) if err != nil { return nil, err } - var amountWithDirection float64 - amountWithDirection, err = getFutureOrderSize(s) + timeInForce, err := getTimeInForce(s) if err != nil { return nil, err } - var timeInForce string - timeInForce, err = getTimeInForce(s) + settle, err := getSettlementCurrency(s.Pair, s.AssetType) if err != nil { return nil, err } - fOrder, err := g.PlaceFuturesOrder(ctx, &ContractOrderCreateParams{ + orderParams := &ContractOrderCreateParams{ Contract: s.Pair, Size: amountWithDirection, Price: strconv.FormatFloat(s.Price, 'f', -1, 64), // Cannot be an empty string, requires "0" for market orders. @@ -1083,76 +988,34 @@ func (g *Gateio) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi ReduceOnly: s.ReduceOnly, TimeInForce: timeInForce, Text: s.ClientOrderID, - }) + } + var o *Order + if s.AssetType == asset.DeliveryFutures { + o, err = g.PlaceDeliveryOrder(ctx, orderParams) + } else { + o, err = g.PlaceFuturesOrder(ctx, orderParams) + } if err != nil { return nil, err } - response, err := s.DeriveSubmitResponse(strconv.FormatInt(fOrder.ID, 10)) + resp, err := s.DeriveSubmitResponse(strconv.FormatInt(o.ID, 10)) if err != nil { return nil, err } - status := order.Open - if fOrder.Status != statusOpen { - status, err = order.StringToOrderStatus(fOrder.FinishAs) + if o.Status != statusOpen { + resp.Status, err = order.StringToOrderStatus(o.FinishAs) if err != nil { return nil, err } + } else { + resp.Status = order.Open } - response.Status = status - response.Pair = s.Pair - response.Date = fOrder.CreateTime.Time() - response.ClientOrderID = getClientOrderIDFromText(fOrder.Text) - response.ReduceOnly = fOrder.IsReduceOnly - response.Amount = math.Abs(fOrder.Size) - response.Price = fOrder.OrderPrice.Float64() - response.AverageExecutedPrice = fOrder.FillPrice.Float64() - return response, nil - case asset.DeliveryFutures: - settle, err := getSettlementFromCurrency(s.Pair) - if err != nil { - return nil, err - } - var amountWithDirection float64 - amountWithDirection, err = getFutureOrderSize(s) - if err != nil { - return nil, err - } - var timeInForce string - timeInForce, err = getTimeInForce(s) - if err != nil { - return nil, err - } - newOrder, err := g.PlaceDeliveryOrder(ctx, &ContractOrderCreateParams{ - Contract: s.Pair, - Size: amountWithDirection, - Price: strconv.FormatFloat(s.Price, 'f', -1, 64), // Cannot be an empty string, requires "0" for market orders. - Settle: settle, - ReduceOnly: s.ReduceOnly, - TimeInForce: timeInForce, - Text: s.ClientOrderID, - }) - if err != nil { - return nil, err - } - response, err := s.DeriveSubmitResponse(strconv.FormatInt(newOrder.ID, 10)) - if err != nil { - return nil, err - } - status := order.Open - if newOrder.Status != statusOpen { - status, err = order.StringToOrderStatus(newOrder.FinishAs) - if err != nil { - return nil, err - } - } - response.Status = status - response.Pair = s.Pair - response.Date = newOrder.CreateTime.Time() - response.ClientOrderID = getClientOrderIDFromText(newOrder.Text) - response.Amount = math.Abs(newOrder.Size) - response.Price = newOrder.OrderPrice.Float64() - response.AverageExecutedPrice = newOrder.FillPrice.Float64() - return response, nil + resp.Date = o.CreateTime.Time() + resp.ClientOrderID = getClientOrderIDFromText(o.Text) + resp.Amount = math.Abs(o.Size) + resp.Price = o.OrderPrice.Float64() + resp.AverageExecutedPrice = o.FillPrice.Float64() + return resp, nil case asset.Options: optionOrder, err := g.PlaceOptionOrder(ctx, &OptionOrderParam{ Contract: s.Pair.String(), @@ -1199,19 +1062,14 @@ func (g *Gateio) CancelOrder(ctx context.Context, o *order.Cancel) error { switch o.AssetType { case asset.Spot, asset.Margin, asset.CrossMargin: _, err = g.CancelSingleSpotOrder(ctx, o.OrderID, fPair.String(), o.AssetType == asset.CrossMargin) - case asset.Futures, asset.DeliveryFutures: + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: var settle currency.Code - settle, err = getSettlementFromCurrency(fPair) - if err != nil { - return err - } - if o.AssetType == asset.Futures { - _, err = g.CancelSingleFuturesOrder(ctx, settle, o.OrderID) - } else { - _, err = g.CancelSingleDeliveryOrder(ctx, settle, o.OrderID) - } - if err != nil { - return err + if settle, err = getSettlementCurrency(o.Pair, o.AssetType); err == nil { + if o.AssetType == asset.DeliveryFutures { + _, err = g.CancelSingleDeliveryOrder(ctx, settle, o.OrderID) + } else { + _, err = g.CancelSingleFuturesOrder(ctx, settle, o.OrderID) + } } case asset.Options: _, err = g.CancelOptionSingleOrder(ctx, o.OrderID) @@ -1223,8 +1081,9 @@ func (g *Gateio) CancelOrder(ctx context.Context, o *order.Cancel) error { // CancelBatchOrders cancels an orders by their corresponding ID numbers func (g *Gateio) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order.CancelBatchResponse, error) { - var response order.CancelBatchResponse - response.Status = map[string]string{} + response := order.CancelBatchResponse{ + Status: map[string]string{}, + } if len(o) == 0 { return nil, errors.New("no cancel order passed") } @@ -1267,47 +1126,39 @@ func (g *Gateio) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*orde if err != nil { return nil, err } - for x := range cancel { - response.Status[cancel[x].OrderID] = func() string { - if cancel[x].Succeeded { - return order.Cancelled.String() - } - return "" - }() + for j := range cancel { + if cancel[j].Succeeded { + response.Status[cancel[j].OrderID] = order.Cancelled.String() + } } } - case asset.Futures: - for a := range o { - cancel, err := g.CancelMultipleFuturesOpenOrders(ctx, o[a].Pair, o[a].Side.Lower(), o[a].Pair.Quote) + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: + for i := range o { + settle, err := getSettlementCurrency(o[i].Pair, a) if err != nil { return nil, err } - for x := range cancel { - response.Status[strconv.FormatInt(cancel[x].ID, 10)] = cancel[x].Status + var resp []Order + if a == asset.DeliveryFutures { + resp, err = g.CancelMultipleDeliveryOrders(ctx, o[i].Pair, o[i].Side.Lower(), settle) + } else { + resp, err = g.CancelMultipleFuturesOpenOrders(ctx, o[i].Pair, o[i].Side.Lower(), settle) } - } - case asset.DeliveryFutures: - for a := range o { - settle, err := getSettlementFromCurrency(o[a].Pair) if err != nil { return nil, err } - cancel, err := g.CancelMultipleDeliveryOrders(ctx, o[a].Pair, o[a].Side.Lower(), settle) - if err != nil { - return nil, err - } - for x := range cancel { - response.Status[strconv.FormatInt(cancel[x].ID, 10)] = cancel[x].Status + for j := range resp { + response.Status[strconv.FormatInt(resp[j].ID, 10)] = resp[j].Status } } case asset.Options: - for a := range o { - cancel, err := g.CancelMultipleOptionOpenOrders(ctx, o[a].Pair, o[a].Pair.String(), o[a].Side.Lower()) + for i := range o { + cancel, err := g.CancelMultipleOptionOpenOrders(ctx, o[i].Pair, o[i].Pair.String(), o[i].Side.Lower()) if err != nil { return nil, err } - for x := range cancel { - response.Status[strconv.FormatInt(cancel[x].OptionOrderID, 10)] = cancel[x].Status + for j := range cancel { + response.Status[strconv.FormatInt(cancel[j].OptionOrderID, 10)] = cancel[j].Status } } default: @@ -1337,34 +1188,20 @@ func (g *Gateio) CancelAllOrders(ctx context.Context, o *order.Cancel) (order.Ca for x := range cancel { cancelAllOrdersResponse.Status[strconv.FormatInt(cancel[x].AutoOrderID, 10)] = cancel[x].Status } - case asset.Futures: + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: if o.Pair.IsEmpty() { return cancelAllOrdersResponse, currency.ErrCurrencyPairEmpty } - var settle currency.Code - settle, err = getSettlementFromCurrency(o.Pair) + settle, err := getSettlementCurrency(o.Pair, o.AssetType) if err != nil { return cancelAllOrdersResponse, err } var cancel []Order - cancel, err = g.CancelMultipleFuturesOpenOrders(ctx, o.Pair, o.Side.Lower(), settle) - if err != nil { - return cancelAllOrdersResponse, err + if o.AssetType == asset.DeliveryFutures { + cancel, err = g.CancelMultipleDeliveryOrders(ctx, o.Pair, o.Side.Lower(), settle) + } else { + cancel, err = g.CancelMultipleFuturesOpenOrders(ctx, o.Pair, o.Side.Lower(), settle) } - for f := range cancel { - cancelAllOrdersResponse.Status[strconv.FormatInt(cancel[f].ID, 10)] = cancel[f].Status - } - case asset.DeliveryFutures: - if o.Pair.IsEmpty() { - return cancelAllOrdersResponse, currency.ErrCurrencyPairEmpty - } - var settle currency.Code - settle, err = getSettlementFromCurrency(o.Pair) - if err != nil { - return cancelAllOrdersResponse, err - } - var cancel []Order - cancel, err = g.CancelMultipleDeliveryOrders(ctx, o.Pair, o.Side.Lower(), settle) if err != nil { return cancelAllOrdersResponse, err } @@ -1440,18 +1277,16 @@ func (g *Gateio) GetOrderInfo(ctx context.Context, orderID string, pair currency Date: spotOrder.CreateTime.Time(), LastUpdated: spotOrder.UpdateTime.Time(), }, nil - case asset.Futures, asset.DeliveryFutures: - var settle currency.Code - settle, err = getSettlementFromCurrency(pair) + case asset.USDTMarginedFutures, asset.CoinMarginedFutures, asset.DeliveryFutures: + settle, err := getSettlementCurrency(pair, a) if err != nil { return nil, err } var fOrder *Order - var err error - if asset.Futures == a { - fOrder, err = g.GetSingleFuturesOrder(ctx, settle, orderID) - } else { + if a == asset.DeliveryFutures { fOrder, err = g.GetSingleDeliveryOrder(ctx, settle, orderID) + } else { + fOrder, err = g.GetSingleFuturesOrder(ctx, settle, orderID) } if err != nil { return nil, err @@ -1604,14 +1439,12 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque } switch req.AssetType { case asset.Spot, asset.Margin, asset.CrossMargin: - var spotOrders []SpotOrdersDetail - spotOrders, err = g.GetSpotOpenOrders(ctx, 0, 0, req.AssetType == asset.CrossMargin) + spotOrders, err := g.GetSpotOpenOrders(ctx, 0, 0, req.AssetType == asset.CrossMargin) if err != nil { return nil, err } for x := range spotOrders { - var symbol currency.Pair - symbol, err = currency.NewPairDelimiter(spotOrders[x].CurrencyPair, format.Delimiter) + symbol, err := currency.NewPairDelimiter(spotOrders[x].CurrencyPair, format.Delimiter) if err != nil { return nil, err } @@ -1619,18 +1452,15 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque if spotOrders[x].Orders[y].Status != statusOpen { continue } - var side order.Side - side, err = order.StringToOrderSide(spotOrders[x].Orders[y].Side) + side, err := order.StringToOrderSide(spotOrders[x].Orders[y].Side) if err != nil { log.Errorf(log.ExchangeSys, "%s %v", g.Name, err) } - var oType order.Type - oType, err = order.StringToOrderType(spotOrders[x].Orders[y].Type) + oType, err := order.StringToOrderType(spotOrders[x].Orders[y].Type) if err != nil { return nil, err } - var status order.Status - status, err = order.StringToOrderStatus(spotOrders[x].Orders[y].Status) + status, err := order.StringToOrderStatus(spotOrders[x].Orders[y].Status) if err != nil { log.Errorf(log.ExchangeSys, "%s %v", g.Name, err) } @@ -1654,76 +1484,52 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque }) } } - case asset.Futures, asset.DeliveryFutures: - settlements := map[currency.Code]bool{} - if len(req.Pairs) == 0 { - for x := range settlementCurrencies { - settlements[settlementCurrencies[x]] = true - } - } else { - for x := range req.Pairs { - var settle currency.Code - settle, err = getSettlementFromCurrency(req.Pairs[x]) - if err != nil { - return nil, err - } - settlements[settle] = true - } + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: + settle, err := getSettlementCurrency(currency.EMPTYPAIR, req.AssetType) + if err != nil { + return nil, err } + var futuresOrders []Order + if req.AssetType == asset.DeliveryFutures { + futuresOrders, err = g.GetDeliveryOrders(ctx, currency.EMPTYPAIR, statusOpen, settle, "", 0, 0, 0) + } else { + futuresOrders, err = g.GetFuturesOrders(ctx, currency.EMPTYPAIR, statusOpen, "", settle, 0, 0, 0) + } + if err != nil { + return nil, err + } + for i := range futuresOrders { + pair, err := currency.NewPairFromString(futuresOrders[i].Contract) + if err != nil { + return nil, err + } - for settlement := range settlements { - // All delivery futures are settled in USDT only, despite the API accepting a settlement currency parameter for all delivery futures endpoints - if req.AssetType == asset.DeliveryFutures && !settlement.Equal(currency.USDT) { + if futuresOrders[i].Status != statusOpen || (len(req.Pairs) > 0 && !req.Pairs.Contains(pair, true)) { continue } - var futuresOrders []Order - if req.AssetType == asset.Futures { - futuresOrders, err = g.GetFuturesOrders(ctx, currency.EMPTYPAIR, statusOpen, "", settlement, 0, 0, 0) - } else { - futuresOrders, err = g.GetDeliveryOrders(ctx, currency.EMPTYPAIR, statusOpen, settlement, "", 0, 0, 0) - } - if err != nil { - if strings.Contains(err.Error(), unfundedFuturesAccount) { - log.Warnf(log.ExchangeSys, "%s %v", g.Name, err) - continue - } - return nil, err - } - for x := range futuresOrders { - var pair currency.Pair - pair, err = currency.NewPairFromString(futuresOrders[x].Contract) - if err != nil { - return nil, err - } - - if futuresOrders[x].Status != statusOpen || (len(req.Pairs) > 0 && !req.Pairs.Contains(pair, true)) { - continue - } - - side, amount, remaining := getSideAndAmountFromSize(futuresOrders[x].Size, futuresOrders[x].RemainingAmount) - orders = append(orders, order.Detail{ - Status: order.Open, - Amount: amount, - ContractAmount: amount, - Pair: pair, - OrderID: strconv.FormatInt(futuresOrders[x].ID, 10), - ClientOrderID: getClientOrderIDFromText(futuresOrders[x].Text), - Price: futuresOrders[x].OrderPrice.Float64(), - ExecutedAmount: amount - remaining, - RemainingAmount: remaining, - LastUpdated: futuresOrders[x].FinishTime.Time(), - Date: futuresOrders[x].CreateTime.Time(), - Exchange: g.Name, - AssetType: req.AssetType, - Side: side, - Type: order.Limit, - SettlementCurrency: settlement, - ReduceOnly: futuresOrders[x].IsReduceOnly, - PostOnly: futuresOrders[x].TimeInForce == pocTIF, - AverageExecutedPrice: futuresOrders[x].FillPrice.Float64(), - }) - } + side, amount, remaining := getSideAndAmountFromSize(futuresOrders[i].Size, futuresOrders[i].RemainingAmount) + orders = append(orders, order.Detail{ + Status: order.Open, + Amount: amount, + ContractAmount: amount, + Pair: pair, + OrderID: strconv.FormatInt(futuresOrders[i].ID, 10), + ClientOrderID: getClientOrderIDFromText(futuresOrders[i].Text), + Price: futuresOrders[i].OrderPrice.Float64(), + ExecutedAmount: amount - remaining, + RemainingAmount: remaining, + LastUpdated: futuresOrders[i].FinishTime.Time(), + Date: futuresOrders[i].CreateTime.Time(), + Exchange: g.Name, + AssetType: req.AssetType, + Side: side, + Type: order.Limit, + SettlementCurrency: settle, + ReduceOnly: futuresOrders[i].IsReduceOnly, + PostOnly: futuresOrders[i].TimeInForce == "poc", + AverageExecutedPrice: futuresOrders[i].FillPrice.Float64(), + }) } case asset.Options: var optionsOrders []OptionOrderResponse @@ -1731,30 +1537,30 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque if err != nil { return nil, err } - for x := range optionsOrders { + for i := range optionsOrders { var currencyPair currency.Pair var status order.Status - currencyPair, err = currency.NewPairFromString(optionsOrders[x].Contract) + currencyPair, err = currency.NewPairFromString(optionsOrders[i].Contract) if err != nil { return nil, err } - status, err = order.StringToOrderStatus(optionsOrders[x].Status) + status, err = order.StringToOrderStatus(optionsOrders[i].Status) if err != nil { return nil, err } orders = append(orders, order.Detail{ Status: status, - Amount: optionsOrders[x].Size, + Amount: optionsOrders[i].Size, Pair: currencyPair, - OrderID: strconv.FormatInt(optionsOrders[x].OptionOrderID, 10), - Price: optionsOrders[x].Price.Float64(), - ExecutedAmount: optionsOrders[x].Size - optionsOrders[x].Left, - RemainingAmount: optionsOrders[x].Left, - LastUpdated: optionsOrders[x].FinishTime.Time(), - Date: optionsOrders[x].CreateTime.Time(), + OrderID: strconv.FormatInt(optionsOrders[i].OptionOrderID, 10), + Price: optionsOrders[i].Price.Float64(), + ExecutedAmount: optionsOrders[i].Size - optionsOrders[i].Left, + RemainingAmount: optionsOrders[i].Left, + LastUpdated: optionsOrders[i].FinishTime.Time(), + Date: optionsOrders[i].CreateTime.Time(), Exchange: g.Name, AssetType: req.AssetType, - ClientOrderID: optionsOrders[x].Text, + ClientOrderID: optionsOrders[i].Text, }) } default: @@ -1766,8 +1572,7 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque // GetOrderHistory retrieves account order information // Can Limit response to specific order status func (g *Gateio) GetOrderHistory(ctx context.Context, req *order.MultiOrderRequest) (order.FilteredOrders, error) { - err := req.Validate() - if err != nil { + if err := req.Validate(); err != nil { return nil, err } var orders []order.Detail @@ -1779,8 +1584,7 @@ func (g *Gateio) GetOrderHistory(ctx context.Context, req *order.MultiOrderReque case asset.Spot, asset.Margin, asset.CrossMargin: for x := range req.Pairs { fPair := req.Pairs[x].Format(format) - var spotOrders []SpotPersonalTradeHistory - spotOrders, err = g.GetMySpotTradingHistory(ctx, fPair, req.FromOrderID, 0, 0, req.AssetType == asset.CrossMargin, req.StartTime, req.EndTime) + spotOrders, err := g.GetMySpotTradingHistory(ctx, fPair, req.FromOrderID, 0, 0, req.AssetType == asset.CrossMargin, req.StartTime, req.EndTime) if err != nil { return nil, err } @@ -1807,19 +1611,18 @@ func (g *Gateio) GetOrderHistory(ctx context.Context, req *order.MultiOrderReque orders = append(orders, detail) } } - case asset.Futures, asset.DeliveryFutures: + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: for x := range req.Pairs { fPair := req.Pairs[x].Format(format) - var settle currency.Code - settle, err = getSettlementFromCurrency(fPair) + settle, err := getSettlementCurrency(fPair, req.AssetType) if err != nil { return nil, err } var futuresOrder []TradingHistoryItem - if req.AssetType == asset.Futures { - futuresOrder, err = g.GetMyFuturesTradingHistory(ctx, settle, "", req.FromOrderID, fPair, 0, 0, 0) - } else { + if req.AssetType == asset.DeliveryFutures { futuresOrder, err = g.GetMyDeliveryTradingHistory(ctx, settle, req.FromOrderID, fPair, 0, 0, 0, "") + } else { + futuresOrder, err = g.GetMyFuturesTradingHistory(ctx, settle, "", req.FromOrderID, fPair, 0, 0, 0) } if err != nil { return nil, err @@ -1874,46 +1677,44 @@ func (g *Gateio) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a var listCandlesticks []kline.Candle switch a { case asset.Spot, asset.Margin, asset.CrossMargin: - var candles []Candlestick - candles, err = g.GetCandlesticks(ctx, req.RequestFormatted, 0, start, end, interval) + candles, err := g.GetCandlesticks(ctx, req.RequestFormatted, 0, start, end, interval) if err != nil { return nil, err } listCandlesticks = make([]kline.Candle, len(candles)) - for x := range candles { - listCandlesticks[x] = kline.Candle{ - Time: candles[x].Timestamp, - Open: candles[x].OpenPrice, - High: candles[x].HighestPrice, - Low: candles[x].LowestPrice, - Close: candles[x].ClosePrice, - Volume: candles[x].QuoteCcyVolume, + for i := range candles { + listCandlesticks[i] = kline.Candle{ + Time: candles[i].Timestamp, + Open: candles[i].OpenPrice, + High: candles[i].HighestPrice, + Low: candles[i].LowestPrice, + Close: candles[i].ClosePrice, + Volume: candles[i].QuoteCcyVolume, } } - case asset.Futures, asset.DeliveryFutures: - var settlement currency.Code - settlement, err = getSettlementFromCurrency(req.RequestFormatted) + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: + settle, err := getSettlementCurrency(pair, a) if err != nil { return nil, err } var candles []FuturesCandlestick - if a == asset.Futures { - candles, err = g.GetFuturesCandlesticks(ctx, settlement, req.RequestFormatted.String(), start, end, 0, interval) + if a == asset.DeliveryFutures { + candles, err = g.GetDeliveryFuturesCandlesticks(ctx, settle, req.RequestFormatted.Upper(), start, end, 0, interval) } else { - candles, err = g.GetDeliveryFuturesCandlesticks(ctx, settlement, req.RequestFormatted.Upper(), start, end, 0, interval) + candles, err = g.GetFuturesCandlesticks(ctx, settle, req.RequestFormatted.String(), start, end, 0, interval) } if err != nil { return nil, err } listCandlesticks = make([]kline.Candle, len(candles)) - for x := range candles { - listCandlesticks[x] = kline.Candle{ - Time: candles[x].Timestamp.Time(), - Open: candles[x].OpenPrice.Float64(), - High: candles[x].HighestPrice.Float64(), - Low: candles[x].LowestPrice.Float64(), - Close: candles[x].ClosePrice.Float64(), - Volume: candles[x].Volume, + for i := range candles { + listCandlesticks[i] = kline.Candle{ + Time: candles[i].Timestamp.Time(), + Open: candles[i].OpenPrice.Float64(), + High: candles[i].HighestPrice.Float64(), + Low: candles[i].LowestPrice.Float64(), + Close: candles[i].ClosePrice.Float64(), + Volume: candles[i].Volume, } } default: @@ -1929,47 +1730,45 @@ func (g *Gateio) GetHistoricCandlesExtended(ctx context.Context, pair currency.P return nil, err } candlestickItems := make([]kline.Candle, 0, req.Size()) - for b := range req.RangeHolder.Ranges { + for _, r := range req.RangeHolder.Ranges { switch a { case asset.Spot, asset.Margin, asset.CrossMargin: - var candles []Candlestick - candles, err = g.GetCandlesticks(ctx, req.RequestFormatted, 0, req.RangeHolder.Ranges[b].Start.Time, req.RangeHolder.Ranges[b].End.Time, interval) + candles, err := g.GetCandlesticks(ctx, req.RequestFormatted, 0, r.Start.Time, r.End.Time, interval) if err != nil { return nil, err } - for x := range candles { + for j := range candles { candlestickItems = append(candlestickItems, kline.Candle{ - Time: candles[x].Timestamp, - Open: candles[x].OpenPrice, - High: candles[x].HighestPrice, - Low: candles[x].LowestPrice, - Close: candles[x].ClosePrice, - Volume: candles[x].QuoteCcyVolume, + Time: candles[j].Timestamp, + Open: candles[j].OpenPrice, + High: candles[j].HighestPrice, + Low: candles[j].LowestPrice, + Close: candles[j].ClosePrice, + Volume: candles[j].QuoteCcyVolume, }) } - case asset.Futures, asset.DeliveryFutures: - var settle currency.Code - settle, err = getSettlementFromCurrency(req.RequestFormatted) + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: + settle, err := getSettlementCurrency(pair, a) if err != nil { return nil, err } var candles []FuturesCandlestick - if a == asset.Futures { - candles, err = g.GetFuturesCandlesticks(ctx, settle, req.RequestFormatted.String(), req.RangeHolder.Ranges[b].Start.Time, req.RangeHolder.Ranges[b].End.Time, 0, interval) + if a == asset.DeliveryFutures { + candles, err = g.GetDeliveryFuturesCandlesticks(ctx, settle, req.RequestFormatted.Upper(), r.Start.Time, r.End.Time, 0, interval) } else { - candles, err = g.GetDeliveryFuturesCandlesticks(ctx, settle, req.RequestFormatted.Upper(), req.RangeHolder.Ranges[b].Start.Time, req.RangeHolder.Ranges[b].End.Time, 0, interval) + candles, err = g.GetFuturesCandlesticks(ctx, settle, req.RequestFormatted.String(), r.Start.Time, r.End.Time, 0, interval) } if err != nil { return nil, err } - for x := range candles { + for i := range candles { candlestickItems = append(candlestickItems, kline.Candle{ - Time: candles[x].Timestamp.Time(), - Open: candles[x].OpenPrice.Float64(), - High: candles[x].HighestPrice.Float64(), - Low: candles[x].LowestPrice.Float64(), - Close: candles[x].ClosePrice.Float64(), - Volume: candles[x].Volume, + Time: candles[i].Timestamp.Time(), + Open: candles[i].OpenPrice.Float64(), + High: candles[i].HighestPrice.Float64(), + Low: candles[i].LowestPrice.Float64(), + Close: candles[i].ClosePrice.Float64(), + Volume: candles[i].Volume, }) } default: @@ -2013,60 +1812,57 @@ func (g *Gateio) checkInstrumentAvailabilityInSpot(instrument currency.Pair) (bo } // GetFuturesContractDetails returns details about futures contracts -func (g *Gateio) GetFuturesContractDetails(ctx context.Context, item asset.Item) ([]futures.Contract, error) { - if !item.IsFutures() { +func (g *Gateio) GetFuturesContractDetails(ctx context.Context, a asset.Item) ([]futures.Contract, error) { + if !a.IsFutures() { return nil, futures.ErrNotFuturesAsset } - if !g.SupportsAsset(item) { - return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, item) + if !g.SupportsAsset(a) { + return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a) } - switch item { - case asset.Futures: - var resp []futures.Contract - for k := range settlementCurrencies { - contracts, err := g.GetAllFutureContracts(ctx, settlementCurrencies[k]) + settle, err := getSettlementCurrency(currency.EMPTYPAIR, a) + if err != nil { + return nil, err + } + switch a { + case asset.CoinMarginedFutures, asset.USDTMarginedFutures: + contracts, err := g.GetAllFutureContracts(ctx, settle) + if err != nil { + return nil, err + } + resp := make([]futures.Contract, len(contracts)) + for i := range contracts { + name, err := currency.NewPairFromString(contracts[i].Name) if err != nil { return nil, err } - contractsToAdd := make([]futures.Contract, len(contracts)) - for j := range contracts { - var name currency.Pair - name, err = currency.NewPairFromString(contracts[j].Name) - if err != nil { - return nil, err - } - contractSettlementType := futures.Linear - switch { - case name.Base.Equal(currency.BTC) && settlementCurrencies[k].Equal(currency.BTC): - contractSettlementType = futures.Inverse - case !name.Base.Equal(settlementCurrencies[k]) && !settlementCurrencies[k].Equal(currency.USDT): - contractSettlementType = futures.Quanto - } - c := futures.Contract{ - Exchange: g.Name, - Name: name, - Underlying: name, - Asset: item, - IsActive: !contracts[j].InDelisting, - Type: futures.Perpetual, - SettlementType: contractSettlementType, - SettlementCurrencies: currency.Currencies{settlementCurrencies[k]}, - Multiplier: contracts[j].QuantoMultiplier.Float64(), - MaxLeverage: contracts[j].LeverageMax.Float64(), - } - if contracts[j].FundingRate > 0 { - c.LatestRate = fundingrate.Rate{ - Time: contracts[j].FundingNextApply.Time().Add(-time.Duration(contracts[j].FundingInterval) * time.Second), - Rate: contracts[j].FundingRate.Decimal(), - } - } - contractsToAdd[j] = c + contractSettlementType := futures.Linear + switch { + case name.Base.Equal(currency.BTC) && settle.Equal(currency.BTC): + contractSettlementType = futures.Inverse + case !name.Base.Equal(settle) && !settle.Equal(currency.USDT): + contractSettlementType = futures.Quanto } - resp = append(resp, contractsToAdd...) + c := futures.Contract{ + Exchange: g.Name, + Name: name, + Underlying: name, + Asset: a, + IsActive: !contracts[i].InDelisting, + Type: futures.Perpetual, + SettlementType: contractSettlementType, + SettlementCurrencies: currency.Currencies{settle}, + Multiplier: contracts[i].QuantoMultiplier.Float64(), + MaxLeverage: contracts[i].LeverageMax.Float64(), + } + c.LatestRate = fundingrate.Rate{ + Time: contracts[i].FundingNextApply.Time().Add(-time.Duration(contracts[i].FundingInterval) * time.Second), + Rate: contracts[i].FundingRate.Decimal(), + } + resp[i] = c } return resp, nil case asset.DeliveryFutures: - contracts, err := g.GetAllDeliveryContracts(ctx, currency.USDT) + contracts, err := g.GetAllDeliveryContracts(ctx, settle) if err != nil { return nil, err } @@ -2103,13 +1899,13 @@ func (g *Gateio) GetFuturesContractDetails(ctx context.Context, item asset.Item) Exchange: g.Name, Name: name, Underlying: underlying, - Asset: item, + Asset: a, StartDate: s, EndDate: e, SettlementType: futures.Linear, IsActive: !contracts[i].InDelisting, Type: ct, - SettlementCurrencies: currency.Currencies{currency.USDT}, + SettlementCurrencies: currency.Currencies{settle}, MarginCurrency: currency.Code{}, Multiplier: contracts[i].QuantoMultiplier.Float64(), MaxLeverage: contracts[i].LeverageMax.Float64(), @@ -2117,7 +1913,7 @@ func (g *Gateio) GetFuturesContractDetails(ctx context.Context, item asset.Item) } return resp, nil } - return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, item) + return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a) } // UpdateOrderExecutionLimits sets exchange executions for a required asset type @@ -2129,37 +1925,35 @@ func (g *Gateio) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) e var limits []order.MinMaxLevel switch a { case asset.Spot: - var pairsData []CurrencyPairDetail pairsData, err := g.ListSpotCurrencyPairs(ctx) if err != nil { return err } limits = make([]order.MinMaxLevel, 0, len(pairsData)) - for x := range pairsData { - if pairsData[x].TradeStatus == "untradable" { + for i := range pairsData { + if pairsData[i].TradeStatus == "untradable" { continue } - var pair currency.Pair - pair, err = g.MatchSymbolWithAvailablePairs(pairsData[x].ID, a, true) + pair, err := g.MatchSymbolWithAvailablePairs(pairsData[i].ID, a, true) if err != nil { return err } // Minimum base amounts are not always provided this will default to // precision for base deployment. This can't be done for quote. - minBaseAmount := pairsData[x].MinBaseAmount.Float64() + minBaseAmount := pairsData[i].MinBaseAmount.Float64() if minBaseAmount == 0 { - minBaseAmount = math.Pow10(-int(pairsData[x].AmountPrecision)) + minBaseAmount = math.Pow10(-int(pairsData[i].AmountPrecision)) } limits = append(limits, order.MinMaxLevel{ Asset: a, Pair: pair, - QuoteStepIncrementSize: math.Pow10(-int(pairsData[x].Precision)), - AmountStepIncrementSize: math.Pow10(-int(pairsData[x].AmountPrecision)), + QuoteStepIncrementSize: math.Pow10(-int(pairsData[i].Precision)), + AmountStepIncrementSize: math.Pow10(-int(pairsData[i].AmountPrecision)), MinimumBaseAmount: minBaseAmount, - MinimumQuoteAmount: pairsData[x].MinQuoteAmount.Float64(), + MinimumQuoteAmount: pairsData[i].MinQuoteAmount.Float64(), }) } default: @@ -2175,7 +1969,7 @@ func (g *Gateio) GetHistoricalFundingRates(ctx context.Context, r *fundingrate.H if r == nil { return nil, fmt.Errorf("%w LatestRateRequest", common.ErrNilPointer) } - if r.Asset != asset.Futures { + if r.Asset != asset.CoinMarginedFutures && r.Asset != asset.USDTMarginedFutures { return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, r.Asset) } @@ -2184,8 +1978,7 @@ func (g *Gateio) GetHistoricalFundingRates(ctx context.Context, r *fundingrate.H } if !r.StartDate.IsZero() && !r.EndDate.IsZero() { - err := common.StartEndTimeCheck(r.StartDate, r.EndDate) - if err != nil { + if err := common.StartEndTimeCheck(r.StartDate, r.EndDate); err != nil { return nil, err } } @@ -2257,65 +2050,55 @@ func (g *Gateio) GetLatestFundingRates(ctx context.Context, r *fundingrate.Lates if r == nil { return nil, fmt.Errorf("%w LatestRateRequest", common.ErrNilPointer) } - if r.Asset != asset.Futures { + if r.Asset != asset.CoinMarginedFutures && r.Asset != asset.USDTMarginedFutures { return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, r.Asset) } + settle, err := getSettlementCurrency(r.Pair, r.Asset) + if err != nil { + return nil, err + } + if !r.Pair.IsEmpty() { - resp := make([]fundingrate.LatestRateResponse, 1) fPair, err := g.FormatExchangeCurrency(r.Pair, r.Asset) if err != nil { return nil, err } - var settle currency.Code - settle, err = getSettlementFromCurrency(fPair) - if err != nil { - return nil, err - } contract, err := g.GetFuturesContract(ctx, settle, fPair.String()) if err != nil { return nil, err } - resp[0] = contractToFundingRate(g.Name, r.Asset, fPair, contract, r.IncludePredictedRate) - return resp, nil + return []fundingrate.LatestRateResponse{ + contractToFundingRate(g.Name, r.Asset, fPair, contract, r.IncludePredictedRate), + }, nil } - var resp []fundingrate.LatestRateResponse - pairs, err := g.GetEnabledPairs(asset.Futures) + pairs, err := g.GetEnabledPairs(r.Asset) if err != nil { return nil, err } - for i := range settlementCurrencies { - contracts, err := g.GetAllFutureContracts(ctx, settlementCurrencies[i]) + contracts, err := g.GetAllFutureContracts(ctx, settle) + if err != nil { + return nil, err + } + resp := make([]fundingrate.LatestRateResponse, 0, len(contracts)) + for i := range contracts { + p := strings.ToUpper(contracts[i].Name) + if !g.IsValidPairString(p) { + continue + } + cp, err := currency.NewPairFromString(p) if err != nil { return nil, err } - for j := range contracts { - p := strings.ToUpper(contracts[j].Name) - if !g.IsValidPairString(p) { - continue - } - cp, err := currency.NewPairFromString(p) - if err != nil { - return nil, err - } - if !pairs.Contains(cp, false) { - continue - } - var isPerp bool - isPerp, err = g.IsPerpetualFutureCurrency(r.Asset, cp) - if err != nil { - return nil, err - } - if !isPerp { - continue - } - resp = append(resp, contractToFundingRate(g.Name, r.Asset, cp, &contracts[j], r.IncludePredictedRate)) + if !pairs.Contains(cp, false) { + continue } + resp = append(resp, contractToFundingRate(g.Name, r.Asset, cp, &contracts[i], r.IncludePredictedRate)) } - return resp, nil + return slices.Clip(resp), nil } func contractToFundingRate(name string, item asset.Item, fPair currency.Pair, contract *FuturesContract, includeUpcomingRate bool) fundingrate.LatestRateResponse { @@ -2341,142 +2124,123 @@ func contractToFundingRate(name string, item asset.Item, fPair currency.Pair, co // IsPerpetualFutureCurrency ensures a given asset and currency is a perpetual future func (g *Gateio) IsPerpetualFutureCurrency(a asset.Item, _ currency.Pair) (bool, error) { - return a == asset.Futures, nil + return a == asset.CoinMarginedFutures || a == asset.USDTMarginedFutures, nil } // GetOpenInterest returns the open interest rate for a given asset pair -func (g *Gateio) GetOpenInterest(ctx context.Context, k ...key.PairAsset) ([]futures.OpenInterest, error) { - for i := range k { - if k[i].Asset != asset.DeliveryFutures && k[i].Asset != asset.Futures { - // avoid API calls or returning errors after a successful retrieval - return nil, fmt.Errorf("%w %v %v", asset.ErrNotSupported, k[i].Asset, k[i].Pair()) +// If no pairs are provided, all enabled assets and pairs will be used +// If keys are provided, those asset pairs only need to be available, not enabled +func (g *Gateio) GetOpenInterest(ctx context.Context, keys ...key.PairAsset) ([]futures.OpenInterest, error) { + var errs error + resp := make([]futures.OpenInterest, 0, len(keys)) + assets := asset.Items{} + if len(keys) == 0 { + assets = asset.Items{asset.DeliveryFutures, asset.CoinMarginedFutures, asset.USDTMarginedFutures} + } else { + for _, k := range keys { + if !slices.Contains(assets, k.Asset) { + assets = append(assets, k.Asset) + } } } - if len(k) == 1 { - p, isEnabled, err := g.MatchSymbolCheckEnabled(k[0].Pair().String(), k[0].Asset, false) + for _, a := range assets { + var p currency.Pair + if len(keys) == 1 && a == keys[0].Asset { + if p, errs = g.MatchSymbolWithAvailablePairs(keys[0].Pair().String(), a, false); errs != nil { + return nil, errs + } + } + contracts, err := g.getOpenInterestContracts(ctx, a, p) if err != nil { - return nil, err + errs = common.AppendError(errs, fmt.Errorf("%w fetching %s", err, a)) + continue } - if !isEnabled { - return nil, fmt.Errorf("%w: %v", currency.ErrPairNotEnabled, k[0].Pair()) - } - switch k[0].Asset { - case asset.DeliveryFutures: - contractResp, err := g.GetDeliveryContract(ctx, currency.USDT, p) - if err != nil { - return nil, err - } - openInterest := contractResp.QuantoMultiplier.Float64() * float64(contractResp.PositionSize) * contractResp.IndexPrice.Float64() - return []futures.OpenInterest{ - { - Key: key.ExchangePairAsset{ - Exchange: g.Name, - Base: k[0].Base, - Quote: k[0].Quote, - Asset: k[0].Asset, - }, - OpenInterest: openInterest, - }, - }, nil - case asset.Futures: - for _, s := range settlementCurrencies { - contractResp, err := g.GetFuturesContract(ctx, s, p.String()) - if err != nil { - continue - } - openInterest := contractResp.QuantoMultiplier.Float64() * float64(contractResp.PositionSize) * contractResp.IndexPrice.Float64() - return []futures.OpenInterest{ - { - Key: key.ExchangePairAsset{ - Exchange: g.Name, - Base: k[0].Base, - Quote: k[0].Quote, - Asset: k[0].Asset, - }, - OpenInterest: openInterest, - }, - }, nil - } - } - } - var resp []futures.OpenInterest - for _, a := range g.GetAssetTypes(true) { - switch a { - case asset.DeliveryFutures: - contractResp, err := g.GetAllDeliveryContracts(ctx, currency.USDT) - if err != nil { - return nil, err - } - - for i := range contractResp { - p, isEnabled, err := g.MatchSymbolCheckEnabled(contractResp[i].Name, a, true) + for _, c := range contracts { + if p.IsEmpty() { // If not exactly one key provided + p, err = g.MatchSymbolWithAvailablePairs(c.contractName(), a, true) if err != nil && !errors.Is(err, currency.ErrPairNotFound) { - return nil, err - } - if !isEnabled { + errs = common.AppendError(errs, fmt.Errorf("%w from %s contract %s", err, a, c.contractName())) continue } - var appendData bool - for j := range k { - if k[j].Pair().Equal(p) { - appendData = true - break - } - } - if len(k) > 0 && !appendData { - continue - } - openInterest := contractResp[i].QuantoMultiplier.Float64() * float64(contractResp[i].PositionSize) * contractResp[i].IndexPrice.Float64() - resp = append(resp, futures.OpenInterest{ - Key: key.ExchangePairAsset{ - Exchange: g.Name, - Base: p.Base.Item, - Quote: p.Quote.Item, - Asset: a, - }, - OpenInterest: openInterest, - }) - } - case asset.Futures: - for _, s := range settlementCurrencies { - contractResp, err := g.GetAllFutureContracts(ctx, s) - if err != nil { - return nil, err - } - - for i := range contractResp { - p, isEnabled, err := g.MatchSymbolCheckEnabled(contractResp[i].Name, a, true) - if err != nil && !errors.Is(err, currency.ErrPairNotFound) { - return nil, err - } - if !isEnabled { + if len(keys) == 0 { // No keys: All enabled pairs + if enabled, err := g.IsPairEnabled(p, a); err != nil { + errs = common.AppendError(errs, fmt.Errorf("%w: %s %s", err, a, p)) + continue + } else if !enabled { continue } - var appendData bool - for j := range k { - if k[j].Pair().Equal(p) { - appendData = true - break - } - } - if len(k) > 0 && !appendData { + } else { // More than one key; Any available pair + if !slices.ContainsFunc(keys, func(k key.PairAsset) bool { return a == k.Asset && k.Pair().Equal(p) }) { continue } - openInterest := contractResp[i].QuantoMultiplier.Float64() * float64(contractResp[i].PositionSize) * contractResp[i].IndexPrice.Float64() - resp = append(resp, futures.OpenInterest{ - Key: key.ExchangePairAsset{ - Exchange: g.Name, - Base: p.Base.Item, - Quote: p.Quote.Item, - Asset: a, - }, - OpenInterest: openInterest, - }) } } + resp = append(resp, futures.OpenInterest{ + Key: key.ExchangePairAsset{ + Exchange: g.Name, + Base: p.Base.Item, + Quote: p.Quote.Item, + Asset: a, + }, + OpenInterest: c.openInterest(), + }) } } - return resp, nil + return slices.Clip(resp), errs +} + +type openInterestContract interface { + openInterest() float64 + contractName() string +} + +func (c *FuturesContract) openInterest() float64 { + i := float64(c.PositionSize) * c.IndexPrice.Float64() + if q := c.QuantoMultiplier.Float64(); q != 0 { + i *= q + } + return i +} + +func (c *FuturesContract) contractName() string { + return c.Name +} + +func (c *DeliveryContract) openInterest() float64 { + return c.QuantoMultiplier.Float64() * float64(c.PositionSize) * c.IndexPrice.Float64() +} + +func (c *DeliveryContract) contractName() string { + return c.Name +} + +func (g *Gateio) getOpenInterestContracts(ctx context.Context, a asset.Item, p currency.Pair) ([]openInterestContract, error) { + settle, err := getSettlementCurrency(p, a) + if err != nil { + return nil, err + } + if a == asset.DeliveryFutures { + if p != currency.EMPTYPAIR { + d, err := g.GetDeliveryContract(ctx, settle, p) + return []openInterestContract{d}, err + } + d, err := g.GetAllDeliveryContracts(ctx, settle) + contracts := make([]openInterestContract, len(d)) + for i := range d { + contracts[i] = &d[i] + } + return contracts, err + } + if p != currency.EMPTYPAIR { + contract, err := g.GetFuturesContract(ctx, settle, p.String()) + return []openInterestContract{contract}, err + } + fc, err := g.GetAllFutureContracts(ctx, settle) + contracts := make([]openInterestContract, len(fc)) + for i := range fc { + contracts[i] = &fc[i] + } + return contracts, err } // getClientOrderIDFromText returns the client order ID from the text response @@ -2551,11 +2315,16 @@ func (g *Gateio) GetCurrencyTradeURL(_ context.Context, a asset.Item, cp currenc cp.Delimiter = currency.UnderscoreDelimiter switch a { case asset.Spot, asset.CrossMargin, asset.Margin: - return tradeBaseURL + tradeSpot + cp.Upper().String(), nil - case asset.Futures: - return tradeBaseURL + tradeFutures + cp.Upper().String(), nil - case asset.DeliveryFutures: - return tradeBaseURL + tradeDelivery + cp.Upper().String(), nil + return tradeBaseURL + "trade/" + cp.Upper().String(), nil + case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.DeliveryFutures: + settle, err := getSettlementCurrency(cp, a) + if err != nil { + return "", err + } + if a == asset.DeliveryFutures { + return tradeBaseURL + "futures-delivery/" + settle.String() + "/" + cp.Upper().String(), nil + } + return tradeBaseURL + futuresPath + settle.String() + "/" + cp.Upper().String(), nil default: return "", fmt.Errorf("%w %v", asset.ErrNotSupported, a) } @@ -2582,12 +2351,12 @@ func (g *Gateio) WebsocketSubmitOrder(ctx context.Context, s *order.Submit) (*or return nil, err } - got, err := g.WebsocketSpotSubmitOrder(ctx, req) + resp, err := g.WebsocketSpotSubmitOrder(ctx, req) if err != nil { return nil, err } - return g.deriveSpotWebsocketOrderResponse(got) - case asset.Futures: + return g.deriveSpotWebsocketOrderResponse(resp) + case asset.CoinMarginedFutures, asset.USDTMarginedFutures: amountWithDirection, err := getFutureOrderSize(s) if err != nil { return nil, err @@ -2598,12 +2367,7 @@ func (g *Gateio) WebsocketSubmitOrder(ctx context.Context, s *order.Submit) (*or return nil, err } - a, err := getAssetFromFuturesPair(s.Pair) - if err != nil { - return nil, err - } - - got, err := g.WebsocketFuturesSubmitOrder(ctx, a, &ContractOrderCreateParams{ + resp, err := g.WebsocketFuturesSubmitOrder(ctx, s.AssetType, &ContractOrderCreateParams{ Contract: s.Pair, Size: amountWithDirection, Price: strconv.FormatFloat(s.Price, 'f', -1, 64), @@ -2614,18 +2378,18 @@ func (g *Gateio) WebsocketSubmitOrder(ctx context.Context, s *order.Submit) (*or if err != nil { return nil, err } - return g.deriveFuturesWebsocketOrderResponse(got) + return g.deriveFuturesWebsocketOrderResponse(resp) default: return nil, common.ErrNotYetImplemented } } func (g *Gateio) deriveSpotWebsocketOrderResponse(responses *WebsocketOrderResponse) (*order.SubmitResponse, error) { - got, err := g.deriveSpotWebsocketOrderResponses([]*WebsocketOrderResponse{responses}) + resp, err := g.deriveSpotWebsocketOrderResponses([]*WebsocketOrderResponse{responses}) if err != nil { return nil, err } - return got[0], nil + return resp[0], nil } // deriveSpotWebsocketOrderResponses returns the order submission responses for spot @@ -2692,11 +2456,11 @@ func (g *Gateio) deriveSpotWebsocketOrderResponses(responses []*WebsocketOrderRe } func (g *Gateio) deriveFuturesWebsocketOrderResponse(responses *WebsocketFuturesOrderResponse) (*order.SubmitResponse, error) { - got, err := g.deriveFuturesWebsocketOrderResponses([]*WebsocketFuturesOrderResponse{responses}) + resp, err := g.deriveFuturesWebsocketOrderResponses([]*WebsocketFuturesOrderResponse{responses}) if err != nil { return nil, err } - return got[0], nil + return resp[0], nil } // deriveFuturesWebsocketOrderResponses returns the order submission responses for futures @@ -2781,3 +2545,26 @@ func (g *Gateio) getSpotOrderRequest(s *order.Submit) (*CreateOrderRequest, erro TimeInForce: timeInForce, }, nil } + +func getSettlementCurrency(p currency.Pair, a asset.Item) (currency.Code, error) { + switch a { + case asset.DeliveryFutures: + return currency.USDT, nil + case asset.USDTMarginedFutures: + if p.IsEmpty() || p.Quote.Equal(currency.USDT) { + return currency.USDT, nil + } + return currency.EMPTYCODE, fmt.Errorf("%w %s %s", errInvalidSettlementQuote, a, p) + case asset.CoinMarginedFutures: + if !p.IsEmpty() { + if !p.Base.Equal(currency.BTC) { // Only BTC endpoint currently available + return currency.EMPTYCODE, fmt.Errorf("%w %s %s", errInvalidSettlementBase, a, p) + } + if !p.Quote.Equal(currency.USD) { // We expect all Coin-M to be quoted in USD + return currency.EMPTYCODE, fmt.Errorf("%w %s %s", errInvalidSettlementQuote, a, p) + } + } + return currency.BTC, nil + } + return currency.EMPTYCODE, fmt.Errorf("%w: %s", asset.ErrNotSupported, a) +} diff --git a/exchanges/gateio/testdata/wsFutures.json b/exchanges/gateio/testdata/wsFutures.json new file mode 100644 index 00000000..c4cfb803 --- /dev/null +++ b/exchanges/gateio/testdata/wsFutures.json @@ -0,0 +1,15 @@ +{"time":1541659086,"channel":"futures.tickers","event":"update","error":null,"result":[{"contract":"BTC_USD","last":"118.4","change_percentage":"0.77","funding_rate":"-0.000114","funding_rate_indicative":"0.01875","mark_price":"118.35","index_price":"118.36","total_size":"73648","volume_24h":"745487577","volume_24h_btc":"117","volume_24h_usd":"419950","quanto_base_rate":"","volume_24h_quote":"1665006","volume_24h_settle":"178","volume_24h_base":"5526","low_24h":"99.2","high_24h":"132.5"}]} +{"channel":"futures.trades","event":"update","time":1541503698,"result":[{"size":-108,"id":27753479,"create_time":1545136464,"create_time_ms":1545136464123,"price":"96.4","contract":"BTC_USD"}]} +{"time":1615366379,"channel":"futures.book_ticker","event":"update","error":null,"result":{"t":1615366379123,"u":2517661076,"s":"BTC_USD","b":"54696.6","B":37000,"a":"54696.7","A":47061}} +{"channel":"futures.orders","event":"update","time":1541505434,"result":[{"contract":"BTC_USD","create_time":1628736847,"create_time_ms":1628736847325,"fill_price":40000.4,"finish_as":"filled","finish_time":1628736848,"finish_time_ms":1628736848321,"iceberg":0,"id":4872460,"is_close":false,"is_liq":false,"is_reduce_only":false,"left":0,"mkfr":-0.00025,"price":40000.4,"refr":0,"refu":0,"size":1,"status":"finished","text":"-","tif":"gtc","tkfr":0.0005,"user":"110xxxxx"}]} +{"time":1543205083,"channel":"futures.usertrades","event":"update","error":null,"result":[{"id":"3335259","create_time":1628736848,"create_time_ms":1628736848321,"contract":"BTC_USD","order_id":"4872460","size":1,"price":"40000.4","role":"maker","text":"api","fee":0.0009290592,"point_fee":0}]} +{"channel":"futures.liquidates","event":"update","time":1541505434,"result":[{"entry_price":209,"fill_price":215.1,"left":0,"leverage":0,"liq_price":213,"margin":0.007816722941,"mark_price":213,"order_id":4093362,"order_price":215.1,"size":-124,"time":1541486601,"time_ms":1541486601123,"contract":"BTC_USD","user":"1040xxxx"}]} +{"channel": "futures.auto_deleverages", "event": "update", "time": 1541505434, "result": [{"entry_price": 209,"fill_price": 215.1,"position_size": 10,"trade_size": 10,"time": 1541486601,"time_ms": 1541486601123,"contract": "BTC_USD","user": "1040"} ]} +{"channel":"futures.position_closes","event":"update","time":1541505434,"result":[{"contract":"BTC_USD","pnl":-0.000624354791,"side":"long","text":"web","time":1547198562,"time_ms":1547198562123,"user":"211xxxx"}]} +{"channel":"futures.balances","event":"update","time":1541505434,"result":[{"balance":9.998739899488,"change":-2.074115e-06,"text":"BTC_USD:3914424","time":1547199246,"time_ms":1547199246123,"type":"fee","user":"211xxx"}]} +{"time":1551858330,"channel":"futures.reduce_risk_limits","event":"update","error":null,"result":[{"cancel_orders":0,"contract":"BTC_USD","leverage_max":10,"liq_price":136.53,"maintenance_rate":0.09,"risk_limit":450,"time":1551858330,"time_ms":1551858330123,"user":"20011"}]} +{"time": 1588212926,"channel": "futures.positions", "event": "update", "error": null, "result": [ { "contract": "BTC_USD", "cross_leverage_limit": 0, "entry_price": 40000.36666661111, "history_pnl": -0.000108569505, "history_point": 0, "last_close_pnl": -0.000050123368,"leverage": 0,"leverage_max": 100,"liq_price": 0.1,"maintenance_rate": 0.005,"margin": 49.999890611186,"mode": "single","realised_pnl": -1.25e-8,"realised_point": 0,"risk_limit": 100,"size": 3,"time": 1628736848,"time_ms": 1628736848321,"user": "110xxxxx"}]} +{"time":1596798126,"channel":"futures.autoorders","event":"update","error":null,"result":[{"user":123456,"trigger":{"strategy_type":0,"price_type":0,"price":"10000","rule":2,"expiration":86400},"initial":{"contract":"BTC_USDT","size":10,"price":"10000","tif":"gtc","text":"web","iceberg":0,"is_close":false,"is_reduce_only":false},"id":9256,"trade_id":0,"status":"open","reason":"","create_time":1596798126,"name":"price_autoorders","is_stop_order":false,"stop_trigger":{"rule":0,"trigger_price":"","order_price":""}}]} +{"time":1678468497,"time_ms":1678468497232,"channel":"futures.order_book","event":"all","result":{"t":1678468497168,"id":4010394406,"contract":"BTC_USD","asks":[{"p":"19909","s":3100},{"p":"19909.1","s":5000},{"p":"19910","s":3100},{"p":"19914.4","s":4400},{"p":"19916.6","s":5000},{"p":"19917.2","s":8255},{"p":"19919.2","s":5000},{"p":"19920.3","s":11967},{"p":"19922.2","s":5000},{"p":"19924.2","s":5000},{"p":"19927.1","s":17129},{"p":"19927.2","s":5000},{"p":"19929","s":20864},{"p":"19929.3","s":5000},{"p":"19929.7","s":24683},{"p":"19930.3","s":750},{"p":"19931.4","s":5000},{"p":"19931.5","s":1},{"p":"19934.2","s":5000},{"p":"19935.4","s":1}],"bids":[{"p":"19901.2","s":5000},{"p":"19900.3","s":3100},{"p":"19900.2","s":5000},{"p":"19899.3","s":2983},{"p":"19899.2","s":6035},{"p":"19897.2","s":5000},{"p":"19895.7","s":5984},{"p":"19895","s":5000},{"p":"19892.9","s":195},{"p":"19892.8","s":5000},{"p":"19889.4","s":5000},{"p":"19889","s":8800},{"p":"19888.5","s":11968},{"p":"19887.1","s":5000},{"p":"19886.4","s":24683},{"p":"19885.7","s":1},{"p":"19883.8","s":5000},{"p":"19880.2","s":5000},{"p":"19878.2","s":5000},{"p":"19876.8","s":1}]}} +{"time":1678469222,"time_ms":1678469222982,"channel":"futures.order_book_update","event":"update","result":{"t":1678469222617,"s":"BTC_USD","U":4010424331,"u":4010424361,"b":[{"p":"19860.7","s":5984},{"p":"19858.6","s":5000},{"p":"19845.4","s":20864},{"p":"19859.1","s":0},{"p":"19862.5","s":0},{"p":"19358","s":0},{"p":"19864.5","s":5000},{"p":"19840.7","s":0},{"p":"19863.6","s":3100},{"p":"19839.3","s":0},{"p":"19851.5","s":8800},{"p":"19720","s":0},{"p":"19333","s":0},{"p":"19852.7","s":5000},{"p":"19861.5","s":0},{"p":"19860.6","s":3100},{"p":"19833.6","s":0},{"p":"19360","s":0},{"p":"19863.5","s":5000},{"p":"19736.9","s":0},{"p":"19838.5","s":0},{"p":"19841.3","s":0},{"p":"19858.1","s":3100},{"p":"19710.9","s":0},{"p":"19342","s":0},{"p":"19852.1","s":11967},{"p":"19343","s":0},{"p":"19705","s":0},{"p":"19836.5","s":0},{"p":"19862.6","s":3100},{"p":"19729.6","s":0},{"p":"19849.9","s":5000}],"a":[{"p":"19900.5","s":0},{"p":"19883.1","s":11967},{"p":"19910.9","s":0},{"p":"19897.7","s":5000},{"p":"19875.9","s":5984},{"p":"19899.6","s":0},{"p":"19878","s":4400},{"p":"19877.6","s":0},{"p":"19889.5","s":5000},{"p":"19875.5","s":3100},{"p":"19875.3","s":0},{"p":"19878.5","s":0},{"p":"19895.2","s":0},{"p":"20284.6","s":0},{"p":"19880.7","s":5000},{"p":"19875.4","s":0},{"p":"19985.8","s":0},{"p":"19887.1","s":5000},{"p":"19896","s":1},{"p":"19869.3","s":0},{"p":"19900","s":0},{"p":"19875.6","s":5000},{"p":"19980.6","s":0},{"p":"19885.1","s":5000},{"p":"19877.7","s":5000},{"p":"20000","s":0},{"p":"19892.2","s":8255},{"p":"19886.8","s":0},{"p":"20257.4","s":0},{"p":"20280","s":0},{"p":"20002.5","s":0},{"p":"20263.1","s":0},{"p":"19900.2","s":0}]}} +{"time":1678469467,"time_ms":1678469467981,"channel":"futures.candlesticks","event":"update","result":[{"t":1678469460,"v":0,"c":"19896","h":"19896","l":"19896","o":"19896","n":"1m_BTC_USD"}]}