---
title: "Order Events"
description: "As the state of your orders change, we notify you with OrderEvent objects through the OnOrderEvent and OnAssignmentOrderEvent event handlers."
canonical: https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/order-events
markdown: https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/order-events.md
language: python
source: https://github.com/QuantConnect/Documentation/tree/master/03%20Writing%20Algorithms/22%20Trading%20and%20Orders/10%20Order%20Events
modified: 2026-10-31
index: https://www.quantconnect.com/llms.txt
---

# Order Events

[Documentation](https://www.quantconnect.com/docs/v2.md) > [Writing Algorithms](https://www.quantconnect.com/docs/v2/writing-algorithms.md) > Trading and Orders > Order Events

> Code examples are shown in Python. Append `?lang=csharp` to this URL for the C# version of this page.

## 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 `on_order_event` and `on_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 `on_order_event` and `on_assignment_order_event` methods. The `on_order_event` event handler receives all order events. The `on_assignment_order_event` receives order events for Option assignments. The event handlers receive an `OrderEvent` object, which contains information about the order status.

```python
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))
```

The `OrderEvent` object doesn't have a tag or a time property. To get the order tag, read it from the order ticket. To get the event time, read the `utc_time` property, which is in UTC. The fee amount is in the `value` property of the order fee.

```python
tag = order_event.ticket.tag
utc_time = order_event.utc_time
fee = order_event.order_fee.value.amount
```

To get a list of all `OrderEvent` objects for an order, call the `order_events` method of the [order ticket](https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/order-management/order-tickets.md).

```python
order_events = order_ticket.order_events()
```

If you don't have the order ticket, [get the order ticket from the TransactionManager](https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/order-management/transaction-manager.md#03-Get-Order-Tickets).

## Order States

Orders can have any of the following states:

*Members of `QuantConnect.Orders.OrderStatus` (JSON): https://www.quantconnect.com/services/inspector?type=T:QuantConnect.Orders.OrderStatus&language=python*

## Event Attributes

The `on_order_event` and `on_assignment_order_event` event handlers in your algorithm receive `OrderEvent` objects, which have the following attributes:

*Members of `QuantConnect.Orders.OrderEvent` (JSON): https://www.quantconnect.com/services/inspector?type=T:QuantConnect.Orders.OrderEvent&language=python*

## 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 50% of the previous bar's volume per fill. We cancel the remaining open order after the partial fill since we only trade on the updated information.

```python
class OrderEventsAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2024, 9, 1)
        self.set_end_date(2024, 12, 31)

        # 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: Price Actions

The following algorithm saves the trailing 3 `TradeBar` objects into a `RollingWindow`. When it identifies a volume contraction breakout price action pattern on the SPY, it buys to ride on the capital inflow. To exit positions, it places a 2% take profit and 1% stop loss order in the `on_order_event` method.

```python
class RollingWindowAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2024, 9, 1)
        self.set_end_date(2024, 12, 31)

        # Add SPY data for signal generation and trading.
        self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol

        # Set up a rolling window to hold the last 3 trade bars for price action detection as the trade signal.
        self.windows = RollingWindow(3)
        # Warm up the rolling window.
        history = self.history[TradeBar](self.spy, 3, Resolution.MINUTE)
        for bar in history:
            self.windows.add(bar)

    def on_data(self, slice: Slice) -> None:
        bar = slice.bars.get(self.spy)
        if bar:
            # Trade the price action if the previous bars fulfill a contraction breakout.
            if self.contraction_action and self.breakout(bar.close):
                self.set_holdings(self.spy, 0.5)

            # Add the current bar to the window.
            self.windows.add(bar)

    def contraction_action(self) -> None:
        # We trade contraction type price action, where the buying preesure is increasing.
        # 1. The last 3 bars are green.
        # 2. The price is increasing in trend.
        # 3. The trading volume is increasing as well.
        # 4. The range of the bars are decreasing.
        return (
            self.windows[2].close > self.windows[2].open and
            self.windows[1].close > self.windows[1].open and
            self.windows[0].close > self.windows[0].open and
            self.windows[0].close > self.windows[1].close > self.windows[2].close and
            self.windows[0].volume > self.windows[1].volume > self.windows[2].volume and
            self.windows[2].close - self.windows[2].open > self.windows[1].close - self.windows[1].open > self.windows[0].close - self.windows[0].open
        )

    def breakout(self, current_close: float) -> None:
        # Trade breakout from contraction: the breakout should be much greater than the contracted range of the last bar.
        return current_close - self.windows[0].close > (self.windows[0].close - self.windows[0].open) * 2

    def on_order_event(self, order_event: OrderEvent) -> None:
        if order_event.status == OrderStatus.FILLED:
            if order_event.ticket.order_type == OrderType.MARKET:
                # Stop loss order at 1%.
                stop_price = order_event.fill_price * (0.99 if order_event.fill_quantity > 0 else 1.01)
                self.stop_market_order(self.spy, -self.portfolio[self.spy].quantity, stop_price)
                # Take profit order at 2%.
                take_profit_price = order_event.fill_price * (1.02 if order_event.fill_quantity > 0 else 0.98)
                self.limit_order(self.spy, -self.portfolio[self.spy].quantity, take_profit_price)
            elif order_event.ticket.order_type == OrderType.STOP_MARKET or order_event.ticket.order_type == OrderType.LIMIT:
                # Cancel open orders if stop loss or take profit order fills.
                self.transactions.cancel_open_orders()
```
