Trading and Orders
Order Events
Track Order Events
Each order generates events over its life as its status changes. Your algorithm receives these events through the OnOrderEvent
on_order_event
and OnAssignmentOrderEvent
on_assignment_order_event
methods. The OnOrderEvent
on_order_event
event handler receives all order events. The OnAssignmentOrderEvent
on_assignment_order_event
receives order events for Option assignments. The event handlers receive an OrderEvent
object, which contains information about the order status.
public override void OnOrderEvent(OrderEvent orderEvent) { var order = Transactions.GetOrderById(orderEvent.OrderId); if (orderEvent.Status == OrderStatus.Filled) { Debug($"{Time}: {order.Type}: {orderEvent}"); } } public override void OnAssignmentOrderEvent(OrderEvent assignmentEvent) { Log(assignmentEvent.ToString()); }
def on_order_event(self, order_event: OrderEvent) -> None: order = self.transactions.get_order_by_id(order_event.order_id) if order_event.status == OrderStatus.FILLED: self.debug(f"{self.time}: {order.type}: {order_event}") def on_assignment_order_event(self, assignment_event: OrderEvent) -> None: self.log(str(assignment_event))
To get a list of all OrderEvent
objects for an order, call the OrderEvents
order_events
method of the order ticket.
var orderEvents = orderTicket.OrderEvents();
order_events = order_ticket.order_events()
If you don't have the order ticket, get the order ticket from the TransactionManager.
Examples
The following examples demonstrate some common practices for using order events.
Example 1: Illiquid Stock Partial Fill
The following algorithm trades EMA cross on CARZ, an illiquid ETF. To realistically simulate the fill behavior, we set a fill model to partially fill the orders with at most 100 shares per fill. We cancel the remaining open order after the partial fill since we only trade on the updated information.
public class OrderEventsAlgorithm : QCAlgorithm { private Symbol _carz; private ExponentialMovingAverage _ema; public override void Initialize() { SetStartDate(2024, 2, 1); SetEndDate(2024, 4, 1); // Request CARZ data to feed indicator and trade. var equity = AddEquity("CARZ"); _carz = equity.Symbol; // Set a custom partial fill model for the illiquid CARZ stock since it is more realistic. equity.SetFillModel(new CustomPartialFillModel(this)); // Create EMA indicator to generate trade signals. _ema = EMA(_carz, 60, Resolution.Daily); //Warm-up indicator for immediate readiness to use. WarmUpIndicator(_carz, _ema, Resolution.Daily); } public override void OnData(Slice slice) { if (slice.Bars.TryGetValue(_carz, out var bar)) { // Trade EMA cross on CARZ for trend-following strategy. if (bar.Close > _ema && !Portfolio[_carz].IsLong) { SetHoldings(_carz, 0.5m); } else if (bar.Close < _ema && !Portfolio[_carz].IsShort) { SetHoldings(_carz, -0.5m); } } } public override void OnOrderEvent(OrderEvent orderEvent) { // If an order is only partially filled, we cancel the rest to avoid trade on non-updated information. if (orderEvent.Status == OrderStatus.PartiallyFilled) { Transactions.CancelOpenOrders(); } } /// Implements a custom fill model that partially fills each order with a ratio of the previous trade bar. private class CustomPartialFillModel : FillModel { private readonly QCAlgorithm _algorithm; private readonly Dictionary<int, decimal> _absoluteRemainingByOrderId; // Save the ratio of the volume of the previous bar to fill the order. private decimal _ratio; public CustomPartialFillModel(QCAlgorithm algorithm, decimal ratio = 0.5m) : base() { _algorithm = algorithm; _absoluteRemainingByOrderId = new Dictionary<int, decimal>(); _ratio = ratio; } public override OrderEvent MarketFill(Security asset, MarketOrder order) { decimal absoluteRemaining; if (!_absoluteRemainingByOrderId.TryGetValue(order.Id, out absoluteRemaining)) { absoluteRemaining = order.AbsoluteQuantity; } var fill = base.MarketFill(asset, order); // Partially filled each order with at most 50% of the previous bar. fill.FillQuantity = Math.Sign(order.Quantity) * asset.Volume * _ratio; if (Math.Min(Math.Abs(fill.FillQuantity), absoluteRemaining) == absoluteRemaining) { fill.FillQuantity = Math.Sign(order.Quantity) * absoluteRemaining; fill.Status = OrderStatus.Filled; _absoluteRemainingByOrderId.Remove(order.Id); } else { fill.Status = OrderStatus.PartiallyFilled; // Save the remaining quantity after it is partially filled. _absoluteRemainingByOrderId[order.Id] = absoluteRemaining - Math.Abs(fill.FillQuantity); var price = fill.FillPrice; } return fill; } } }
class OrderEventsAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 2, 1) self.set_end_date(2024, 4, 1) # Request CARZ data to feed indicator and trade. equity = self.add_equity("CARZ") self.carz = equity.symbol # Set a custom partial fill model for the illiquid CARZ stock since it is more realistic. equity.set_fill_model(CustomPartialFillModel(self)) # Create EMA indicator to generate trade signals. self._ema = self.ema(self.carz, 60, Resolution.DAILY) # Warm-up indicator for immediate readiness to use. self.warm_up_indicator(self.carz, self._ema, Resolution.DAILY) def on_data(self, slice: Slice) -> None: bar = slice.bars.get(self.carz) if bar and self._ema.is_ready: ema = self._ema.current.value # Trade EMA cross on CARZ for trend-following strategy. if bar.close > ema and not self.portfolio[self.carz].is_long: self.set_holdings(self.carz, 0.5) elif bar.close < ema and not self.portfolio[self.carz].is_short: self.set_holdings(self.carz, -0.5) def on_order_event(self, order_event: OrderEvent) -> None: # If an order is only partially filled, we cancel the rest to avoid trade on non-updated information. if order_event.status == OrderStatus.PARTIALLY_FILLED: self.transactions.cancel_open_orders() # Implements a custom fill model that partially fills each order with a ratio of the previous trade bar. class CustomPartialFillModel(FillModel): def __init__(self, algorithm: QCAlgorithm, ratio: float = 0.5) -> None: self.algorithm = algorithm self.absolute_remaining_by_order_id = {} # Save the ratio of the volume of the previous bar to fill the order. self.ratio = ratio def market_fill(self, asset: Security, order: MarketOrder) -> None: absolute_remaining = self.absolute_remaining_by_order_id.get(order.id, order. AbsoluteQuantity) fill = super().market_fill(asset, order) # Partially fill each order with at most 50% of the previous bar. fill.fill_quantity = np.sign(order.quantity) * asset.volume * self.ratio if (min(abs(fill.fill_quantity), absolute_remaining) == absolute_remaining): fill.fill_quantity = np.sign(order.quantity) * absolute_remaining fill.status = OrderStatus.FILLED self.absolute_remaining_by_order_id.pop(order.id, None) else: fill.status = OrderStatus.PARTIALLY_FILLED # Save the remaining quantity after it is partially filled. self.absolute_remaining_by_order_id[order.id] = absolute_remaining - abs(fill.fill_quantity) price = fill.fill_price return fill
Example 2: Take Profit Stop Loss
The QCAlgorithm.ActiveSecurities
QCAlgorithm.active_securities
is a collection of Universe.Members
Universe.members
of all universes. The ActiveSecurities
active_securities
property of the algorithm class contains all of the assets currently in your algorithm. It is a dictionary where the key is a Symbol
and the value is a Security
. When you remove an asset from a universe, LEAN usually removes the security from the ActiveSecurities
active_securities
collection and removes the security subscription. However, it won't remove the security in any of the following situations:
- You own the security.
- You have an open order for the security.
- The security wasn't in the universe long enough to meet the
MinimumTimeInUniverse
minimum_time_in_universe
setting.
When LEAN removes the security, the Security
object remains in the Securities
securities
collection for record-keeping purposes, like tracking fees and trading volume.
To improve iteration speed, LEAN removes delisted securities from the Securities
securities
primary collection. To access all the securities, iterate the Securities.Total
securities.total
property.
// Access all the securities. foreach (var security in Securities.Total) { } // Exclude delisted securities. foreach (var security in Securities.Values) { }
# Access all the securities. for security in self.securities.total: pass # Exclude delisted securities. for security in self.securities.values(): pass
To get only the assets that are currently in the universe, see Selected Securities.