🚀 AI One-Click Generation of quantconnect Strategy Code

How to Track and Respond to Order Status Changes in Python

quantconnect | Published on: 2025/11/27
The following content is generated by EasyQuant .

Problem Description

How can I check if my trade order was filled before placing the next one?

Solution

To check if a trade order was filled before placing the next one in QuantConnect, you should utilize Order Tickets and the Order Status enumeration.

There are two primary ways to handle this:

  1. Polling in on_data: Check the status of the specific OrderTicket returned when you placed the order.
  2. Event-Driven in on_order_event: Listen for status changes globally and trigger logic immediately upon a fill.

Method 1: Using Order Tickets (Recommended for Sequential Logic)

When you place an order (e.g., self.market_order), LEAN returns an OrderTicket object. You can save this object and check its .status property in your main loop.

Here is a complete algorithm demonstrating how to place an entry order, wait for it to fill, and only then place a profit-taking order.

from AlgorithmImports import *

class OrderFillCheckAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 1, 1)
        self.set_end_date(2023, 6, 1)
        self.set_cash(100000)
        
        self.symbol = self.add_equity("SPY", Resolution.MINUTE).symbol
        
        # Variables to hold our order tickets
        self.entry_ticket = None
        self.exit_ticket = None

    def on_data(self, data: Slice):
        if not data.contains_key(self.symbol):
            return

        # 1. Place the Initial Order if we haven't yet
        if self.entry_ticket is None:
            self.entry_ticket = self.market_order(self.symbol, 10)
            self.debug("Entry Order Placed")
            return

        # 2. Check if the Entry Order is Filled
        # We check if the ticket exists and the status is FILLED
        if self.entry_ticket.status == OrderStatus.FILLED:
            
            # Ensure we haven't already placed the next order
            if self.exit_ticket is None:
                
                # Logic for the next order (e.g., a Limit Sell 1% higher)
                fill_price = self.entry_ticket.average_fill_price
                target_price = fill_price * 1.01
                
                self.exit_ticket = self.limit_order(self.symbol, -10, target_price)
                self.debug(f"Entry Filled at {fill_price}. Placing Exit at {target_price}")

        # Optional: Check if the specific order was canceled or invalid
        elif self.entry_ticket.status == OrderStatus.CANCELED:
            self.debug("Entry order was canceled. Resetting logic.")
            self.entry_ticket = None

Method 2: Using on_order_event (Event-Driven)

For more complex systems, you can track fills asynchronously using the on_order_event handler. This is efficient because it only runs when an order state changes, rather than checking every minute.

from AlgorithmImports import *

class EventDrivenFillCheckAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 1, 1)
        self.set_cash(100000)
        self.symbol = self.add_equity("SPY", Resolution.MINUTE).symbol
        self.entry_order_id = -1

    def on_data(self, data: Slice):
        # Place initial trade if not invested and no open order
        if not self.portfolio.invested and self.entry_order_id == -1:
            ticket = self.market_order(self.symbol, 10)
            self.entry_order_id = ticket.order_id

    def on_order_event(self, order_event: OrderEvent):
        # 1. Check if the event is a Fill
        if order_event.status == OrderStatus.FILLED:
            
            # 2. Check if this event belongs to our specific entry order
            if order_event.order_id == self.entry_order_id:
                self.debug(f"Order {order_event.order_id} Filled. Placing next order.")
                
                # Place the next order immediately
                # Note: In live trading, be careful placing orders directly inside 
                # OnOrderEvent to avoid potential recursive loops if that order fills instantly.
                self.limit_order(self.symbol, -10, order_event.fill_price * 1.01)
                
                # Reset tracker
                self.entry_order_id = -1

Key Properties Used

  • OrderTicket.status: The current state of the order.
  • OrderStatus.FILLED: The specific enum value indicating the order is completely executed.
  • OrderStatus.PARTIALLY_FILLED: Indicates some shares were bought/sold, but not the total quantity. If your logic requires a complete fill, ensure you check for FILLED and not just PARTIALLY_FILLED.

Q&A

Q: What happens if my order is only partially filled?
A: The status will be OrderStatus.PARTIALLY_FILLED. If your strategy requires the full quantity before proceeding, you should wait until the status becomes OrderStatus.FILLED. If you are okay with proceeding with whatever amount was filled, you can check ticket.quantity_filled.

Q: Does this work differently in Backtesting vs. Live Trading?
A: In backtesting, Market Orders are usually filled synchronously (immediately) on the next line of code. However, in Live Trading, execution is asynchronous. Using the OrderTicket check or on_order_event ensures your code works correctly in both environments by not assuming an instant fill.

Q: How do I handle rejected orders?
A: You should check for OrderStatus.INVALID. If an order is invalid (e.g., insufficient buying power or bad parameters), it will not fill. Your logic should handle this state to prevent the algorithm from waiting indefinitely for a fill that will never happen.