diff --git a/nautilus_trader/adapters/dydx/config.py b/nautilus_trader/adapters/dydx/config.py index 1c4c96604fcd..fa2b21c00983 100644 --- a/nautilus_trader/adapters/dydx/config.py +++ b/nautilus_trader/adapters/dydx/config.py @@ -36,12 +36,16 @@ class DYDXDataClientConfig(LiveDataClientConfig, frozen=True): If the client is connecting to the dYdX testnet API. update_instruments_interval_mins: PositiveInt or None, default 60 The interval (minutes) between reloading instruments from the venue. + max_reconnection_tries: int, default 3 + The number of retries to reconnect the websocket connection if the + connection is broken. """ wallet_address: str | None = None is_testnet: bool = False update_instruments_interval_mins: PositiveInt | None = 60 + max_ws_reconnection_tries: int | None = 3 class DYDXExecClientConfig(LiveExecClientConfig, frozen=True): diff --git a/nautilus_trader/adapters/dydx/data.py b/nautilus_trader/adapters/dydx/data.py index 1a9217eb30f8..815097081515 100644 --- a/nautilus_trader/adapters/dydx/data.py +++ b/nautilus_trader/adapters/dydx/data.py @@ -133,6 +133,7 @@ def __init__( handler_reconnect=None, base_url=ws_base_url, loop=loop, + max_reconnection_tries=config.max_ws_reconnection_tries, ) # HTTP API diff --git a/nautilus_trader/adapters/dydx/execution.py b/nautilus_trader/adapters/dydx/execution.py index 3f7aef32d61f..44e0a4bf1e9c 100644 --- a/nautilus_trader/adapters/dydx/execution.py +++ b/nautilus_trader/adapters/dydx/execution.py @@ -974,7 +974,7 @@ async def _submit_order_list(self, command: SubmitOrderList) -> None: for order in command.order_list.orders: await self._submit_order_single(order=order) - async def _submit_order_single(self, order) -> None: + async def _submit_order_single(self, order: Order) -> None: """ Submit a single order. """ @@ -1057,6 +1057,8 @@ async def _submit_order_single(self, order) -> None: order_type_map = { OrderType.LIMIT: DYDXGRPCOrderType.LIMIT, OrderType.MARKET: DYDXGRPCOrderType.MARKET, + OrderType.STOP_MARKET: DYDXGRPCOrderType.STOP_MARKET, + OrderType.STOP_LIMIT: DYDXGRPCOrderType.STOP_LIMIT, } order_side_map = { OrderSide.NO_ORDER_SIDE: DYDXOrder.Side.SIDE_UNSPECIFIED, @@ -1071,11 +1073,18 @@ async def _submit_order_single(self, order) -> None: } price = 0 + trigger_price = None if order.order_type == OrderType.LIMIT: price = order.price.as_double() elif order.order_type == OrderType.MARKET: price = 0 + elif order.order_type == OrderType.STOP_LIMIT: + price = order.price.as_double() + trigger_price = order.trigger_price.as_double() + elif order.order_type == OrderType.STOP_MARKET: + price = 0 + trigger_price = order.trigger_price.as_double() else: rejection_reason = ( f"Cannot submit order: order type `{order.order_type}` not (yet) supported" @@ -1100,6 +1109,7 @@ async def _submit_order_single(self, order) -> None: post_only=order.is_post_only, good_til_block=good_til_block, good_til_block_time=good_til_date_secs, + trigger_price=trigger_price, ) await self._place_order(order_msg=order_msg, order=order) diff --git a/nautilus_trader/adapters/dydx/grpc/order_builder.py b/nautilus_trader/adapters/dydx/grpc/order_builder.py index 63bb53020bc8..54054969ae78 100644 --- a/nautilus_trader/adapters/dydx/grpc/order_builder.py +++ b/nautilus_trader/adapters/dydx/grpc/order_builder.py @@ -240,7 +240,7 @@ def create_order( good_til_block: int | None = None, good_til_block_time: int | None = None, execution: OrderExecution = OrderExecution.DEFAULT, - conditional_order_trigger_subticks: int = 0, + trigger_price: float | None = None, ) -> Order: """ Create a new Order instance. @@ -271,7 +271,9 @@ def create_order( not yet filled. execution : OrderExecution, default OrderExecution.DEFAULT OrderExecution enum: DEFAULT, IOC, FOK or POST_ONLY - conditional_order_trigger_subticks : int, default value is 0. + trigger_price : float, optional. + The price of the conditional limit order. Only applicable to STOP_LIMIT, + STOP_MARKET, TAKE_PROFIT_MARKET or TAKE_PROFIT_LIMIT orders. """ order_time_in_force = OrderHelper.calculate_time_in_force( @@ -282,6 +284,10 @@ def create_order( ) client_metadata = OrderHelper.calculate_client_metadata(order_type) condition_type = OrderHelper.calculate_condition_type(order_type) + conditional_order_trigger_subticks = 0 + + if trigger_price is not None: + conditional_order_trigger_subticks = self.calculate_subticks(trigger_price) return Order( order_id=order_id, diff --git a/nautilus_trader/adapters/dydx/websocket/client.py b/nautilus_trader/adapters/dydx/websocket/client.py index 15f293efe38c..3fd82886e0b9 100644 --- a/nautilus_trader/adapters/dydx/websocket/client.py +++ b/nautilus_trader/adapters/dydx/websocket/client.py @@ -52,6 +52,9 @@ class DYDXWebsocketClient: The event loop for the client. subscription_rate_limit_per_second : int, default 2 The maximum number of subscription message to send to the venue. + max_reconnection_tries: int, default 3 + The number of retries to reconnect the websocket connection if the + connection is broken. """ @@ -63,6 +66,7 @@ def __init__( handler_reconnect: Callable[..., Awaitable[None]] | None, loop: asyncio.AbstractEventLoop, subscription_rate_limit_per_second: int = 2, + max_reconnection_tries: int | None = 3, ) -> None: """ Provide a dYdX streaming WebSocket client. @@ -77,6 +81,7 @@ def __init__( self._is_running = False self._subscriptions: set[tuple[str, str]] = set() self._subscription_rate_limit_per_second = subscription_rate_limit_per_second + self._max_reconnection_tries = max_reconnection_tries self._msg_timestamp = self._clock.utc_now() self._msg_timeout_secs: int = 60 self._reconnect_task: asyncio.Task | None = None @@ -145,6 +150,7 @@ async def connect(self) -> None: heartbeat=10, headers=[], ping_handler=self._handle_ping, + max_reconnection_tries=self._max_reconnection_tries, ) client = await WebSocketClient.connect( config=config, diff --git a/nautilus_trader/core/nautilus_pyo3.pyi b/nautilus_trader/core/nautilus_pyo3.pyi index 5e896dc92e29..473ea6fac5ab 100644 --- a/nautilus_trader/core/nautilus_pyo3.pyi +++ b/nautilus_trader/core/nautilus_pyo3.pyi @@ -2770,6 +2770,7 @@ class WebSocketConfig: heartbeat: int | None = None, heartbeat_msg: str | None = None, ping_handler: Callable[..., Any] | None = None, + max_reconnection_tries: int | None = None, ) -> None: ... class WebSocketClient: