diff --git a/nautilus_trader/adapters/bybit/common/enums.py b/nautilus_trader/adapters/bybit/common/enums.py index db6c7cddeb42..b8fe4afb6a6d 100644 --- a/nautilus_trader/adapters/bybit/common/enums.py +++ b/nautilus_trader/adapters/bybit/common/enums.py @@ -245,10 +245,10 @@ class BybitEndpointType(Enum): def check_dict_keys(key, data): try: return data[key] - except KeyError: + except KeyError as exec: raise RuntimeError( f"Unrecognized Bybit {key} not found in {data}", - ) + ) from exec class BybitEnumParser: @@ -506,6 +506,23 @@ def __init__(self) -> None: TimeInForce.FOK, } + # trigger direction + self.trigger_direction_map_buy = { + OrderType.STOP_MARKET: BybitTriggerDirection.RISES_TO, + OrderType.STOP_LIMIT: BybitTriggerDirection.RISES_TO, + OrderType.MARKET_IF_TOUCHED: BybitTriggerDirection.RISES_TO, + OrderType.TRAILING_STOP_MARKET: BybitTriggerDirection.RISES_TO, + OrderType.LIMIT_IF_TOUCHED: BybitTriggerDirection.FALLS_TO, + } + + self.trigger_direction_map_sell = { + OrderType.STOP_MARKET: BybitTriggerDirection.FALLS_TO, + OrderType.STOP_LIMIT: BybitTriggerDirection.FALLS_TO, + OrderType.MARKET_IF_TOUCHED: BybitTriggerDirection.FALLS_TO, + OrderType.TRAILING_STOP_MARKET: BybitTriggerDirection.FALLS_TO, + OrderType.LIMIT_IF_TOUCHED: BybitTriggerDirection.RISES_TO, + } + def parse_bybit_order_status( self, order_type: OrderType, @@ -537,10 +554,10 @@ def parse_bybit_order_type( def parse_nautilus_time_in_force(self, time_in_force: TimeInForce) -> BybitTimeInForce: try: return self.nautilus_to_bybit_time_in_force[time_in_force] - except KeyError: + except KeyError as exec: raise RuntimeError( f"unrecognized Bybit time in force, was {time_in_force_to_str(time_in_force)}", # pragma: no cover - ) + ) from exec def parse_nautilus_trigger_type(self, trigger_type: TriggerType) -> BybitTriggerType: return check_dict_keys(trigger_type, self.nautilus_to_bybit_trigger_type) @@ -548,39 +565,16 @@ def parse_nautilus_trigger_type(self, trigger_type: TriggerType) -> BybitTrigger def parse_bybit_trigger_type(self, trigger_type: BybitTriggerType) -> TriggerType: return check_dict_keys(trigger_type, self.bybit_to_nautilus_trigger_type) - def parse_trigger_direction( # noqa: C901 (too complex) + def parse_trigger_direction( self, order_type: OrderType, order_side: OrderSide, ) -> BybitTriggerDirection | None: - if order_side == OrderSide.BUY: - match order_type: - case OrderType.STOP_MARKET: - return BybitTriggerDirection.RISES_TO - case OrderType.STOP_LIMIT: - return BybitTriggerDirection.RISES_TO - case OrderType.MARKET_IF_TOUCHED: - return BybitTriggerDirection.RISES_TO - case OrderType.TRAILING_STOP_MARKET: - return BybitTriggerDirection.RISES_TO - case OrderType.LIMIT_IF_TOUCHED: - return BybitTriggerDirection.FALLS_TO - case _: - return None - else: # SELL - match order_type: - case OrderType.STOP_MARKET: - return BybitTriggerDirection.FALLS_TO - case OrderType.STOP_LIMIT: - return BybitTriggerDirection.FALLS_TO - case OrderType.MARKET_IF_TOUCHED: - return BybitTriggerDirection.FALLS_TO - case OrderType.TRAILING_STOP_MARKET: - return BybitTriggerDirection.FALLS_TO - case OrderType.LIMIT_IF_TOUCHED: - return BybitTriggerDirection.RISES_TO - case _: - return None + return ( + self.trigger_direction_map_buy.get(order_type) + if order_side == OrderSide.BUY + else self.trigger_direction_map_sell.get(order_type) + ) def parse_bybit_kline(self, bar_type: BarType) -> BybitKlineInterval: try: @@ -593,7 +587,7 @@ def parse_bybit_kline(self, bar_type: BarType) -> BybitKlineInterval: raise ValueError( f"Bybit incorrect aggregation {aggregation}", # pragma: no cover ) - except KeyError: + except KeyError as exec: raise RuntimeError( f"unrecognized Bybit bar type, was {bar_type}", # pragma: no cover - ) + ) from exec diff --git a/tests/integration_tests/adapters/bybit/test_parsing.py b/tests/integration_tests/adapters/bybit/test_parsing.py index 6d1fc2c8cca2..29c978b0f37e 100644 --- a/tests/integration_tests/adapters/bybit/test_parsing.py +++ b/tests/integration_tests/adapters/bybit/test_parsing.py @@ -17,8 +17,10 @@ from nautilus_trader.adapters.bybit.common.enums import BybitEnumParser from nautilus_trader.adapters.bybit.common.enums import BybitOrderSide +from nautilus_trader.adapters.bybit.common.enums import BybitTriggerDirection from nautilus_trader.model.data import BarType from nautilus_trader.model.enums import OrderSide +from nautilus_trader.model.enums import OrderType from nautilus_trader.test_kit.providers import TestInstrumentProvider @@ -145,3 +147,62 @@ def test_parse_nautilus_order_side( # ) -> None: # result = self._enum_parser.parse_nautilus_order_status(order_status) # assert result == bybit_order_status + + @pytest.mark.parametrize( + ("order_type", "expected_direction_buy"), + [ + (OrderType.STOP_MARKET, BybitTriggerDirection.RISES_TO), + (OrderType.STOP_LIMIT, BybitTriggerDirection.RISES_TO), + (OrderType.MARKET_IF_TOUCHED, BybitTriggerDirection.RISES_TO), + (OrderType.TRAILING_STOP_MARKET, BybitTriggerDirection.RISES_TO), + (OrderType.LIMIT_IF_TOUCHED, BybitTriggerDirection.FALLS_TO), + (OrderType.MARKET, None), + (OrderType.LIMIT, None), + ], + ) + def test_parse_trigger_direction_buy_orders(self, order_type, expected_direction_buy): + # Arrange & Act + result = self._enum_parser.parse_trigger_direction( + order_type=order_type, + order_side=OrderSide.BUY, + ) + + # Assert + assert result == expected_direction_buy + + @pytest.mark.parametrize( + ("order_type", "expected_direction_sell"), + [ + (OrderType.STOP_MARKET, BybitTriggerDirection.FALLS_TO), + (OrderType.STOP_LIMIT, BybitTriggerDirection.FALLS_TO), + (OrderType.MARKET_IF_TOUCHED, BybitTriggerDirection.FALLS_TO), + (OrderType.TRAILING_STOP_MARKET, BybitTriggerDirection.FALLS_TO), + (OrderType.LIMIT_IF_TOUCHED, BybitTriggerDirection.RISES_TO), + (OrderType.MARKET, None), + (OrderType.LIMIT, None), + ], + ) + def test_parse_trigger_direction_sell_orders(self, order_type, expected_direction_sell): + # Arrange & Act + result = self._enum_parser.parse_trigger_direction( + order_type=order_type, + order_side=OrderSide.SELL, + ) + + # Assert + assert result == expected_direction_sell + + def test_parse_trigger_direction_unsupported_order_types(self): + # Arrange & Act + result_buy = self._enum_parser.parse_trigger_direction( + order_type=OrderType.MARKET, + order_side=OrderSide.BUY, + ) + result_sell = self._enum_parser.parse_trigger_direction( + order_type=OrderType.MARKET, + order_side=OrderSide.SELL, + ) + + # Assert + assert result_buy is None + assert result_sell is None