Skip to content

Commit

Permalink
Add Bybit inverse instrument support
Browse files Browse the repository at this point in the history
  • Loading branch information
cjdsellers committed Apr 1, 2024
1 parent 3e5f2cd commit 1dd8135
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 76 deletions.
13 changes: 10 additions & 3 deletions examples/live/bybit/bybit_market_maker.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@
# *** THIS INTEGRATION IS STILL UNDER CONSTRUCTION. ***
# *** CONSIDER IT TO BE IN AN UNSTABLE BETA PHASE AND EXERCISE CAUTION. ***

# LINEAR
product_type = BybitProductType.LINEAR
symbol = f"ETHUSDT-{product_type.value.upper()}"
trade_size = Decimal("0.010")

# INVERSE
# product_type = BybitProductType.INVERSE
# symbol = f"XRPUSD-{product_type.value.upper()}" # Use for inverse
# trade_size = Decimal("100") # Use for inverse

# Configure the trading node
config_node = TradingNodeConfig(
Expand Down Expand Up @@ -101,14 +109,13 @@
node = TradingNode(config=config_node)

# Configure your strategy
symbol = f"ETHUSDT-{product_type.value.upper()}"
strat_config = VolatilityMarketMakerConfig(
instrument_id=InstrumentId.from_str(f"{symbol}.BYBIT"),
external_order_claims=[InstrumentId.from_str(f"{symbol}.BYBIT")],
bar_type=BarType.from_str(f"{symbol}.BYBIT-1-MINUTE-LAST-EXTERNAL"),
atr_period=20,
atr_multiple=6.0,
trade_size=Decimal("0.010"),
atr_multiple=3.0,
trade_size=trade_size,
)
# Instantiate your strategy
strategy = VolatilityMarketMaker(config=strat_config)
Expand Down
3 changes: 2 additions & 1 deletion nautilus_trader/adapters/bybit/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,10 @@ def __init__(
self._decoders["trade"][product_type] = decoder_ws_trade()
self._decoders["ticker"][product_type] = decoder_ws_ticker(product_type)
self._decoders["kline"][product_type] = decoder_ws_kline()

self._decoder_ws_msg_general = msgspec.json.Decoder(BybitWsMessageGeneral)

self._log.info(f"Initialized WebSocket handlers for {product_type.value} products")

self._tob_quotes: set[InstrumentId] = set()
self._depths: dict[InstrumentId, int] = {}
self._topic_bar_type: dict[str, BarType] = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from nautilus_trader.adapters.bybit.common.enums import BybitProductType
from nautilus_trader.adapters.bybit.endpoints.endpoint import BybitHttpEndpoint
from nautilus_trader.adapters.bybit.http.client import BybitHttpClient
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentsInverseResponse
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentsLinearResponse
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentsOptionResponse
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentsSpotResponse
Expand All @@ -43,10 +44,13 @@ def __init__(
endpoint_type=BybitEndpointType.MARKET,
url_path=url_path,
)
self._response_decoder_instrument_spot = msgspec.json.Decoder(BybitInstrumentsSpotResponse)
self._response_decoder_instrument_linear = msgspec.json.Decoder(
BybitInstrumentsLinearResponse,
)
self._response_decoder_instrument_spot = msgspec.json.Decoder(BybitInstrumentsSpotResponse)
self._response_decoder_instrument_inverse = msgspec.json.Decoder(
BybitInstrumentsInverseResponse,
)
self._response_decoder_instrument_option = msgspec.json.Decoder(
BybitInstrumentsOptionResponse,
)
Expand All @@ -57,15 +61,18 @@ async def get(
) -> (
BybitInstrumentsSpotResponse
| BybitInstrumentsLinearResponse
| BybitInstrumentsInverseResponse
| BybitInstrumentsOptionResponse
):
method_type = HttpMethod.GET
raw = await self._method(method_type, params)
if params.category == BybitProductType.SPOT:
return self._response_decoder_instrument_spot.decode(raw)
elif params.category in (BybitProductType.LINEAR, BybitProductType.INVERSE):
elif params.category == BybitProductType.LINEAR:
return self._response_decoder_instrument_linear.decode(raw)
elif params.category == BybitProductType.INVERSE:
return self._response_decoder_instrument_inverse.decode(raw)
elif params.category == BybitProductType.OPTION:
return self._response_decoder_instrument_option.decode(raw)
else:
raise ValueError("Invalid account type")
raise ValueError(f"Invalid product type, was {params.category}")
17 changes: 14 additions & 3 deletions nautilus_trader/adapters/bybit/http/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,16 @@ async def query_position_info(
product_type: BybitProductType,
symbol: str | None = None,
) -> list[BybitPositionStruct]:
# symbol = 'USD'
match product_type:
case BybitProductType.INVERSE:
settle_coin = None
case _:
settle_coin = self.default_settle_coin if symbol is None else None

response = await self._endpoint_position_info.get(
PositionInfoGetParams(
symbol=symbol,
settleCoin=(self.default_settle_coin if symbol is None else None),
settleCoin=settle_coin,
category=get_category_from_product_type(product_type),
),
)
Expand All @@ -106,11 +111,17 @@ async def query_open_orders(
product_type: BybitProductType,
symbol: str | None = None,
) -> list[BybitOrder]:
match product_type:
case BybitProductType.INVERSE:
settle_coin = None
case _:
settle_coin = self.default_settle_coin if symbol is None else None

response = await self._endpoint_open_orders.get(
BybitOpenOrdersGetParams(
category=product_type,
symbol=symbol,
settleCoin=(self.default_settle_coin if symbol is None else None),
settleCoin=settle_coin,
),
)
return response.result.list
Expand Down
41 changes: 33 additions & 8 deletions nautilus_trader/adapters/bybit/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from nautilus_trader.adapters.bybit.http.market import BybitMarketHttpAPI
from nautilus_trader.adapters.bybit.schemas.account.fee_rate import BybitFeeRate
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrument
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentInverse
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentLinear
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentList
from nautilus_trader.adapters.bybit.schemas.instrument import BybitInstrumentOption
Expand Down Expand Up @@ -144,10 +145,12 @@ def _parse_instrument(
self._parse_spot_instrument(instrument, fee_rate)
elif isinstance(instrument, BybitInstrumentLinear):
self._parse_linear_instrument(instrument, fee_rate)
elif isinstance(instrument, BybitInstrumentInverse):
self._parse_inverse_instrument(instrument, fee_rate)
elif isinstance(instrument, BybitInstrumentOption):
self._parse_option_instrument(instrument)
else:
raise TypeError("Unsupported instrument type in BybitInstrumentProvider")
raise TypeError(f"Unsupported Bybit instrument, was {instrument}")

async def load_async(self, instrument_id: InstrumentId, filters: dict | None = None) -> None:
PyCondition.not_none(instrument_id, "instrument_id")
Expand All @@ -174,19 +177,31 @@ def _parse_spot_instrument(
if self._log_warnings:
self._log.warning(f"Unable to parse option instrument {data.symbol}, {e}")

def _parse_option_instrument(
def _parse_linear_instrument(
self,
instrument: BybitInstrumentOption,
data: BybitInstrumentLinear,
fee_rate: BybitFeeRate,
) -> None:
try:
pass
base_currency = data.parse_to_base_currency()
quote_currency = data.parse_to_quote_currency()
ts_event = self._clock.timestamp_ns()
ts_init = self._clock.timestamp_ns()
instrument = data.parse_to_instrument(
fee_rate=fee_rate,
ts_event=ts_event,
ts_init=ts_init,
)
self.add_currency(base_currency)
self.add_currency(quote_currency)
self.add(instrument=instrument)
except ValueError as e:
if self._log_warnings:
self._log.warning(f"Unable to parse option instrument {instrument.symbol}, {e}")
self._log.warning(f"Unable to parse linear instrument {data.symbol}, {e}")

def _parse_linear_instrument(
def _parse_inverse_instrument(
self,
data: BybitInstrumentLinear,
data: BybitInstrumentInverse,
fee_rate: BybitFeeRate,
) -> None:
try:
Expand All @@ -204,4 +219,14 @@ def _parse_linear_instrument(
self.add(instrument=instrument)
except ValueError as e:
if self._log_warnings:
self._log.warning(f"Unable to parse instrument {data.symbol}, {e}")
self._log.warning(f"Unable to parse inverse instrument {data.symbol}, {e}")

def _parse_option_instrument(
self,
instrument: BybitInstrumentOption,
) -> None:
try:
pass
except ValueError as e:
if self._log_warnings:
self._log.warning(f"Unable to parse option instrument {instrument.symbol}, {e}")
Loading

0 comments on commit 1dd8135

Please sign in to comment.