Problem Description
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:
- Polling in
on_data: Check the status of the specificOrderTicketreturned when you placed the order. - 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 forFILLEDand not justPARTIALLY_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.