Skip to content

Commit 2bf4266

Browse files
TexasCodingclaude
andcommitted
fix(events): Fix order event data structure mismatches across event handlers
Fixed critical issues where event handlers were expecting different data structures than what was being emitted, causing order fill detection to fail: OrderManager tracking.py: - Added order_id directly to event payload for backward compatibility - Modified fill_handler and terminal_handler to check both event_data.get("order_id") and event_data.get("order").id to handle different event structures - Ensures bracket orders and other wait_for_fill patterns work correctly RiskManager managed_trade.py: - Changed from listening to ORDER_MODIFIED to ORDER_FILLED/CANCELLED/REJECTED - ORDER_MODIFIED is for parameter changes (stop loss, limit price modifications) - ORDER_FILLED/terminal events are for status changes (fills, cancellations) - Updated handlers to use same pattern as OrderManager for consistency This fixes the bracket order fill detection timeout issue where orders were actually filling but the event wasn't being properly detected. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 732e8e9 commit 2bf4266

File tree

2 files changed

+55
-19
lines changed

2 files changed

+55
-19
lines changed

src/project_x_py/order_manager/tracking.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ async def _on_order_update(self, order_data: dict[str, Any] | list[Any]) -> None
188188
order_obj = Order(**actual_order_data)
189189
event_payload = {
190190
"order": order_obj,
191+
"order_id": order_id, # Add order_id for compatibility
191192
"old_status": old_status,
192193
"new_status": new_status,
193194
}
@@ -398,17 +399,31 @@ async def fill_handler(event: Any) -> None:
398399
nonlocal is_filled
399400
# Extract data from Event object
400401
event_data = event.data if hasattr(event, "data") else event
401-
if isinstance(event_data, dict) and event_data.get("order_id") == order_id:
402-
is_filled = True
403-
fill_event.set()
402+
if isinstance(event_data, dict):
403+
# Check both direct order_id and order.id from Order object
404+
event_order_id = event_data.get("order_id")
405+
if not event_order_id and "order" in event_data:
406+
order_obj = event_data.get("order")
407+
if hasattr(order_obj, "id"):
408+
event_order_id = order_obj.id
409+
if event_order_id == order_id:
410+
is_filled = True
411+
fill_event.set()
404412

405413
async def terminal_handler(event: Any) -> None:
406414
nonlocal is_filled
407415
# Extract data from Event object
408416
event_data = event.data if hasattr(event, "data") else event
409-
if isinstance(event_data, dict) and event_data.get("order_id") == order_id:
410-
is_filled = False
411-
fill_event.set()
417+
if isinstance(event_data, dict):
418+
# Check both direct order_id and order.id from Order object
419+
event_order_id = event_data.get("order_id")
420+
if not event_order_id and "order" in event_data:
421+
order_obj = event_data.get("order")
422+
if hasattr(order_obj, "id"):
423+
event_order_id = order_obj.id
424+
if event_order_id == order_id:
425+
is_filled = False
426+
fill_event.set()
412427

413428
from project_x_py.event_bus import EventType
414429

src/project_x_py/risk_manager/managed_trade.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -633,35 +633,56 @@ async def _wait_for_order_fill(
633633
fill_event = asyncio.Event()
634634
filled_successfully = False
635635

636-
async def order_update_handler(event_data: dict[str, Any]) -> None:
636+
async def order_fill_handler(event: Any) -> None:
637637
nonlocal filled_successfully
638-
# The event data for order_update is the Order object itself
639-
updated_order: Order = event_data.get("data", {})
640-
if updated_order and updated_order.id == order.id:
641-
if updated_order.is_filled:
638+
# Extract data from Event object
639+
event_data = event.data if hasattr(event, "data") else event
640+
if isinstance(event_data, dict):
641+
# Check both direct order_id and order.id from Order object
642+
event_order_id = event_data.get("order_id")
643+
if not event_order_id and "order" in event_data:
644+
order_obj = event_data.get("order")
645+
if hasattr(order_obj, "id"):
646+
event_order_id = order_obj.id
647+
if event_order_id == order.id:
642648
filled_successfully = True
643649
fill_event.set()
644-
elif updated_order.is_terminal:
650+
651+
async def order_terminal_handler(event: Any) -> None:
652+
nonlocal filled_successfully
653+
# Extract data from Event object
654+
event_data = event.data if hasattr(event, "data") else event
655+
if isinstance(event_data, dict):
656+
# Check both direct order_id and order.id from Order object
657+
event_order_id = event_data.get("order_id")
658+
if not event_order_id and "order" in event_data:
659+
order_obj = event_data.get("order")
660+
if hasattr(order_obj, "id"):
661+
event_order_id = order_obj.id
662+
if event_order_id == order.id:
645663
filled_successfully = False
646664
fill_event.set()
647665

648-
await self.event_bus.on(EventType.ORDER_MODIFIED, order_update_handler)
666+
await self.event_bus.on(EventType.ORDER_FILLED, order_fill_handler)
667+
await self.event_bus.on(EventType.ORDER_CANCELLED, order_terminal_handler)
668+
await self.event_bus.on(EventType.ORDER_REJECTED, order_terminal_handler)
649669

650670
try:
651671
await asyncio.wait_for(fill_event.wait(), timeout=timeout_seconds)
652672
except TimeoutError:
653673
logger.warning(f"Timeout waiting for order {order.id} to fill via event.")
654674
filled_successfully = False
655675
finally:
656-
# Important: Clean up the event handler to prevent memory leaks
657-
# Assuming a `remove_callback` method exists on the event bus
676+
# Important: Clean up the event handlers to prevent memory leaks
658677
if hasattr(self.event_bus, "remove_callback"):
659678
await self.event_bus.remove_callback(
660-
EventType.ORDER_MODIFIED, order_update_handler
679+
EventType.ORDER_FILLED, order_fill_handler
661680
)
662-
elif hasattr(self.event_bus, "remove_listener"):
663-
await self.event_bus.remove_listener(
664-
EventType.ORDER_MODIFIED, order_update_handler
681+
await self.event_bus.remove_callback(
682+
EventType.ORDER_CANCELLED, order_terminal_handler
683+
)
684+
await self.event_bus.remove_callback(
685+
EventType.ORDER_REJECTED, order_terminal_handler
665686
)
666687

667688
return filled_successfully

0 commit comments

Comments
 (0)