mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
bybit: Enhance order execution limits (#2069)
* 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) * bybit: enhance order execution limits (cherry-pick) * relax test to not break all the others and add Delivery field lost in cherry-pick wonderland * boss king nits * Update exchanges/bybit/bybit_wrapper.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * Update exchanges/bybit/bybit_test.go Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> * Update exchanges/bybit/bybit_wrapper.go Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> * gk:nits * Update exchanges/bybit/bybit_wrapper.go Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> * gk:nits --------- 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> Co-authored-by: Scott <gloriousCode@users.noreply.github.com> Co-authored-by: shazbert <shazbert@DESKTOP-3QKKR6J.localdomain>
This commit is contained in:
@@ -771,15 +771,42 @@ func TestGetDeliveryPrice(t *testing.T) {
|
||||
|
||||
func TestUpdateOrderExecutionLimits(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testexch.UpdatePairsOnce(t, e)
|
||||
for _, a := range e.GetAssetTypes(false) {
|
||||
t.Run(a.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.NoError(t, e.UpdateOrderExecutionLimits(t.Context(), a), "UpdateOrderExecutionLimits must not error")
|
||||
pairs, err := e.CurrencyPairs.GetPairs(a, true)
|
||||
require.NoError(t, err, "GetPairs must not error")
|
||||
l, err := e.GetOrderExecutionLimits(a, pairs[0])
|
||||
require.NoError(t, err, "GetOrderExecutionLimits must not error")
|
||||
assert.Positive(t, l.MinimumBaseAmount, "MinimumBaseAmount should be positive")
|
||||
|
||||
for _, p := range pairs {
|
||||
t.Run(p.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
l, err := e.GetOrderExecutionLimits(a, p)
|
||||
require.NoError(t, err, "GetOrderExecutionLimits must not error")
|
||||
assert.Positive(t, l.MinimumBaseAmount, "MinimumBaseAmount should be positive")
|
||||
|
||||
if !l.Delisted.IsZero() {
|
||||
assert.NotZero(t, l.Delisting, "Delisting should be set for Delisted coins")
|
||||
}
|
||||
|
||||
pair := l.Key.Pair()
|
||||
require.True(t, pair.Equal(p), "Pair must be equal to input")
|
||||
require.Greater(t, len(pair.String()), 3, "pair string length must be > 3 to check for 1xxx rule")
|
||||
require.Equal(t, e.Name, l.Key.Exchange, "Exchange must be equal to input")
|
||||
require.Equal(t, a, l.Key.Asset, "Asset must be equal to input")
|
||||
|
||||
assert.Positive(t, l.PriceDivisor, "PriceDivisor should be positive")
|
||||
if pair.String()[:2] == "10" {
|
||||
assert.Greater(t, l.PriceDivisor, 1.0, "PriceDivisor for 1xxx pairs should be > 1.0")
|
||||
}
|
||||
|
||||
if a == asset.USDTMarginedFutures && !pair.Quote.Equal(currency.USDT) {
|
||||
assert.NotZero(t, l.Expiry, "Expiry should be set for USDT margined non-USDT pairs")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ type AccountFee struct {
|
||||
|
||||
// InstrumentsInfo represents a category, page indicator, and list of instrument information.
|
||||
type InstrumentsInfo struct {
|
||||
Category string `json:"category"`
|
||||
List []InstrumentInfo `json:"list"`
|
||||
NextPageCursor string `json:"nextPageCursor"`
|
||||
Category string `json:"category"`
|
||||
List []*InstrumentInfo `json:"list"`
|
||||
NextPageCursor string `json:"nextPageCursor"`
|
||||
}
|
||||
|
||||
// InstrumentInfo holds all instrument info across
|
||||
@@ -86,15 +86,16 @@ type InstrumentInfo struct {
|
||||
TickSize types.Number `json:"tickSize"`
|
||||
} `json:"priceFilter"`
|
||||
LotSizeFilter struct {
|
||||
MaxOrderQty types.Number `json:"maxOrderQty"`
|
||||
MinOrderQty types.Number `json:"minOrderQty"`
|
||||
QtyStep types.Number `json:"qtyStep"`
|
||||
PostOnlyMaxOrderQty types.Number `json:"postOnlyMaxOrderQty"`
|
||||
BasePrecision types.Number `json:"basePrecision"`
|
||||
QuotePrecision types.Number `json:"quotePrecision"`
|
||||
MinOrderAmt types.Number `json:"minOrderAmt"`
|
||||
MaxOrderAmt types.Number `json:"maxOrderAmt"`
|
||||
MinNotionalValue types.Number `json:"minNotionalValue"`
|
||||
MaxOrderQuantity types.Number `json:"maxOrderQty"`
|
||||
MinOrderQuantity types.Number `json:"minOrderQty"`
|
||||
QuantityStep types.Number `json:"qtyStep"`
|
||||
PostOnlyMaxOrderQuantity types.Number `json:"postOnlyMaxOrderQty"`
|
||||
BasePrecision types.Number `json:"basePrecision"`
|
||||
QuotePrecision types.Number `json:"quotePrecision"`
|
||||
MinOrderAmount types.Number `json:"minOrderAmt"`
|
||||
MaxOrderAmount types.Number `json:"maxOrderAmt"`
|
||||
MinNotionalValue types.Number `json:"minNotionalValue"`
|
||||
MaxMarketOrderQuantity types.Number `json:"maxMktOrderQty"`
|
||||
} `json:"lotSizeFilter"`
|
||||
UnifiedMarginTrade bool `json:"unifiedMarginTrade"`
|
||||
FundingInterval int64 `json:"fundingInterval"`
|
||||
|
||||
@@ -412,7 +412,7 @@ func (e *Exchange) FetchTradablePairs(ctx context.Context, a asset.Item) (curren
|
||||
}
|
||||
var (
|
||||
pairs currency.Pairs
|
||||
allPairs []InstrumentInfo
|
||||
allPairs []*InstrumentInfo
|
||||
response *InstrumentsInfo
|
||||
)
|
||||
var nextPageCursor string
|
||||
@@ -1630,27 +1630,79 @@ func (e *Exchange) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item)
|
||||
return fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
l := make([]limits.MinMaxLevel, 0, len(allInstrumentsInfo.List))
|
||||
for x := range allInstrumentsInfo.List {
|
||||
if allInstrumentsInfo.List[x].Status != "Trading" {
|
||||
continue
|
||||
}
|
||||
symbol := allInstrumentsInfo.List[x].transformSymbol(a)
|
||||
for _, inst := range allInstrumentsInfo.List {
|
||||
symbol := inst.transformSymbol(a)
|
||||
pair, err := e.MatchSymbolWithAvailablePairs(symbol, a, true)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s unable to load limits for %s %v, pair data missing", e.Name, a, symbol)
|
||||
continue
|
||||
}
|
||||
|
||||
priceDivisor := 1.0
|
||||
if symbol[:2] == "10" { // handle 1000SHIBUSDT, 1000PEPEUSDT etc; screen 1INCHUSDT
|
||||
for _, r := range symbol[1:] {
|
||||
if r != '0' {
|
||||
break
|
||||
}
|
||||
priceDivisor *= 10
|
||||
}
|
||||
}
|
||||
|
||||
var delistingAt time.Time
|
||||
var delistedAt time.Time
|
||||
var delivery time.Time
|
||||
if !inst.DeliveryTime.Time().IsZero() {
|
||||
switch a {
|
||||
case asset.Options:
|
||||
delivery = inst.DeliveryTime.Time()
|
||||
case asset.USDTMarginedFutures, asset.CoinMarginedFutures, asset.USDCMarginedFutures:
|
||||
switch inst.ContractType {
|
||||
case "LinearFutures", "InverseFutures":
|
||||
delivery = inst.DeliveryTime.Time()
|
||||
default:
|
||||
delistedAt = inst.DeliveryTime.Time()
|
||||
// Not entirely accurate but from docs the system will use the average index price in the last
|
||||
// 30 minutes before the delisting time. See: https://www.bybit.com/en/help-center/article/Bybit-Derivatives-Delisting-Mechanism-DDM
|
||||
delistingAt = delistedAt.Add(-30 * time.Minute)
|
||||
}
|
||||
case asset.Spot:
|
||||
// asset.Spot does not return a delivery time and there is no API field for delisting time
|
||||
log.Warnf(log.ExchangeSys, "%s %s: delivery time returned for spot asset", e.Name, pair)
|
||||
}
|
||||
}
|
||||
|
||||
baseStepAmount := inst.LotSizeFilter.QuantityStep.Float64()
|
||||
if a == asset.Spot {
|
||||
baseStepAmount = inst.LotSizeFilter.BasePrecision.Float64()
|
||||
}
|
||||
|
||||
maxBaseAmount := inst.LotSizeFilter.MaxOrderQuantity.Float64()
|
||||
if a != asset.Spot && a != asset.Options {
|
||||
maxBaseAmount = inst.LotSizeFilter.MaxMarketOrderQuantity.Float64()
|
||||
}
|
||||
|
||||
minQuoteAmount := inst.LotSizeFilter.MinOrderAmount.Float64()
|
||||
if a != asset.Spot {
|
||||
minQuoteAmount = inst.LotSizeFilter.MinNotionalValue.Float64()
|
||||
}
|
||||
|
||||
l = append(l, limits.MinMaxLevel{
|
||||
Key: key.NewExchangeAssetPair(e.Name, a, pair),
|
||||
MinimumBaseAmount: allInstrumentsInfo.List[x].LotSizeFilter.MinOrderQty.Float64(),
|
||||
MaximumBaseAmount: allInstrumentsInfo.List[x].LotSizeFilter.MaxOrderQty.Float64(),
|
||||
MinPrice: allInstrumentsInfo.List[x].PriceFilter.MinPrice.Float64(),
|
||||
MaxPrice: allInstrumentsInfo.List[x].PriceFilter.MaxPrice.Float64(),
|
||||
PriceStepIncrementSize: allInstrumentsInfo.List[x].PriceFilter.TickSize.Float64(),
|
||||
AmountStepIncrementSize: allInstrumentsInfo.List[x].LotSizeFilter.BasePrecision.Float64(),
|
||||
QuoteStepIncrementSize: allInstrumentsInfo.List[x].LotSizeFilter.QuotePrecision.Float64(),
|
||||
MinimumQuoteAmount: allInstrumentsInfo.List[x].LotSizeFilter.MinOrderQty.Float64() * allInstrumentsInfo.List[x].PriceFilter.MinPrice.Float64(),
|
||||
MaximumQuoteAmount: allInstrumentsInfo.List[x].LotSizeFilter.MaxOrderQty.Float64() * allInstrumentsInfo.List[x].PriceFilter.MaxPrice.Float64(),
|
||||
MinimumBaseAmount: inst.LotSizeFilter.MinOrderQuantity.Float64(),
|
||||
MaximumBaseAmount: maxBaseAmount,
|
||||
MinPrice: inst.PriceFilter.MinPrice.Float64(),
|
||||
MaxPrice: inst.PriceFilter.MaxPrice.Float64(),
|
||||
PriceStepIncrementSize: inst.PriceFilter.TickSize.Float64(),
|
||||
AmountStepIncrementSize: baseStepAmount,
|
||||
QuoteStepIncrementSize: inst.LotSizeFilter.QuotePrecision.Float64(),
|
||||
MinimumQuoteAmount: minQuoteAmount,
|
||||
MaximumQuoteAmount: inst.LotSizeFilter.MaxOrderAmount.Float64(),
|
||||
Delisting: delistingAt,
|
||||
Delisted: delistedAt,
|
||||
Expiry: delivery,
|
||||
PriceDivisor: priceDivisor,
|
||||
Listed: inst.LaunchTime.Time(),
|
||||
MultiplierDecimal: 1, // All assets on Bybit are 1x
|
||||
})
|
||||
}
|
||||
return limits.Load(l)
|
||||
@@ -1780,7 +1832,7 @@ func (e *Exchange) GetFuturesContractDetails(ctx context.Context, item asset.Ite
|
||||
}
|
||||
resp := make([]futures.Contract, 0, len(inverseContracts.List)+len(linearContracts.List))
|
||||
|
||||
var instruments []InstrumentInfo
|
||||
var instruments []*InstrumentInfo
|
||||
for i := range linearContracts.List {
|
||||
if linearContracts.List[i].SettleCoin != "USDC" {
|
||||
continue
|
||||
@@ -1859,7 +1911,7 @@ func (e *Exchange) GetFuturesContractDetails(ctx context.Context, item asset.Ite
|
||||
}
|
||||
resp := make([]futures.Contract, 0, len(inverseContracts.List)+len(linearContracts.List))
|
||||
|
||||
var instruments []InstrumentInfo
|
||||
var instruments []*InstrumentInfo
|
||||
for i := range linearContracts.List {
|
||||
if linearContracts.List[i].SettleCoin != "USDT" {
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user