Trading and Orders

Order Events

Introduction

An OrderEvent object represents an update to the state of an order. As the state of your orders change, we notify your algorithm with OrderEvent objects through the OnOrderEventon_order_event and OnAssignmentOrderEventon_assignment_order_event event handlers.

Track Order Events

Each order generates events over its life as its status changes. Your algorithm receives these events through the OnOrderEventon_order_event and OnAssignmentOrderEventon_assignment_order_event methods. The OnOrderEventon_order_event event handler receives all order events. The OnAssignmentOrderEventon_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 OrderEventsorder_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.

Order States

Orders can have any of the following states:

Event Attributes

The OnOrderEventon_order_event and OnAssignmentOrderEventon_assignment_order_event event handlers in your algorithm receive OrderEvent objects, which have the following attributes:

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.ActiveSecuritiesQCAlgorithm.active_securities is a collection of Universe.MembersUniverse.members of all universes. The ActiveSecuritiesactive_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 ActiveSecuritiesactive_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 MinimumTimeInUniverseminimum_time_in_universe setting.

When LEAN removes the security, the Security object remains in the Securitiessecurities collection for record-keeping purposes, like tracking fees and trading volume.

To improve iteration speed, LEAN removes delisted securities from the Securitiessecurities primary collection. To access all the securities, iterate the Securities.Totalsecurities.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.

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: