🚀 AI One-Click Generation of quantconnect Strategy Code

How to Effectively Handle OrderStatus in the `on_order_event` Handler in QuantConnect

quantconnect | Published on: 2025/12/5
The following content is generated by EasyQuant .

Problem Description

How do I write code that reacts differently if an order is filled, cancelled, or just submitted?

Solution

To handle different order states in QuantConnect, you utilize the on_order_event event handler. This method is automatically triggered whenever an order's status changes (e.g., when it is submitted, partially filled, filled, or cancelled).

Inside this method, you inspect the OrderEvent object to determine the status and execute specific logic accordingly.

Key Concepts

  1. on_order_event(self, order_event): The specific method to override in your algorithm.
  2. order_event.status: An enum (OrderStatus) representing the current state. Common states include OrderStatus.SUBMITTED, OrderStatus.FILLED, OrderStatus.PARTIALLY_FILLED, and OrderStatus.CANCELLED.
  3. order_event.order_id: Used to retrieve the full order object if you need details not present in the event (like the order type or tag).

Strategy Code

The following example demonstrates how to implement specific logic for Submitted, Filled, and Cancelled orders.

# region imports
from AlgorithmImports import *
# endregion

class OrderStatusReactionAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 1, 1)
        self.set_end_date(2023, 1, 10)
        self.set_cash(100000)
        
        self.symbol = self.add_equity("SPY", Resolution.DAILY).symbol
        self.limit_order_ticket = None

    def on_data(self, data: Slice):
        # Simple logic to trigger different order events for demonstration
        if not self.portfolio.invested:
            # 1. Place a Market Order (Will go Submitted -> Filled)
            self.market_order(self.symbol, 10, tag="Market Entry")

            # 2. Place a Limit Order far below price (Will go Submitted)
            # We save the ticket to cancel it later
            current_price = data[self.symbol].close
            self.limit_order_ticket = self.limit_order(self.symbol, 10, current_price * 0.5, tag="Deep Limit")

        # 3. Cancel the limit order to trigger the Cancelled event
        if self.limit_order_ticket is not None and self.limit_order_ticket.status == OrderStatus.SUBMITTED:
            self.limit_order_ticket.cancel()

    def on_order_event(self, order_event: OrderEvent):
        """
        This event handler is triggered whenever an order status changes.
        """
        # We can retrieve the full order object using the ID if we need to check the OrderType or Tag
        order = self.transactions.get_order_by_id(order_event.order_id)
        
        # Logic for SUBMITTED orders
        if order_event.status == OrderStatus.SUBMITTED:
            self.log(f"Order Submitted: ID {order_event.order_id} | Symbol: {order_event.symbol}")
            # Example: You might want to track open orders in a custom list here

        # Logic for FILLED orders (includes Partial Fills)
        elif order_event.status == OrderStatus.FILLED or order_event.status == OrderStatus.PARTIALLY_FILLED:
            self.log(f"Order Filled: {order_event.fill_quantity} shares of {order_event.symbol} at ${order_event.fill_price}")
            
            # Example: Logic specific to the order tag
            if order.tag == "Market Entry":
                self.log("Entry logic complete. Setting stop loss...")
                # self.stop_market_order(...) 

        # Logic for CANCELLED orders
        elif order_event.status == OrderStatus.CANCELLED:
            self.log(f"Order Cancelled: ID {order_event.order_id}")
            # Example: Retry logic or cleanup
            if order.tag == "Deep Limit":
                self.log("Deep limit order cancelled successfully.")

Implementation Details

  1. Accessing Order Details: The order_event object contains the most critical information regarding the change (Fill Price, Fill Quantity, Status). If you need static information about the order (like the Tag or OrderType), use self.transactions.get_order_by_id(order_event.order_id).
  2. Partial Fills: Note that OrderStatus.FILLED implies the order is completely finished. If you are trading illiquid assets or large volume, you should also handle OrderStatus.PARTIALLY_FILLED.
  3. Async Nature: In live trading, on_order_event is triggered asynchronously. Do not place new orders directly inside this method if there is a risk of creating an infinite loop (e.g., an order fills, triggering logic that places a new order immediately).

Q&A

Q: Does on_order_event run before or after on_data?
A: In backtesting, on_order_event is triggered synchronously after the main data events but before the next time slice. In live trading, it is asynchronous and triggers immediately when the brokerage reports the status change.

Q: How do I distinguish between a Buy and a Sell fill?
A: You can check order_event.direction (0 for Buy, 1 for Sell) or check if order_event.fill_quantity is positive (Buy) or negative (Sell).

Q: Can I modify an order inside on_order_event?
A: You cannot modify the order that just triggered the event (it is already processed). However, you can update other open orders or place new ones, provided you are careful to avoid logic loops.

Q: What happens if an order is invalid?
A: There is an OrderStatus.INVALID state. This usually occurs if you try to place an order with insufficient buying power or invalid parameters. You can catch this in on_order_event to log errors or halt the algorithm.