5454
5555import asyncio
5656import logging
57+ import warnings
5758from types import TracebackType
5859from typing import TYPE_CHECKING , Any , Union
5960
61+ from typing_extensions import deprecated
62+
6063from project_x_py .event_bus import EventType
6164from project_x_py .models import BracketOrderResponse , Order , OrderPlaceResponse
6265
@@ -127,15 +130,17 @@ async def _setup_event_handlers(self) -> None:
127130
128131 # Handler for order fills
129132 async def on_fill (data : dict [str , Any ]) -> None :
130- if data .get ("order_id" ) == self .order_id :
131- self ._filled_order = data .get ("order_data" )
133+ order = data .get ("order" )
134+ if order and order .id == self .order_id :
135+ self ._filled_order = order
132136 self ._current_status = 2 # FILLED
133137 self ._fill_event .set ()
134138
135139 # Handler for status changes
136140 async def on_status_change (data : dict [str , Any ]) -> None :
137- if data .get ("order_id" ) == self .order_id :
138- new_status = data .get ("new_status" )
141+ order = data .get ("order" )
142+ if order and order .id == self .order_id :
143+ new_status = order .status
139144 self ._current_status = new_status
140145
141146 # Set status-specific events
@@ -252,32 +257,38 @@ async def wait_for_status(self, status: int, timeout: float = 30.0) -> Order:
252257 if status not in self ._status_events :
253258 self ._status_events [status ] = asyncio .Event ()
254259
255- # Check if already at target status
260+ # Check current status before waiting
256261 if self ._current_status == status :
257262 order = await self .order_manager .get_order_by_id (self .order_id )
258- if order :
263+ if order and order . status == status :
259264 return order
260265
266+ # Wait for the event
261267 try :
262268 await asyncio .wait_for (self ._status_events [status ].wait (), timeout = timeout )
263-
264- if self ._error and status != self ._current_status :
265- raise self ._error
266-
267- # Fetch latest order data
269+ except TimeoutError :
270+ # After timeout, check the status one last time via API
268271 order = await self .order_manager .get_order_by_id (self .order_id )
269272 if order and order .status == status :
270273 return order
271- else :
272- raise OrderLifecycleError (
273- f"Status event received but order not in expected state { status } "
274- )
275-
276- except TimeoutError :
277274 raise TimeoutError (
278275 f"Order { self .order_id } did not reach status { status } within { timeout } seconds"
279276 ) from None
280277
278+ # After event is received
279+ if self ._error and status != self ._current_status :
280+ raise self ._error
281+
282+ order = await self .order_manager .get_order_by_id (self .order_id )
283+ if order and order .status == status :
284+ return order
285+ else :
286+ # This can happen if event fires but API state is not yet consistent,
287+ # or if another status update arrived quickly.
288+ raise OrderLifecycleError (
289+ f"Status event received but order not in expected state { status } . Current state: { order .status if order else 'not found' } "
290+ )
291+
281292 async def modify_or_cancel (
282293 self , new_price : float | None = None , new_size : int | None = None
283294 ) -> bool :
@@ -429,10 +440,9 @@ def with_take_profit(
429440 self ,
430441 offset : float | None = None ,
431442 price : float | None = None ,
432- limit : bool = True ,
433443 ) -> "OrderChainBuilder" :
434444 """Add a take profit to the order chain."""
435- self .take_profit = {"offset" : offset , "price" : price , "limit" : limit }
445+ self .take_profit = {"offset" : offset , "price" : price }
436446 return self
437447
438448 def with_trail_stop (
@@ -519,9 +529,33 @@ async def execute(self) -> BracketOrderResponse:
519529 )
520530
521531 # Add trailing stop if configured
522- if self .trail_stop and result .success and result .entry_order_id :
523- # TODO: Implement trailing stop order
524- logger .warning ("Trailing stop orders not yet implemented" )
532+ if self .trail_stop and result .success and result .stop_order_id :
533+ logger .info (
534+ f"Replacing stop order { result .stop_order_id } with trailing stop."
535+ )
536+ try :
537+ await self .order_manager .cancel_order (result .stop_order_id )
538+ trail_offset = self .trail_stop ["offset" ]
539+ stop_side = 1 if self .side == 0 else 0 # Opposite of entry
540+
541+ trail_response = await self .order_manager .place_trailing_stop_order (
542+ contract_id = contract_id ,
543+ side = stop_side ,
544+ size = self .size ,
545+ trail_price = trail_offset ,
546+ )
547+ if trail_response .success :
548+ logger .info (
549+ f"Trailing stop order placed: { trail_response .orderId } "
550+ )
551+ # Note: The BracketOrderResponse does not have a field for the trailing stop ID.
552+ # The original stop_order_id will remain in the response.
553+ else :
554+ logger .error (
555+ f"Failed to place trailing stop: { trail_response .errorMessage } "
556+ )
557+ except Exception as e :
558+ logger .error (f"Error replacing stop with trailing stop: { e } " )
525559
526560 return result
527561
@@ -571,6 +605,9 @@ class OrderLifecycleError(Exception):
571605
572606
573607# Convenience function for creating order trackers
608+ @deprecated (
609+ "Use TradingSuite.track_order() instead. This function will be removed in v4.0.0."
610+ )
574611def track_order (
575612 trading_suite : "TradingSuite" ,
576613 order : Union [Order , OrderPlaceResponse , int ] | None = None ,
@@ -593,6 +630,12 @@ def track_order(
593630 filled = await tracker.wait_for_fill()
594631 ```
595632 """
633+ warnings .warn (
634+ "track_order() is deprecated, use TradingSuite.track_order() instead. "
635+ "This function will be removed in v4.0.0" ,
636+ DeprecationWarning ,
637+ stacklevel = 2 ,
638+ )
596639 tracker = OrderTracker (trading_suite )
597640 if order :
598641 if isinstance (order , Order | OrderPlaceResponse ):
0 commit comments