Skip to content

Commit 26fbf6c

Browse files
authored
Fix instrument info retrieval for Bybit (#2134)
1 parent 9a2ed76 commit 26fbf6c

File tree

5 files changed

+92
-52
lines changed

5 files changed

+92
-52
lines changed

nautilus_trader/adapters/bybit/endpoints/market/instruments_info.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class BybitInstrumentsInfoGetParams(msgspec.Struct, omit_defaults=True, frozen=T
3030
category: BybitProductType | None = None
3131
symbol: str | None = None
3232
status: str | None = None
33+
baseCoin: str | None = None
34+
limit: int | None = None
35+
cursor: str | None = None
3336

3437

3538
class BybitInstrumentsInfoEndpoint(BybitHttpEndpoint):

nautilus_trader/adapters/bybit/http/market.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@
3131
from nautilus_trader.adapters.bybit.endpoints.market.trades import BybitTradesGetParams
3232
from nautilus_trader.adapters.bybit.http.client import BybitHttpClient
3333
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrument
34+
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentInverse
35+
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentLinear
3436
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentList
37+
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentOption
38+
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentSpot
3539
from nautilus_trader.adapters.bybit.schemas.market.kline import BybitKline
3640
from nautilus_trader.adapters.bybit.schemas.market.server_time import BybitServerTime
3741
from nautilus_trader.adapters.bybit.schemas.market.ticker import BybitTickerList
@@ -86,14 +90,65 @@ async def fetch_server_time(self) -> BybitServerTime:
8690
async def fetch_instruments(
8791
self,
8892
product_type: BybitProductType,
93+
symbol: str | None = None,
94+
status: str | None = None,
95+
base_coin: str | None = None,
96+
limit: int | None = None,
97+
cursor: str | None = None,
8998
) -> BybitInstrumentList:
9099
response = await self._endpoint_instruments.get(
91100
BybitInstrumentsInfoGetParams(
92101
category=product_type,
102+
symbol=symbol,
103+
status=status,
104+
baseCoin=base_coin,
105+
limit=limit,
106+
cursor=cursor,
93107
),
94108
)
95109
return response.result.list
96110

111+
async def fetch_all_instruments(
112+
self,
113+
product_type: BybitProductType,
114+
symbol: str | None = None,
115+
status: str | None = None,
116+
base_coin: str | None = None,
117+
) -> BybitInstrumentList:
118+
"""
119+
Fetch all instruments with pagination from Bybit.
120+
"""
121+
all_instruments: list[BybitInstrument] = []
122+
current_cursor = None
123+
124+
while True:
125+
response = await self._endpoint_instruments.get(
126+
BybitInstrumentsInfoGetParams(
127+
category=product_type,
128+
symbol=symbol,
129+
status=status,
130+
baseCoin=base_coin,
131+
limit=1000,
132+
cursor=current_cursor,
133+
),
134+
)
135+
all_instruments.extend(response.result.list)
136+
current_cursor = response.result.nextPageCursor
137+
138+
if not current_cursor or current_cursor == "":
139+
break
140+
141+
if product_type == BybitProductType.SPOT:
142+
return [x for x in all_instruments if isinstance(x, BybitInstrumentSpot)]
143+
elif product_type == BybitProductType.LINEAR:
144+
return [x for x in all_instruments if isinstance(x, BybitInstrumentLinear)]
145+
elif product_type == BybitProductType.INVERSE:
146+
return [x for x in all_instruments if isinstance(x, BybitInstrumentInverse)]
147+
elif product_type == BybitProductType.OPTION:
148+
return [x for x in all_instruments if isinstance(x, BybitInstrumentOption)]
149+
else:
150+
raise ValueError(f"Unsupported product type: {product_type}")
151+
97152
async def fetch_instrument(
98153
self,
99154
product_type: BybitProductType,

nautilus_trader/adapters/bybit/providers.py

Lines changed: 25 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,15 @@ async def load_all_async(self, filters: dict | None = None) -> None:
9393
fee_rates: dict[BybitProductType, list[BybitFeeRate]] = {}
9494

9595
for product_type in self._product_types:
96-
instrument_infos[product_type] = await self._http_market.fetch_instruments(
96+
instrument_infos[product_type] = await self._http_market.fetch_all_instruments(
9797
product_type,
9898
)
9999
fee_rates[product_type] = await self._http_account.fetch_fee_rate(
100100
product_type,
101101
)
102102

103-
for product_type in instrument_infos:
104-
for instrument in instrument_infos[product_type]:
103+
for product_type, instruments in instrument_infos.items():
104+
for instrument in instruments:
105105
target_fee_rate = next(
106106
(
107107
item
@@ -141,7 +141,7 @@ async def load_ids_async(
141141
fee_rates: dict[BybitProductType, list[BybitFeeRate]] = {}
142142

143143
for product_type in self._product_types:
144-
instrument_infos[product_type] = await self._http_market.fetch_instruments(
144+
instrument_infos[product_type] = await self._http_market.fetch_all_instruments(
145145
product_type,
146146
)
147147
fee_rates[product_type] = await self._http_account.fetch_fee_rate(
@@ -203,17 +203,21 @@ def _parse_instrument(
203203
else:
204204
raise TypeError(f"Unsupported Bybit instrument, was {instrument}")
205205

206-
def _parse_spot_instrument(
206+
def _parse_to_instrument(
207207
self,
208-
data: BybitInstrumentSpot,
208+
instrument: BybitInstrument,
209209
fee_rate: BybitFeeRate,
210210
) -> None:
211+
if isinstance(instrument, BybitInstrumentOption):
212+
self._log.warning("Parsing of instrument Options is currently not supported")
213+
return
214+
211215
try:
212-
base_currency = self.currency(data.baseCoin)
213-
quote_currency = self.currency(data.quoteCoin)
216+
base_currency = self.currency(instrument.baseCoin)
217+
quote_currency = self.currency(instrument.quoteCoin)
214218
ts_event = self._clock.timestamp_ns()
215219
ts_init = self._clock.timestamp_ns()
216-
instrument = data.parse_to_instrument(
220+
instrument = instrument.parse_to_instrument(
217221
base_currency=base_currency,
218222
quote_currency=quote_currency,
219223
fee_rate=fee_rate,
@@ -223,60 +227,34 @@ def _parse_spot_instrument(
223227
self.add(instrument=instrument)
224228
except ValueError as e:
225229
if self._log_warnings:
226-
self._log.warning(f"Unable to parse option instrument {data.symbol}: {e}")
230+
self._log.warning(
231+
f"Unable to parse {instrument.__class__.__name__} instrument {instrument.symbol}: {e}",
232+
)
233+
234+
def _parse_spot_instrument(
235+
self,
236+
data: BybitInstrumentSpot,
237+
fee_rate: BybitFeeRate,
238+
) -> None:
239+
self._parse_to_instrument(data, fee_rate)
227240

228241
def _parse_linear_instrument(
229242
self,
230243
data: BybitInstrumentLinear,
231244
fee_rate: BybitFeeRate,
232245
) -> None:
233-
try:
234-
base_currency = self.currency(data.baseCoin)
235-
quote_currency = self.currency(data.quoteCoin)
236-
ts_event = self._clock.timestamp_ns()
237-
ts_init = self._clock.timestamp_ns()
238-
instrument = data.parse_to_instrument(
239-
base_currency=base_currency,
240-
quote_currency=quote_currency,
241-
fee_rate=fee_rate,
242-
ts_event=ts_event,
243-
ts_init=ts_init,
244-
)
245-
self.add(instrument=instrument)
246-
except ValueError as e:
247-
if self._log_warnings:
248-
self._log.warning(f"Unable to parse linear instrument {data.symbol}: {e}")
246+
self._parse_to_instrument(data, fee_rate)
249247

250248
def _parse_inverse_instrument(
251249
self,
252250
data: BybitInstrumentInverse,
253251
fee_rate: BybitFeeRate,
254252
) -> None:
255-
try:
256-
base_currency = self.currency(data.baseCoin)
257-
quote_currency = self.currency(data.quoteCoin)
258-
ts_event = self._clock.timestamp_ns()
259-
ts_init = self._clock.timestamp_ns()
260-
instrument = data.parse_to_instrument(
261-
base_currency=base_currency,
262-
quote_currency=quote_currency,
263-
fee_rate=fee_rate,
264-
ts_event=ts_event,
265-
ts_init=ts_init,
266-
)
267-
self.add(instrument=instrument)
268-
except ValueError as e:
269-
if self._log_warnings:
270-
self._log.warning(f"Unable to parse inverse instrument {data.symbol}: {e}")
253+
self._parse_to_instrument(data, fee_rate)
271254

272255
def _parse_option_instrument(
273256
self,
274257
instrument: BybitInstrumentOption,
275258
fee_rate: BybitFeeRate,
276259
) -> None:
277260
self._log.warning("Parsing of instrument Options is currently not supported")
278-
try:
279-
pass
280-
except ValueError as e:
281-
if self._log_warnings:
282-
self._log.warning(f"Unable to parse option instrument {instrument.symbol}: {e}")

nautilus_trader/adapters/bybit/schemas/common.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class BybitListResult(Generic[T], msgspec.Struct):
2727
list: list[T]
2828

2929

30+
class BybitListResultWithCursor(BybitListResult[T]):
31+
nextPageCursor: str | None = None
32+
33+
3034
def bybit_coin_result(object_type: Any):
3135
return msgspec.defstruct("", [("coin", list[object_type])])
3236

nautilus_trader/adapters/bybit/schemas/instrument.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from nautilus_trader.adapters.bybit.common.enums import BybitOptionType
2424
from nautilus_trader.adapters.bybit.common.symbol import BybitSymbol
2525
from nautilus_trader.adapters.bybit.schemas.account.fee_rate import BybitFeeRate
26-
from nautilus_trader.adapters.bybit.schemas.common import BybitListResult
26+
from nautilus_trader.adapters.bybit.schemas.common import BybitListResultWithCursor
2727
from nautilus_trader.adapters.bybit.schemas.common import LeverageFilter
2828
from nautilus_trader.adapters.bybit.schemas.common import LinearPriceFilter
2929
from nautilus_trader.adapters.bybit.schemas.common import LotSizeFilter
@@ -376,26 +376,26 @@ def parse_to_instrument(
376376
class BybitInstrumentsSpotResponse(msgspec.Struct):
377377
retCode: int
378378
retMsg: str
379-
result: BybitListResult[BybitInstrumentSpot]
379+
result: BybitListResultWithCursor[BybitInstrumentSpot]
380380
time: int
381381

382382

383383
class BybitInstrumentsLinearResponse(msgspec.Struct):
384384
retCode: int
385385
retMsg: str
386-
result: BybitListResult[BybitInstrumentLinear]
386+
result: BybitListResultWithCursor[BybitInstrumentLinear]
387387
time: int
388388

389389

390390
class BybitInstrumentsInverseResponse(msgspec.Struct):
391391
retCode: int
392392
retMsg: str
393-
result: BybitListResult[BybitInstrumentInverse]
393+
result: BybitListResultWithCursor[BybitInstrumentInverse]
394394
time: int
395395

396396

397397
class BybitInstrumentsOptionResponse(msgspec.Struct):
398398
retCode: int
399399
retMsg: str
400-
result: BybitListResult[BybitInstrumentOption]
400+
result: BybitListResultWithCursor[BybitInstrumentOption]
401401
time: int

0 commit comments

Comments
 (0)