Skip to content

Add initial leverage & margin_mode & position_mode config for Bybit #2441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions nautilus_trader/adapters/bybit/common/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ class BybitMarginMode(Enum):
PORTFOLIO_MARGIN = "PORTFOLIO_MARGIN"


@unique
class BybitPositionMode(Enum):
"""
https://bybit-exchange.github.io/docs/v5/position/position-mode
"""

MERGED_SINGLE = 0
BOTH_SIDES = 3


@unique
class BybitPositionIdx(Enum):
# One-way mode position
Expand Down
12 changes: 12 additions & 0 deletions nautilus_trader/adapters/bybit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@


if TYPE_CHECKING:
from nautilus_trader.adapters.bybit.common.enums import BybitMarginMode
from nautilus_trader.adapters.bybit.common.enums import BybitPositionMode
from nautilus_trader.adapters.bybit.common.enums import BybitProductType
from nautilus_trader.adapters.bybit.common.symbol import BybitSymbol


class BybitDataClientConfig(LiveDataClientConfig, frozen=True):
Expand Down Expand Up @@ -110,6 +113,12 @@ class BybitExecClientConfig(LiveExecClientConfig, frozen=True):
The receive window (milliseconds) for Bybit HTTP requests.
ws_trade_timeout_secs : float, default 5.0
The timeout for trade websocket messages.
futures_leverages : dict[BybitSymbol, PositiveInt], optional
The leverages for futures.
position_mode : BybitPositionMode, optional
The position mode for `USDT perpetual` and `Inverse futures`.
margin_mode : BybitMarginMode, optional
Set Margin Mode.

Warnings
--------
Expand All @@ -133,3 +142,6 @@ class BybitExecClientConfig(LiveExecClientConfig, frozen=True):
retry_delay: PositiveFloat | None = None
recv_window_ms: PositiveInt = 5_000
ws_trade_timeout_secs: float | None = 5.0
futures_leverages: dict[BybitSymbol, PositiveInt] | None = None
position_mode: dict[BybitSymbol, BybitPositionMode] | None = None
margin_mode: BybitMarginMode | None = None
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@


if TYPE_CHECKING:
from nautilus_trader.adapters.bybit.common.enums import BybitPositionMode
from nautilus_trader.adapters.bybit.http.client import BybitHttpClient


class BybitSwitchModePostParams(msgspec.Struct, omit_defaults=True, frozen=True, kw_only=True):
category: BybitProductType
symbol: str | None = None
coin: str | None = None
mode: int
mode: BybitPositionMode


class BybitSwitchModeEndpoint(BybitHttpEndpoint):
Expand Down
75 changes: 75 additions & 0 deletions nautilus_trader/adapters/bybit/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from __future__ import annotations

from asyncio import TaskGroup
from typing import TYPE_CHECKING

import msgspec
Expand Down Expand Up @@ -83,6 +84,7 @@
if TYPE_CHECKING:
import asyncio

from nautilus_trader.adapters.bybit.common.enums import BybitPositionMode
from nautilus_trader.adapters.bybit.config import BybitExecClientConfig
from nautilus_trader.adapters.bybit.http.client import BybitHttpClient
from nautilus_trader.adapters.bybit.providers import BybitInstrumentProvider
Expand Down Expand Up @@ -177,6 +179,10 @@ def __init__(
self._use_ws_execution_fast = config.use_ws_execution_fast
self._use_http_batch_api = config.use_http_batch_api

self._futures_leverages = config.futures_leverages
self._margin_mode = config.margin_mode
self._position_mode = config.position_mode

self._log.info(f"Account type: {account_type_to_str(account_type)}", LogColor.BLUE)
self._log.info(f"Product types: {[p.value for p in product_types]}", LogColor.BLUE)
self._log.info(f"{config.use_gtd=}", LogColor.BLUE)
Expand All @@ -187,6 +193,9 @@ def __init__(
self._log.info(f"{config.retry_delay=}", LogColor.BLUE)
self._log.info(f"{config.recv_window_ms=:_}", LogColor.BLUE)
self._log.info(f"{config.ws_trade_timeout_secs=}", LogColor.BLUE)
self._log.info(f"{config.futures_leverages=}", LogColor.BLUE)
self._log.info(f"{config.margin_mode=}", LogColor.BLUE)
self._log.info(f"{config.position_mode=}", LogColor.BLUE)

self._enum_parser = BybitEnumParser()

Expand Down Expand Up @@ -580,6 +589,72 @@ async def _update_account_state(self) -> None:
except Exception as e:
self._log.error(f"Failed to generate AccountState: {e}")

# Set Leverages
if self._futures_leverages:
async with TaskGroup() as tg:
[
tg.create_task(self.set_leverage(symbol=symbol, leverage=leverage))
for symbol, leverage in self._futures_leverages.items()
if symbol.is_linear or symbol.is_inverse
]

# Set Position Mode
if self._position_mode:
async with TaskGroup() as tg:
[
tg.create_task(self.set_position_mode(symbol=symbol, mode=mode))
for symbol, mode in self._position_mode.items()
if symbol.is_linear
]

# Set Margin Mode
if self._margin_mode:
res_set_margin_mode = await self._http_account.set_margin_mode(self._margin_mode)
self._log.info(f"Set account margin mode result: {res_set_margin_mode.retMsg}")

async def set_leverage(
self,
symbol: BybitSymbol,
leverage: int,
) -> None:
try:
res = await self._http_account.set_leverage(
category=symbol.product_type,
symbol=symbol.raw_symbol,
buy_leverage=str(leverage),
sell_leverage=str(leverage),
)
self._log.info(f"Set symbol `{symbol}` leverage to `{leverage}` result: {res.retMsg}")
except BybitError as e:
if e.code == 110043: # Set leverage has not been modified. (already set)
self._log.info(
f"Set symbol `{symbol}` leverage to `{leverage}` result: {e.message}",
)
return

raise e

async def set_position_mode(
self,
symbol: BybitSymbol,
mode: BybitPositionMode,
) -> None:
try:
res = await self._http_account.switch_mode(
category=symbol.product_type,
symbol=symbol.raw_symbol,
mode=mode,
)
self._log.info(f"Set symbol `{symbol}` position mode to `{mode}` result: {res.retMsg}")
except BybitError as e: # Position mode has not been modified. (already set)
if e.code == 110025:
self._log.info(
f"Set symbol `{symbol}` position mode to `{mode}` result: {e.message}",
)
return

raise e

# -- COMMAND HANDLERS -------------------------------------------------------------------------

async def _cancel_order(self, command: CancelOrder) -> None:
Expand Down
3 changes: 2 additions & 1 deletion nautilus_trader/adapters/bybit/http/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@

if TYPE_CHECKING:
from nautilus_trader.adapters.bybit.common.enums import BybitMarginMode
from nautilus_trader.adapters.bybit.common.enums import BybitPositionMode
from nautilus_trader.adapters.bybit.http.client import BybitHttpClient
from nautilus_trader.adapters.bybit.schemas.account.balance import BybitWalletBalance
from nautilus_trader.adapters.bybit.schemas.account.fee_rate import BybitFeeRate
Expand Down Expand Up @@ -155,7 +156,7 @@ async def set_leverage(
async def switch_mode(
self,
category: BybitProductType,
mode: int,
mode: BybitPositionMode,
symbol: str | None = None,
coin: str | None = None,
) -> BybitSwitchModeResponse:
Expand Down
Loading