diff --git a/Trading/Exchange/bybit/bybit_exchange.py b/Trading/Exchange/bybit/bybit_exchange.py index 27eb4c97a..e1cabbcba 100644 --- a/Trading/Exchange/bybit/bybit_exchange.py +++ b/Trading/Exchange/bybit/bybit_exchange.py @@ -28,23 +28,29 @@ class Bybit(exchanges.SpotCCXTExchange, exchanges.FutureCCXTExchange): BUY_STR = "Buy" SELL_STR = "Sell" + LONG_STR = BUY_STR + SHORT_STR = SELL_STR + MARK_PRICE_IN_TICKER = True FUNDING_IN_TICKER = True - # Spot keys - BYBIT_KLINE_TIMESTAMP = "open_time" - - BYBIT_SYMBOL = "symbol" - # Position BYBIT_SIZE = "size" BYBIT_VALUE = "position_value" - BYBIT_MARGIN = "position_margin" + BYBIT_LEVERAGE = "leverage" + BYBIT_INITIAL_MARGIN = "position_margin" BYBIT_STATUS = "position_status" BYBIT_LIQUIDATION_PRICE = "liq_price" + BYBIT_BANKRUPTCY_PRICE = "bust_price" + BYBIT_CLOSING_FEE = "occ_closing_fee" + BYBIT_MODE = "mode" BYBIT_TIMESTAMP = "created_at" BYBIT_IS_ISOLATED = "is_isolated" + BYBIT_UNREALISED_PNL = "unrealised_pnl" BYBIT_REALIZED_PNL = "cum_realised_pnl" + BYBIT_ONE_WAY = "MergedSingle" + BYBIT_HEDGE = "BothSide" + BYBIT_ENTRY_PRICE = "entry_price" # Funding BYBIT_FUNDING_TIMESTAMP = "funding_rate_timestamp" @@ -65,78 +71,58 @@ async def get_symbol_prices(self, symbol, time_frame, limit: int = 200, **kwargs except Exception as e: raise octobot_trading.errors.FailedRequest(f"Failed to get_symbol_prices {e}") - async def initialize_impl(self): - await super().initialize_impl() + def get_default_type(self): + return 'linear' - # temporary patch defaultMarketType to linear - self.connector.client.options['defaultType'] = 'linear' - - async def get_open_positions(self) -> list: + async def get_positions(self) -> list: return self.parse_positions(await self.connector.client.fetch_positions()) - # async def get_kline_price(self, symbol, time_frame): - # try: - # await self.connector.client.public_get_kline_list( - # { - # "symbol": self.get_exchange_pair(symbol), - # "interval": "", - # "from": "", - # "limit": 1 - # }) - # except BaseError as e: - # self.logger.error(f"Failed to get_kline_price {e}") - # return None - def parse_positions(self, positions) -> list: """ CCXT is returning the position dict as {'data': {position data dict}} """ - return [self.parse_position(position.get('data')) for position in positions] + return [self.parse_position(position.get('data')) for position in positions] if positions else [] def parse_position(self, position_dict) -> dict: try: + size = decimal.Decimal(position_dict.get(self.BYBIT_SIZE, 0)) + if size == constants.ZERO: + return {} # Don't parse empty position + symbol = self.get_pair_from_exchange( - position_dict[trading_enums.ExchangeConstantsPositionColumns.SYMBOL.value]) + position_dict[trading_enums.ExchangeConstantsPositionColumns.SYMBOL.value]) + side = self.parse_position_side(position_dict.get(trading_enums.ExchangePositionCCXTColumns.SIDE.value)) return { trading_enums.ExchangeConstantsPositionColumns.SYMBOL.value: symbol, trading_enums.ExchangeConstantsPositionColumns.TIMESTAMP.value: self.parse_timestamp(position_dict, self.BYBIT_TIMESTAMP), - trading_enums.ExchangeConstantsPositionColumns.SIDE.value: - self.parse_position_side( - position_dict.get(trading_enums.ExchangePositionCCXTColumns.SIDE.value, - trading_enums.PositionSide.UNKNOWN.value)), + trading_enums.ExchangeConstantsPositionColumns.SIDE.value: side, trading_enums.ExchangeConstantsPositionColumns.MARGIN_TYPE.value: - self._parse_position_margin_type( - position_dict.get(self.BYBIT_IS_ISOLATED, True)), - trading_enums.ExchangeConstantsPositionColumns.QUANTITY.value: - decimal.Decimal(self.connector.client.parse_number( - position_dict.get(self.BYBIT_SIZE, 0))), - trading_enums.ExchangeConstantsPositionColumns.COLLATERAL.value: - decimal.Decimal(self.connector.client.parse_number( - position_dict.get(trading_enums.ExchangeConstantsPositionColumns.COLLATERAL.value, 0))), + self._parse_position_margin_type(position_dict.get(self.BYBIT_IS_ISOLATED, True)), + trading_enums.ExchangeConstantsPositionColumns.SIZE.value: + size if side is trading_enums.PositionSide.LONG else -size, + trading_enums.ExchangeConstantsPositionColumns.INITIAL_MARGIN.value: + decimal.Decimal(position_dict.get(self.BYBIT_INITIAL_MARGIN, 0)), trading_enums.ExchangeConstantsPositionColumns.NOTIONAL.value: - decimal.Decimal(self.connector.client.parse_number( - position_dict.get(self.BYBIT_VALUE, 0))), + decimal.Decimal(position_dict.get(self.BYBIT_VALUE, 0)), trading_enums.ExchangeConstantsPositionColumns.LEVERAGE.value: - decimal.Decimal(self.connector.client.parse_number( - position_dict.get(trading_enums.ExchangeConstantsPositionColumns.LEVERAGE.value, 0))), + decimal.Decimal(position_dict.get(self.BYBIT_LEVERAGE, 0)), trading_enums.ExchangeConstantsPositionColumns.UNREALISED_PNL.value: - decimal.Decimal(self.connector.client.parse_number( - position_dict.get(trading_enums.ExchangeConstantsPositionColumns.UNREALISED_PNL.value, 0))), + decimal.Decimal(position_dict.get(self.BYBIT_UNREALISED_PNL, 0)), trading_enums.ExchangeConstantsPositionColumns.REALISED_PNL.value: - decimal.Decimal(self.connector.client.parse_number( - position_dict.get(self.BYBIT_REALIZED_PNL, 0))), + decimal.Decimal(position_dict.get(self.BYBIT_REALIZED_PNL, 0)), trading_enums.ExchangeConstantsPositionColumns.LIQUIDATION_PRICE.value: - decimal.Decimal(self.connector.client.parse_number( - position_dict.get(self.BYBIT_LIQUIDATION_PRICE, 0))), - trading_enums.ExchangeConstantsPositionColumns.MARK_PRICE.value: - decimal.Decimal(self.connector.client.parse_number( - position_dict.get(trading_enums.ExchangeConstantsPositionColumns.MARK_PRICE.value, 0))), + decimal.Decimal(position_dict.get(self.BYBIT_LIQUIDATION_PRICE, 0)), + trading_enums.ExchangeConstantsPositionColumns.CLOSING_FEE.value: + decimal.Decimal(position_dict.get(self.BYBIT_CLOSING_FEE, 0)), + trading_enums.ExchangeConstantsPositionColumns.BANKRUPTCY_PRICE.value: + decimal.Decimal(position_dict.get(self.BYBIT_BANKRUPTCY_PRICE, 0)), trading_enums.ExchangeConstantsPositionColumns.ENTRY_PRICE.value: - decimal.Decimal(self.connector.client.parse_number( - position_dict.get(trading_enums.ExchangeConstantsPositionColumns.ENTRY_PRICE.value, 0))), + decimal.Decimal(position_dict.get(self.BYBIT_ENTRY_PRICE, 0)), trading_enums.ExchangeConstantsPositionColumns.CONTRACT_TYPE.value: self._parse_position_contract_type(symbol), + trading_enums.ExchangeConstantsPositionColumns.POSITION_MODE.value: + self._parse_position_mode(position_dict.get(self.BYBIT_MODE)), } except KeyError as e: self.logger.error(f"Fail to parse position dict ({e})") @@ -157,8 +143,8 @@ def parse_funding(self, funding_dict, from_ticker=False): funding_dict.update({ trading_enums.ExchangeConstantsFundingColumns.LAST_FUNDING_TIME.value: funding_next_timestamp - self.BYBIT_DEFAULT_FUNDING_TIME, - trading_enums.ExchangeConstantsFundingColumns.FUNDING_RATE.value: self.connector.client.safe_float( - funding_dict, trading_enums.ExchangeConstantsFundingColumns.FUNDING_RATE.value), + trading_enums.ExchangeConstantsFundingColumns.FUNDING_RATE.value: decimal.Decimal( + funding_dict.get(trading_enums.ExchangeConstantsFundingColumns.FUNDING_RATE.value, 0)), trading_enums.ExchangeConstantsFundingColumns.NEXT_FUNDING_TIME.value: funding_next_timestamp }) except KeyError as e: @@ -172,9 +158,8 @@ def parse_mark_price(self, mark_price_dict, from_ticker=False) -> dict: try: mark_price_dict = { trading_enums.ExchangeConstantsMarkPriceColumns.MARK_PRICE.value: - self.connector.client.safe_float(mark_price_dict, - trading_enums.ExchangeConstantsMarkPriceColumns.MARK_PRICE.value, - 0) + decimal.Decimal(mark_price_dict.get( + trading_enums.ExchangeConstantsMarkPriceColumns.MARK_PRICE.value, 0)) } except KeyError as e: self.logger.error(f"Fail to parse mark price dict ({e})") @@ -190,12 +175,28 @@ def parse_position_status(self, status): return trading_enums.PositionStatus(self.connector.client.safe_string(statuses, status, status)) def _parse_position_margin_type(self, position_is_isolated): - return trading_enums.TraderPositionType.ISOLATED.value \ - if position_is_isolated else trading_enums.TraderPositionType.CROSS.value + return trading_enums.TraderPositionType.ISOLATED \ + if position_is_isolated else trading_enums.TraderPositionType.CROSS def _parse_position_contract_type(self, position_pair): - if self.is_linear_pair(position_pair): + if self.is_linear_symbol(position_pair): return trading_enums.FutureContractType.LINEAR_PERPETUAL - if self.is_inverse_pair(position_pair): + if self.is_inverse_symbol(position_pair): return trading_enums.FutureContractType.INVERSE_PERPETUAL return None + + def _parse_position_mode(self, raw_mode): + if raw_mode == self.BYBIT_ONE_WAY: + return trading_enums.PositionMode.ONE_WAY + if raw_mode == self.BYBIT_HEDGE: + return trading_enums.PositionMode.HEDGE + return None + + def is_linear_symbol(self, symbol): + return self._get_pair_market_type(symbol) == 'linear' + + def is_inverse_symbol(self, symbol): + return self._get_pair_market_type(symbol) == 'inverse' + + def is_futures_symbol(self, symbol): + return self._get_pair_market_type(symbol) == 'futures'