GateIO: Enhance order execution limits and currency pair details (#2018)

* refactor(gateio): enhance order execution limits and currency pair details

* Update exchanges/gateio/gateio_wrapper.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* REEEEEHHHHHH

* linter: fix

* fix GetOpenInterest when a contract is delisted

* add handling for delisting end time correctly

* Update exchange/order/limits/limits_types.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchange/order/limits/limits_types.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/gateio/gateio_types.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/gateio/gateio_types.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* gk: nits

* gci: fix

* linter: fix

* gateio: Add launch and update tests (cherry-pick)

* gk: nits + removed spot setting delisting as delisted because it is not a start time value

* glorious: apply diff

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>
This commit is contained in:
Ryan O'Hara-Reid
2025-10-02 14:55:43 +10:00
committed by GitHub
parent ac91fabcd5
commit e11765bc36
4 changed files with 157 additions and 149 deletions

View File

@@ -422,12 +422,7 @@ func (e *Exchange) FetchTradablePairs(ctx context.Context, a asset.Item) (curren
if tradables[x].TradeStatus == "untradable" {
continue
}
p := strings.ToUpper(tradables[x].ID)
cp, err := currency.NewPairFromString(p)
if err != nil {
return nil, err
}
pairs = append(pairs, cp)
pairs = append(pairs, currency.NewPair(tradables[x].Base, tradables[x].Quote))
}
return pairs, nil
case asset.Margin, asset.CrossMargin:
@@ -459,15 +454,10 @@ func (e *Exchange) FetchTradablePairs(ctx context.Context, a asset.Item) (curren
}
pairs := make([]currency.Pair, 0, len(contracts))
for i := range contracts {
if contracts[i].InDelisting {
if !contracts[i].DelistedTime.Time().IsZero() && contracts[i].DelistedTime.Time().Before(time.Now()) {
continue
}
p := strings.ToUpper(contracts[i].Name)
cp, err := currency.NewPairFromString(p)
if err != nil {
return nil, err
}
pairs = append(pairs, cp)
pairs = append(pairs, contracts[i].Name)
}
return slices.Clip(pairs), nil
case asset.DeliveryFutures:
@@ -1800,23 +1790,19 @@ func (e *Exchange) GetFuturesContractDetails(ctx context.Context, a asset.Item)
}
resp := make([]futures.Contract, len(contracts))
for i := range contracts {
name, err := currency.NewPairFromString(contracts[i].Name)
if err != nil {
return nil, err
}
contractSettlementType := futures.Linear
switch {
case name.Base.Equal(currency.BTC) && settle.Equal(currency.BTC):
case contracts[i].Name.Base.Equal(currency.BTC) && settle.Equal(currency.BTC):
contractSettlementType = futures.Inverse
case !name.Base.Equal(settle) && !settle.Equal(currency.USDT):
case !contracts[i].Name.Base.Equal(settle) && !settle.Equal(currency.USDT):
contractSettlementType = futures.Quanto
}
c := futures.Contract{
Exchange: e.Name,
Name: name,
Underlying: name,
Name: contracts[i].Name,
Underlying: contracts[i].Name,
Asset: a,
IsActive: !contracts[i].InDelisting,
IsActive: contracts[i].DelistedTime.Time().IsZero() || contracts[i].DelistedTime.Time().After(time.Now()),
Type: futures.Perpetual,
SettlementType: contractSettlementType,
SettlementCurrencies: currency.Currencies{settle},
@@ -1904,10 +1890,6 @@ func (e *Exchange) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item)
if pairsData[i].TradeStatus == "untradable" {
continue
}
pair, err := e.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.
@@ -1917,77 +1899,67 @@ func (e *Exchange) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item)
}
l = append(l, limits.MinMaxLevel{
Key: key.NewExchangeAssetPair(e.Name, a, pair),
QuoteStepIncrementSize: math.Pow10(-int(pairsData[i].Precision)),
Key: key.NewExchangeAssetPair(e.Name, a, currency.NewPair(pairsData[i].Base, pairsData[i].Quote)),
QuoteStepIncrementSize: math.Pow10(-int(pairsData[i].PricePrecision)),
AmountStepIncrementSize: math.Pow10(-int(pairsData[i].AmountPrecision)),
MinimumBaseAmount: minBaseAmount,
MinimumQuoteAmount: pairsData[i].MinQuoteAmount.Float64(),
Delisted: pairsData[i].DelistingTime.Time(),
})
}
case asset.CoinMarginedFutures:
btcContracts, err := e.GetAllFutureContracts(ctx, currency.BTC)
case asset.USDTMarginedFutures, asset.CoinMarginedFutures:
settlement := currency.USDT
if a == asset.CoinMarginedFutures {
settlement = currency.BTC
}
contractInfo, err := e.GetAllFutureContracts(ctx, settlement)
if err != nil {
return err
}
l = make([]limits.MinMaxLevel, 0, len(btcContracts))
for x := range btcContracts {
p := strings.ToUpper(btcContracts[x].Name)
cp, err := currency.NewPairFromString(p)
if err != nil {
return err
// MBABYDOGE price is 1e6 x spot price
divCurrency := currency.NewCode("MBABYDOGE")
l = make([]limits.MinMaxLevel, 0, len(contractInfo))
for i := range contractInfo {
priceDiv := 1.0
if contractInfo[i].Name.Base.Equal(divCurrency) {
priceDiv = 1e6
}
l = append(l, limits.MinMaxLevel{
Key: key.NewExchangeAssetPair(e.Name, a, cp),
MinimumBaseAmount: float64(btcContracts[x].OrderSizeMin),
MaximumBaseAmount: float64(btcContracts[x].OrderSizeMax),
PriceStepIncrementSize: btcContracts[x].OrderPriceRound.Float64(),
AmountStepIncrementSize: 1,
})
}
case asset.USDTMarginedFutures:
usdtContracts, err := e.GetAllFutureContracts(ctx, currency.USDT)
if err != nil {
return err
}
l = make([]limits.MinMaxLevel, 0, len(usdtContracts))
for x := range usdtContracts {
p := strings.ToUpper(usdtContracts[x].Name)
cp, err := currency.NewPairFromString(p)
if err != nil {
return err
}
l = append(l, limits.MinMaxLevel{
Key: key.NewExchangeAssetPair(e.Name, a, cp),
MinimumBaseAmount: float64(usdtContracts[x].OrderSizeMin),
MaximumBaseAmount: float64(usdtContracts[x].OrderSizeMax),
PriceStepIncrementSize: usdtContracts[x].OrderPriceRound.Float64(),
AmountStepIncrementSize: 1,
Key: key.NewExchangeAssetPair(e.Name, a, contractInfo[i].Name),
MinimumBaseAmount: contractInfo[i].OrderSizeMin.Float64(),
MaximumBaseAmount: contractInfo[i].OrderSizeMax.Float64(),
PriceStepIncrementSize: contractInfo[i].OrderPriceRound.Float64(),
AmountStepIncrementSize: 1, // 1 Contract
MultiplierDecimal: contractInfo[i].QuantoMultiplier.Float64(),
PriceDivisor: priceDiv,
Delisting: contractInfo[i].DelistingTime.Time(),
Delisted: contractInfo[i].DelistedTime.Time(),
Listed: contractInfo[i].LaunchTime.Time(),
})
}
case asset.DeliveryFutures:
btcContracts, err := e.GetAllDeliveryContracts(ctx, currency.BTC)
if err != nil {
return err
}
usdtContracts, err := e.GetAllDeliveryContracts(ctx, currency.USDT)
if err != nil {
return err
}
btcContracts = append(btcContracts, usdtContracts...)
l = make([]limits.MinMaxLevel, 0, len(btcContracts))
for x := range btcContracts {
p := strings.ToUpper(btcContracts[x].Name)
cp, err := currency.NewPairFromString(p)
for _, settlement := range []currency.Code{currency.BTC, currency.USDT} {
contractInfo, err := e.GetAllDeliveryContracts(ctx, settlement)
if err != nil {
return err
}
l = append(l, limits.MinMaxLevel{
Key: key.NewExchangeAssetPair(e.Name, a, cp),
MinimumBaseAmount: float64(btcContracts[x].OrderSizeMin),
MaximumBaseAmount: float64(btcContracts[x].OrderSizeMax),
PriceStepIncrementSize: btcContracts[x].OrderPriceRound.Float64(),
AmountStepIncrementSize: 1,
})
l = slices.Grow(l, len(contractInfo))
for x := range contractInfo {
p := strings.ToUpper(contractInfo[x].Name)
cp, err := currency.NewPairFromString(p)
if err != nil {
return err
}
l = append(l, limits.MinMaxLevel{
Key: key.NewExchangeAssetPair(e.Name, a, cp),
MinimumBaseAmount: float64(contractInfo[x].OrderSizeMin),
MaximumBaseAmount: float64(contractInfo[x].OrderSizeMax),
PriceStepIncrementSize: contractInfo[x].OrderPriceRound.Float64(),
AmountStepIncrementSize: 1,
Expiry: contractInfo[x].ExpireTime.Time(),
})
}
}
case asset.Options:
underlyings, err := e.GetAllOptionsUnderlyings(ctx)
@@ -2142,14 +2114,10 @@ func (e *Exchange) GetLatestFundingRates(ctx context.Context, r *fundingrate.Lat
}
resp := make([]fundingrate.LatestRateResponse, 0, len(contracts))
for i := range contracts {
cp, err := currency.NewPairFromString(contracts[i].Name)
if err != nil {
return nil, err
}
if !pairs.Contains(cp, false) {
if !pairs.Contains(contracts[i].Name, true) {
continue
}
resp = append(resp, contractToFundingRate(e.Name, r.Asset, cp, &contracts[i], r.IncludePredictedRate))
resp = append(resp, contractToFundingRate(e.Name, r.Asset, contracts[i].Name, &contracts[i], r.IncludePredictedRate))
}
return slices.Clip(resp), nil
@@ -2212,8 +2180,10 @@ func (e *Exchange) GetOpenInterest(ctx context.Context, keys ...key.PairAsset) (
for _, c := range contracts {
if p.IsEmpty() { // If not exactly one key provided
p, err = e.MatchSymbolWithAvailablePairs(c.contractName(), a, true)
if err != nil && !errors.Is(err, currency.ErrPairNotFound) {
errs = common.AppendError(errs, fmt.Errorf("%w from %s contract %s", err, a, c.contractName()))
if err != nil {
if err := common.ExcludeError(err, currency.ErrPairNotFound); err != nil {
errs = common.AppendError(errs, fmt.Errorf("%w from %s contract %s", err, a, c.contractName()))
}
continue
}
if len(keys) == 0 { // No keys: All enabled pairs
@@ -2257,7 +2227,7 @@ func (c *FuturesContract) openInterest() float64 {
}
func (c *FuturesContract) contractName() string {
return c.Name
return c.Name.String()
}
func (c *DeliveryContract) openInterest() float64 {