From dd9cd2703dd7cc9a0c32a8c6f14c33d93c07689e Mon Sep 17 00:00:00 2001 From: sunlei Date: Sat, 14 Dec 2024 22:33:00 +0800 Subject: [PATCH 1/3] Refine `BybitEnumParser` for Bybit --- .../adapters/bybit/common/enums.py | 58 ++++++++---------- .../adapters/bybit/test_parsing.py | 61 +++++++++++++++++++ 2 files changed, 86 insertions(+), 33 deletions(-) diff --git a/nautilus_trader/adapters/bybit/common/enums.py b/nautilus_trader/adapters/bybit/common/enums.py index db6c7cddeb42..317d7c0c5436 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: @@ -537,10 +537,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 +548,31 @@ 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: + 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, + } + + 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, + } + 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 + return map_buy.get(order_type) 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 map_sell.get(order_type) def parse_bybit_kline(self, bar_type: BarType) -> BybitKlineInterval: try: @@ -593,7 +585,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 From ac55a2f1c58eb50bea024c0f06d08d705e1a1a61 Mon Sep 17 00:00:00 2001 From: sunlei Date: Sat, 14 Dec 2024 22:39:09 +0800 Subject: [PATCH 2/3] Refine `BybitEnumParser` for Bybit --- .../adapters/bybit/common/enums.py | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/nautilus_trader/adapters/bybit/common/enums.py b/nautilus_trader/adapters/bybit/common/enums.py index 317d7c0c5436..a0a4b881b685 100644 --- a/nautilus_trader/adapters/bybit/common/enums.py +++ b/nautilus_trader/adapters/bybit/common/enums.py @@ -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, @@ -553,26 +570,10 @@ def parse_trigger_direction( order_type: OrderType, order_side: OrderSide, ) -> BybitTriggerDirection | None: - 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, - } - - 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, - } - if order_side == OrderSide.BUY: - return map_buy.get(order_type) + return self.trigger_direction_map_buy.get(order_type) else: # SELL - return map_sell.get(order_type) + return self.trigger_direction_map_sell.get(order_type) def parse_bybit_kline(self, bar_type: BarType) -> BybitKlineInterval: try: From fa96783fc250af02a63be478d33f0d8c342ef17a Mon Sep 17 00:00:00 2001 From: sunlei Date: Sat, 14 Dec 2024 22:42:21 +0800 Subject: [PATCH 3/3] Refine `BybitEnumParser` for Bybit --- nautilus_trader/adapters/bybit/common/enums.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/nautilus_trader/adapters/bybit/common/enums.py b/nautilus_trader/adapters/bybit/common/enums.py index a0a4b881b685..b8fe4afb6a6d 100644 --- a/nautilus_trader/adapters/bybit/common/enums.py +++ b/nautilus_trader/adapters/bybit/common/enums.py @@ -570,10 +570,11 @@ def parse_trigger_direction( order_type: OrderType, order_side: OrderSide, ) -> BybitTriggerDirection | None: - if order_side == OrderSide.BUY: - return self.trigger_direction_map_buy.get(order_type) - else: # SELL - return self.trigger_direction_map_sell.get(order_type) + 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: