exchanges: Fix GateIO/Coinbase test failures and OKX race (#1753)

* exchanges: Fix gateio/coinbase test failures

* OKX: Fix TestGetAssetsFromInstrumentTypeOrID race

* GateIO: Add/improve comments

* GateIO: Rid additional API call for FetchTradablePairs and provide additional context for test

* GateIO: Prompt test reviewers to take action if BTC settlement is supported again
This commit is contained in:
Adrian Gallagher
2024-12-20 12:07:46 +11:00
committed by GitHub
parent 16e5398dd5
commit 143e336c03
5 changed files with 115 additions and 178 deletions

View File

@@ -3,7 +3,6 @@ package coinbasepro
import (
"context"
"errors"
"log"
"net/http"
"os"
"testing"
@@ -14,7 +13,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/core"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
@@ -42,33 +40,11 @@ const (
canManipulateRealOrders = false
)
func TestMain(m *testing.M) {
c.SetDefaults()
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json", true)
if err != nil {
log.Fatal("coinbasepro load config error", err)
}
gdxConfig, err := cfg.GetExchangeConfig("CoinbasePro")
if err != nil {
log.Fatal("coinbasepro Setup() init error")
}
gdxConfig.API.Credentials.Key = apiKey
gdxConfig.API.Credentials.Secret = apiSecret
gdxConfig.API.Credentials.ClientID = clientID
gdxConfig.API.AuthenticatedSupport = true
gdxConfig.API.AuthenticatedWebsocketSupport = true
c.Websocket = sharedtestvalues.NewTestWebsocket()
err = c.Setup(gdxConfig)
if err != nil {
log.Fatal("CoinbasePro setup error", err)
}
os.Exit(m.Run())
func TestMain(_ *testing.M) {
os.Exit(0) // Disable full test suite until PR #1381 is merged as more API endpoints have been deprecated over time
}
func TestGetProducts(t *testing.T) {
t.Skip("API is deprecated")
_, err := c.GetProducts(context.Background())
if err != nil {
t.Errorf("Coinbase, GetProducts() Error: %s", err)
@@ -76,8 +52,6 @@ func TestGetProducts(t *testing.T) {
}
func TestGetOrderbook(t *testing.T) {
t.Skip("API is deprecated")
_, err := c.GetOrderbook(context.Background(), testPair.String(), 2)
if err != nil {
t.Error(err)
@@ -89,8 +63,6 @@ func TestGetOrderbook(t *testing.T) {
}
func TestGetTicker(t *testing.T) {
t.Skip("API is deprecated")
_, err := c.GetTicker(context.Background(), testPair.String())
if err != nil {
t.Error("GetTicker() error", err)
@@ -105,8 +77,6 @@ func TestGetTrades(t *testing.T) {
}
func TestGetHistoricRatesGranularityCheck(t *testing.T) {
t.Skip("API is deprecated")
end := time.Now()
start := end.Add(-time.Hour * 2)
_, err := c.GetHistoricCandles(context.Background(),
@@ -117,8 +87,6 @@ func TestGetHistoricRatesGranularityCheck(t *testing.T) {
}
func TestCoinbasePro_GetHistoricCandlesExtended(t *testing.T) {
t.Skip("API is deprecated")
start := time.Unix(1546300800, 0)
end := time.Unix(1577836799, 0)

View File

@@ -10,7 +10,6 @@ import (
"fmt"
"net/http"
"net/url"
"slices"
"strconv"
"strings"
"time"
@@ -2792,7 +2791,7 @@ func (g *Gateio) GetSingleDeliveryPosition(ctx context.Context, settle currency.
// UpdateDeliveryPositionMargin updates position margin
func (g *Gateio) UpdateDeliveryPositionMargin(ctx context.Context, settle currency.Code, change float64, contract currency.Pair) (*Position, error) {
if !slices.Contains(settlementCurrencies, settle) {
if settle.IsEmpty() {
return nil, errEmptyOrInvalidSettlementCurrency
}
if contract.IsInvalid() {
@@ -2809,7 +2808,7 @@ func (g *Gateio) UpdateDeliveryPositionMargin(ctx context.Context, settle curren
// UpdateDeliveryPositionLeverage updates position leverage
func (g *Gateio) UpdateDeliveryPositionLeverage(ctx context.Context, settle currency.Code, contract currency.Pair, leverage float64) (*Position, error) {
if !slices.Contains(settlementCurrencies, settle) {
if settle.IsEmpty() {
return nil, errEmptyOrInvalidSettlementCurrency
}
if contract.IsInvalid() {
@@ -2827,7 +2826,7 @@ func (g *Gateio) UpdateDeliveryPositionLeverage(ctx context.Context, settle curr
// UpdateDeliveryPositionRiskLimit update position risk limit
func (g *Gateio) UpdateDeliveryPositionRiskLimit(ctx context.Context, settle currency.Code, contract currency.Pair, riskLimit uint64) (*Position, error) {
if !slices.Contains(settlementCurrencies, settle) {
if settle.IsEmpty() {
return nil, errEmptyOrInvalidSettlementCurrency
}
if contract.IsInvalid() {
@@ -2920,7 +2919,7 @@ func (g *Gateio) CancelMultipleDeliveryOrders(ctx context.Context, contract curr
// GetSingleDeliveryOrder Get a single order
// Zero-filled order cannot be retrieved 10 minutes after order cancellation
func (g *Gateio) GetSingleDeliveryOrder(ctx context.Context, settle currency.Code, orderID string) (*Order, error) {
if !slices.Contains(settlementCurrencies, settle) {
if settle.IsEmpty() {
return nil, errEmptyOrInvalidSettlementCurrency
}
if orderID == "" {

View File

@@ -1142,22 +1142,18 @@ func TestCancelMultipleDeliveryOrders(t *testing.T) {
func TestGetSingleDeliveryOrder(t *testing.T) {
t.Parallel()
_, err := g.GetSingleDeliveryOrder(context.Background(), currency.USD, "123456")
_, err := g.GetSingleDeliveryOrder(context.Background(), currency.EMPTYCODE, "123456")
assert.ErrorIs(t, err, errEmptyOrInvalidSettlementCurrency, "GetSingleDeliveryOrder should return errEmptyOrInvalidSettlementCurrency")
sharedtestvalues.SkipTestIfCredentialsUnset(t, g)
for _, settle := range settlementCurrencies {
_, err := g.GetSingleDeliveryOrder(context.Background(), settle, "123456")
assert.NoErrorf(t, err, "GetSingleDeliveryOrder %s should not error", settle)
}
_, err = g.GetSingleDeliveryOrder(context.Background(), currency.USDT, "123456")
assert.NoError(t, err, "GetSingleDeliveryOrder should not error")
}
func TestCancelSingleDeliveryOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders)
for _, settle := range settlementCurrencies {
_, err := g.CancelSingleDeliveryOrder(context.Background(), settle, "123456")
assert.NoErrorf(t, err, "CancelSingleDeliveryOrder %s should not error", settle)
}
_, err := g.CancelSingleDeliveryOrder(context.Background(), currency.USDT, "123456")
assert.NoError(t, err, "CancelSingleDeliveryOrder should not error")
}
func TestGetDeliveryPersonalTradingHistory(t *testing.T) {
@@ -1225,7 +1221,7 @@ func TestCancelAllDeliveryPriceTriggeredOrder(t *testing.T) {
func TestGetSingleDeliveryPriceTriggeredOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, g)
_, err := g.GetSingleDeliveryPriceTriggeredOrder(context.Background(), currency.BTC, "12345")
_, err := g.GetSingleDeliveryPriceTriggeredOrder(context.Background(), currency.USDT, "12345")
assert.NoError(t, err, "GetSingleDeliveryPriceTriggeredOrder should not error")
}
@@ -1450,8 +1446,15 @@ func TestCancelAllFuturesOpenOrders(t *testing.T) {
func TestGetAllDeliveryContracts(t *testing.T) {
t.Parallel()
_, err := g.GetAllDeliveryContracts(context.Background(), currency.USDT)
assert.NoError(t, err, "GetAllDeliveryContracts should not error")
r, err := g.GetAllDeliveryContracts(context.Background(), currency.USDT)
require.NoError(t, err, "GetAllDeliveryContracts must not error")
assert.NotEmpty(t, r, "GetAllDeliveryContracts should return data")
r, err = g.GetAllDeliveryContracts(context.Background(), currency.BTC)
require.NoError(t, err, "GetAllDeliveryContracts must not error")
// The test below will fail if support for BTC settlement is added. This is intentional, as it ensures we are alerted when it's time to reintroduce support
if !assert.Empty(t, r, "GetAllDeliveryContracts should not return any data with unsupported settlement currency BTC") {
t.Error("BTC settlement for delivery futures appears to be supported again by the API. Please raise an issue to reintroduce BTC support for this exchange")
}
}
func TestGetSingleDeliveryContracts(t *testing.T) {
@@ -1527,6 +1530,8 @@ func TestGetSingleDeliveryPosition(t *testing.T) {
func TestUpdateDeliveryPositionMargin(t *testing.T) {
t.Parallel()
_, err := g.UpdateDeliveryPositionMargin(context.Background(), 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")
@@ -1536,15 +1541,19 @@ func TestUpdateDeliveryPositionMargin(t *testing.T) {
func TestUpdateDeliveryPositionLeverage(t *testing.T) {
t.Parallel()
_, err := g.UpdateDeliveryPositionLeverage(context.Background(), currency.EMPTYCODE, currency.Pair{}, 0.001)
assert.ErrorIs(t, err, errEmptyOrInvalidSettlementCurrency)
sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders)
_, err := g.UpdateDeliveryPositionLeverage(context.Background(), currency.USDT, getPair(t, asset.DeliveryFutures), 0.001)
_, err = g.UpdateDeliveryPositionLeverage(context.Background(), currency.USDT, getPair(t, asset.DeliveryFutures), 0.001)
assert.NoError(t, err, "UpdateDeliveryPositionLeverage should not error")
}
func TestUpdateDeliveryPositionRiskLimit(t *testing.T) {
t.Parallel()
_, err := g.UpdateDeliveryPositionRiskLimit(context.Background(), currency.EMPTYCODE, currency.Pair{}, 0)
assert.ErrorIs(t, err, errEmptyOrInvalidSettlementCurrency)
sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders)
_, err := g.UpdateDeliveryPositionRiskLimit(context.Background(), currency.USDT, getPair(t, asset.DeliveryFutures), 30)
_, err = g.UpdateDeliveryPositionRiskLimit(context.Background(), currency.USDT, getPair(t, asset.DeliveryFutures), 30)
assert.NoError(t, err, "UpdateDeliveryPositionRiskLimit should not error")
}

View File

@@ -520,21 +520,16 @@ func (g *Gateio) FetchTradablePairs(ctx context.Context, a asset.Item) (currency
}
return pairs, nil
case asset.DeliveryFutures:
btcContracts, err := g.GetAllDeliveryContracts(ctx, currency.BTC)
if err != nil {
return nil, err
}
usdtContracts, err := g.GetAllDeliveryContracts(ctx, currency.USDT)
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(usdtContracts))
for x := range usdtContracts {
if usdtContracts[x].InDelisting {
continue
}
p := strings.ToUpper(btcContracts[x].Name)
p := strings.ToUpper(usdtContracts[x].Name)
if !g.IsValidPairString(p) {
continue
}
@@ -633,6 +628,11 @@ func (g *Gateio) UpdateTickers(ctx context.Context, a asset.Item) error {
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) {
continue
}
if a == asset.Futures {
ticks, err = g.GetFuturesTickers(ctx, settle, currency.EMPTYPAIR)
} else {
@@ -828,6 +828,11 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, a asset.Item) (account.H
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])
@@ -1721,6 +1726,11 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque
}
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) {
continue
}
var futuresOrders []Order
if req.AssetType == asset.Futures {
futuresOrders, err = g.GetFuturesOrders(ctx, currency.EMPTYPAIR, "open", "", settlement, 0, 0, 0)
@@ -2112,58 +2122,56 @@ func (g *Gateio) GetFuturesContractDetails(ctx context.Context, item asset.Item)
return resp, nil
case asset.DeliveryFutures:
var resp []futures.Contract
for k := range settlementCurrencies {
contracts, err := g.GetAllDeliveryContracts(ctx, settlementCurrencies[k])
contracts, err := g.GetAllDeliveryContracts(ctx, currency.USDT)
if err != nil {
return nil, err
}
contractsToAdd := make([]futures.Contract, len(contracts))
for j := range contracts {
var name, underlying currency.Pair
name, err = currency.NewPairFromString(contracts[j].Name)
if err != nil {
return nil, err
}
contractsToAdd := make([]futures.Contract, len(contracts))
for j := range contracts {
var name, underlying currency.Pair
name, err = currency.NewPairFromString(contracts[j].Name)
if err != nil {
return nil, err
}
underlying, err = currency.NewPairFromString(contracts[j].Underlying)
if err != nil {
return nil, err
}
var ct futures.ContractType
// no start information, inferring it based on contract type
// gateio also reuses contracts for kline data, cannot use a lookup to see the first trade
var s, e time.Time
e = contracts[j].ExpireTime.Time()
switch contracts[j].Cycle {
case "WEEKLY":
ct = futures.Weekly
s = e.Add(-kline.OneWeek.Duration())
case "BI-WEEKLY":
ct = futures.Fortnightly
s = e.Add(-kline.TwoWeek.Duration())
case "QUARTERLY":
ct = futures.Quarterly
s = e.Add(-kline.ThreeMonth.Duration())
case "BI-QUARTERLY":
ct = futures.HalfYearly
s = e.Add(-kline.SixMonth.Duration())
default:
ct = futures.LongDated
}
contractsToAdd[j] = futures.Contract{
Exchange: g.Name,
Name: name,
Underlying: underlying,
Asset: item,
StartDate: s,
EndDate: e,
SettlementType: futures.Linear,
IsActive: !contracts[j].InDelisting,
Type: ct,
SettlementCurrencies: currency.Currencies{settlementCurrencies[k]},
MarginCurrency: currency.Code{},
Multiplier: contracts[j].QuantoMultiplier.Float64(),
MaxLeverage: contracts[j].LeverageMax.Float64(),
}
underlying, err = currency.NewPairFromString(contracts[j].Underlying)
if err != nil {
return nil, err
}
var ct futures.ContractType
// no start information, inferring it based on contract type
// gateio also reuses contracts for kline data, cannot use a lookup to see the first trade
var s, e time.Time
e = contracts[j].ExpireTime.Time()
switch contracts[j].Cycle {
case "WEEKLY":
ct = futures.Weekly
s = e.Add(-kline.OneWeek.Duration())
case "BI-WEEKLY":
ct = futures.Fortnightly
s = e.Add(-kline.TwoWeek.Duration())
case "QUARTERLY":
ct = futures.Quarterly
s = e.Add(-kline.ThreeMonth.Duration())
case "BI-QUARTERLY":
ct = futures.HalfYearly
s = e.Add(-kline.SixMonth.Duration())
default:
ct = futures.LongDated
}
contractsToAdd[j] = futures.Contract{
Exchange: g.Name,
Name: name,
Underlying: underlying,
Asset: item,
StartDate: s,
EndDate: e,
SettlementType: futures.Linear,
IsActive: !contracts[j].InDelisting,
Type: ct,
SettlementCurrencies: currency.Currencies{currency.USDT},
MarginCurrency: currency.Code{},
Multiplier: contracts[j].QuantoMultiplier.Float64(),
MaxLeverage: contracts[j].LeverageMax.Float64(),
}
resp = append(resp, contractsToAdd...)
}

View File

@@ -3380,80 +3380,33 @@ func TestIsPerpetualFutureCurrency(t *testing.T) {
func TestGetAssetsFromInstrumentTypeOrID(t *testing.T) {
t.Parallel()
ok := new(Okx) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(ok), "Setup must not error")
_, err := ok.GetAssetsFromInstrumentTypeOrID("", "")
if !errors.Is(err, errEmptyArgument) {
t.Error(err)
}
assert.ErrorIs(t, err, errEmptyArgument)
assets, err := ok.GetAssetsFromInstrumentTypeOrID("SPOT", "")
if !errors.Is(err, nil) {
t.Error(err)
}
if len(assets) != 1 {
t.Errorf("received %v expected %v", len(assets), 1)
}
if assets[0] != asset.Spot {
t.Errorf("received %v expected %v", assets[0], asset.Spot)
}
assets, err = ok.GetAssetsFromInstrumentTypeOrID("", ok.CurrencyPairs.Pairs[asset.Futures].Enabled[0].String())
if !errors.Is(err, nil) {
t.Error(err)
}
if len(assets) != 1 {
t.Errorf("received %v expected %v", len(assets), 1)
}
if assets[0] != asset.Futures {
t.Errorf("received %v expected %v", assets[0], asset.Futures)
}
assets, err = ok.GetAssetsFromInstrumentTypeOrID("", ok.CurrencyPairs.Pairs[asset.PerpetualSwap].Enabled[0].String())
if !errors.Is(err, nil) {
t.Error(err)
}
if len(assets) != 1 {
t.Errorf("received %v expected %v", len(assets), 1)
}
if assets[0] != asset.PerpetualSwap {
t.Errorf("received %v expected %v", assets[0], asset.PerpetualSwap)
for _, a := range []asset.Item{asset.Spot, asset.Futures, asset.PerpetualSwap, asset.Options} {
symbol := ""
if a != asset.Spot {
symbol = ok.CurrencyPairs.Pairs[a].Enabled[0].String()
}
assets, err2 := ok.GetAssetsFromInstrumentTypeOrID(a.String(), symbol)
require.NoErrorf(t, err2, "GetAssetsFromInstrumentTypeOrID must not error for asset: %s", a)
require.Len(t, assets, 1)
assert.Equalf(t, a, assets[0], "Should contain asset: %s", a)
}
_, err = ok.GetAssetsFromInstrumentTypeOrID("", "test")
if !errors.Is(err, currency.ErrCurrencyNotSupported) {
t.Error(err)
}
assert.ErrorIs(t, err, currency.ErrCurrencyNotSupported)
_, err = ok.GetAssetsFromInstrumentTypeOrID("", "test-test")
if !errors.Is(err, asset.ErrNotSupported) {
t.Error(err)
}
assert.ErrorIs(t, err, asset.ErrNotSupported)
assets, err = ok.GetAssetsFromInstrumentTypeOrID("", ok.CurrencyPairs.Pairs[asset.Margin].Enabled[0].String())
if !errors.Is(err, nil) {
t.Error(err)
}
var found bool
for i := range assets {
if assets[i] == asset.Margin {
found = true
}
}
if !found {
t.Errorf("received %v expected %v", assets, asset.Margin)
}
assets, err = ok.GetAssetsFromInstrumentTypeOrID("", ok.CurrencyPairs.Pairs[asset.Spot].Enabled[0].String())
if !errors.Is(err, nil) {
t.Error(err)
}
found = false
for i := range assets {
if assets[i] == asset.Spot {
found = true
}
}
if !found {
t.Errorf("received %v expected %v", assets, asset.Spot)
for _, a := range []asset.Item{asset.Margin, asset.Spot} {
assets, err2 := ok.GetAssetsFromInstrumentTypeOrID("", ok.CurrencyPairs.Pairs[a].Enabled[0].String())
require.NoErrorf(t, err2, "GetAssetsFromInstrumentTypeOrID must not error for asset: %s", a)
assert.Contains(t, assets, a)
}
}