Order Management

Order Tickets

Introduction

When you create an order, you get an OrderTicket object to manage your order.

Properties

OrderTicket objects have the following attributes:

Track Orders

As the state of your order updates over time, your order ticket automatically updates. To track an order, you can check any of the preceding order ticket properties.

To get an order field, call the Getget method with an OrderField.

private Symbol _symbol;
private OrderTicket _ticket;

public override void Initialize()
{
    _symbol = AddEquity("SPY").Symbol;
}

public override void OnData(Slice slice)
{
    // Place order if not invested and save the order ticket for later retrival.
    if (!Portfolio.Invested && slice.Bars.TryGetValue(_symbol, out var bar))
    {
        _ticket = LimitOrder(_symbol, 10, bar.Close);
    }
    // Get the limit price if the order is filled.
    else if (Portfolio.Invested)
    {
        var limitPrice = _ticket.Get(OrderField.LimitPrice);
    }
}
def initialize(self) -> None:
    self._symbol = self.add_equity("SPY").symbol
    self._ticket = None
    
def on_data(self, slice: Slice) -> None:
    # Place order if not invested and save the order ticket for later retrival.
    if not self.portfolio.invested and self._symbol in slice.bars:
        self._ticket = self.limit_order(self._symbol, 10, slice.bars[self._symbol].close)
    # Get the limit price if the order is filled.
    elif self.portfolio.invested:
        limit_price = self._ticket.get(OrderField.LIMIT_PRICE)

The OrderField enumeration has the following members:

In addition to using order tickets to track orders, you can receive order events through the OnOrderEventon_order_event event handler.

Update Orders

To update an order, use its OrderTicket. You can update other orders until they are filled or the brokerage prevents modifications. You just can't update orders during warm up and initialization.

Updatable Properties

The specific properties you can update depends on the order type. The following table shows the properties you can update for each order type.

Order TypeUpdatable Properties
TagQuantityLimitPriceTriggerPriceStopPrice
Market Order
Limit Ordergreen checkgreen checkgreen check
Limit If Touched Ordergreen checkgreen checkgreen checkgreen check
Stop Market Ordergreen checkgreen checkgreen check
Stop Limit Ordergreen checkgreen checkgreen checkgreen check
Market On Open Ordergreen checkgreen check
Market On Close Ordergreen checkgreen check

Update Methods

To update an order, pass an UpdateOrderFields object to the Updateupdate method. The method returns an OrderResponse to signal the success or failure of the update request.

private Symbol _symbol;
private OrderTicket _ticket;

public override void Initialize()
{
    _symbol = AddEquity("SPY").Symbol;
}

public override void OnData(Slice slice)
{
    // Place order if not order yet and save the order ticket for later retrival.
    if (_ticket == null && slice.Bars.TryGetValue(_symbol, out var bar))
    {
        _ticket = LimitOrder(_symbol, 10, bar.Close * 0.98m);
    }
    // If order is placed, update the limit price to be 90% of the orginal.
    else if (_ticket != null && _ticket.Status == OrderStatus.Submitted)
    {
        // Update the order tag and limit price
        var response = _ticket.Update(new UpdateOrderFields()
        { 
            Tag = "Our New Tag for SPY Trade",
            LimitPrice = _ticket.Get(OrderField.LimitPrice * 0.9m)
        });

        // Check if the update request is successfully submitted to the broker.
        // Note that it may not represent the order is updated successfully: during the order updating process, it may be filled or canceled.
        if (response.IsSuccess)
        { 
            Debug("Order update request is submitted successfully");
        }
    }
}
def initialize(self) -> None:
    self._symbol = self.add_equity("SPY").symbol
    self._ticket = None
    
def on_data(self, slice: Slice) -> None:
    # Place order if not invested and save the order ticket for later retrival.
    if not self._ticket and self._symbol in slice.bars:
        self._ticket = self.limit_order("SPY", 100, slice.bars[self._symbol].close * 0.98)
    # If order is placed, update the limit price to be 90% of the orginal.
    elif self._ticket != None and self._ticket.status == OrderStatus.SUBMITTED:
        # Update the order tag and limit price
        update_settings = UpdateOrderFields()
        update_settings.limit_price = self._ticket.get(OrderField.LIMIT_PRICE) * 0.9
        update_settings.tag = "Limit Price Updated for SPY Trade"
        response = self._ticket.update(update_settings)

        # Check if the update request is successfully submitted to the broker.
        # Note that it may not represent the order is updated successfully: during the order updating process, it may be filled or canceled.
        if response.is_success:
            self.debug("Order update request is submitted successfully")

To update individual fields of an order, call any of the following methods:

  • UpdateLimitPriceupdate_limit_price
  • UpdateQuantityupdate_quantity
  • UpdateStopPriceupdate_stop_price
  • UpdateTagupdate_tag
var limitResponse = ticket.UpdateLimitPrice(limitPrice, tag);

var quantityResponse = ticket.UpdateQuantity(quantity, tag);

var stopResponse = ticket.UpdateStopPrice(stopPrice, tag);

var tagResponse = ticket.UpdateTag(tag);

response = ticket.update_limit_price(limit_price, tag)

response = ticket.update_quantity(quantity, tag)

response = ticket.update_stop_price(stop_price, tag)

response = ticket.update_tag(tag)

Update Order Requests

When you update an order, LEAN creates an UpdateOrderRequest object, which have the following attributes:

To get a list of UpdateOrderRequest objects for an order, call the UpdateRequestsupdate_requests method.

var updateRequests = ticket.UpdateRequests();
update_requests = ticket.update_requests()

Workaround for Brokerages That Don't Support Updates

Not all brokerages fully support order updates. To check what functionality your brokerage supports for order updates, see the Orders section of the documentation for your brokerage model. If your brokerage doesn't support order updates and you want to update an order, cancel the order. When you get an order event that confirms the order is no longer active, place a new order.

private Symbol _symbol;
private OrderTicket _ticket;

public override void Initialize()
{
    _symbol = AddEquity("SPY").Symbol;
}

public override void OnData(Slice slice)
{
    // Place order if not order yet and save the order ticket for later retrival.
    if (_ticket == null && slice.Bars.TryGetValue(_symbol, out var bar))
    {
        _ticket = LimitOrder(_symbol, 10, bar.Close);
    }
    // If order is placed, cancel the order and place a new one as substituent.
    else if (_ticket != null && _ticket.Status == OrderStatus.Submitted)
    {
        // Cancel the order
        _ticket.Cancel();
    }
}

public override void OnOrderEvent(OrderEvent orderEvent)
{
    if (_ticket != null 
        && orderEvent.OrderId == _ticket.OrderId 
        && orderEvent.Status == OrderStatus.Canceled)
    {
        // Place a new order
        var quantity = _ticket.Quantity - _ticket.QuantityFilled;
        var limitPrice = Securities[_ticket.Symbol].Price + 1;
        _ticket = LimitOrder(_ticket.Symbol, quantity, limitPrice);
    }
}
def initialize(self) -> None:
    self._symbol = self.add_equity("SPY").symbol
    self._ticket = None
    
def on_data(self, slice: Slice) -> None:
    # Place order if not invested and save the order ticket for later retrival.
    if not self._ticket and self._symbol in slice.bars:
        self._ticket = self.limit_order("SPY", 100, slice.bars[self._symbol].close)
    # If order is placed, cancel the order and place a new one as substituent.
    elif self._ticket != None and self._ticket.status == OrderStatus.SUBMITTED:
        self._ticket.cancel()

def on_order_event(self, order_event: OrderEvent) -> None:
    if (self._ticket and order_event.order_id == self._ticket.order_id and
        order_event.status == OrderStatus.CANCELED):
        # Place a new order
        quantity = self._ticket.quantity - self._ticket.quantity_filled
        limit_price = self.securities[self._ticket.symbol].price + 1
        self._ticket = self.limit_order(self._ticket.symbol, quantity, limit_price)

Cancel Orders

To cancel an order, call the Cancelcancel method on the OrderTicket. The method returns an OrderResponse object to signal the success or failure of the cancel request.

private Symbol _symbol;
private OrderTicket _ticket;

public override void Initialize()
{
    _symbol = AddEquity("SPY").Symbol;
}

public override void OnData(Slice slice)
{
    // Place order if not order yet and save the order ticket for later retrival.
    if (_ticket == null && slice.Bars.TryGetValue(_symbol, out var bar))
    {
        _ticket = LimitOrder(_symbol, 10, bar.Close);
    }
    // If order is placed, cancel the order if it is not filled within 2 minutes.
    else if (_ticket != null && _ticket.Time <= slice.Time.AddMinutes(-2) && _ticket.Status == OrderStatus.Submitted)
    {
        // Cancel the order
        var response = ticket.Cancel("Canceled SPY trade");

        // Check if the cancel request is successfully submitted to the broker.
        // Note that it may not represent the order is canceled successfully: during the order updating process, it may be filled.
        if (response.IsSuccess)
        {
            Debug("Order cancel request successfully submitted");
        }
    }
}
def initialize(self) -> None:
    self._symbol = self.add_equity("SPY").symbol
    self._ticket = None
    
def on_data(self, slice: Slice) -> None:
    # Place order if not invested and save the order ticket for later retrival.
    if not self._ticket and self._symbol in slice.bars:
        self._ticket = self.limit_order("SPY", 100, slice.bars[self._symbol].close)
    # If order is placed, cancel the order if it is not filled within 2 minutes.
    elif self._ticket != None and self._ticket.time < slice.time - timedelta(minutes=2) and self._ticket.status == OrderStatus.SUBMITTED:
        response = self._ticket.cancel("Canceled SPY trade")
    
        # Check if the cancel request is successfully submitted to the broker.
        # Note that it may not represent the order is canceled successfully: during the order updating process, it may be filled.
        if response.is_success:
            self.debug("Order cancel request successfully submitted")

You can't cancel market orders because they are immediately transmitted to the brokerage. You also can't cancel any orders during warm up and initialization.

When you cancel an order, LEAN creates a CancelOrderRequest, which have the following attributes:

To get the CancelOrderRequest for an order, call the CancelRequestcancel_order_request method. The method returns nullNone if the order hasn't been cancelled.

var request = ticket.cancel_order_request();
request = ticket.cancel_order_request()

Order Response

When you update or cancel an order, LEAN returns an OrderReponse object, which have the following attributes:

If your order changes fail, check the ErrorCode or ErrorMessage. For more information about specific order errors, see the Order Response Error Reference.

To get most recent order response, call the GetMostRecentOrderResponseget_most_recent_order_response method.

var response = ticket.get_most_recent_order_response();
response = ticket.get_most_recent_order_response()

Examples

The following examples demonstrate some common practices for order ticket management.

Example 1: Timed Exit

The following algorithm is an EMA-crossover strategy. Using the filled time of the order ticket, we can control to exit the position after 45 minutes.

private Symbol _symbol;
private ExponentialMovingAverage _ema;
private OrderTicket _ticket;

public override void Initialize()
{
    SetStartDate(2019, 1, 1);
    SetEndDate(2019, 4, 1);
    _symbol = AddEquity("SPY").Symbol;
    // Set up EMA indicator for trade signal generation.
    _ema = EMA(_symbol, 20, Resolution.Daily);
    // Warm up the indicator for its readiness to use immediately.
    WarmUpIndicator(_symbol, _ema);
}

public override void OnData(Slice slice)
{
    // Place order if not order yet and save the order ticket for later retrival.
    if (_ticket == null && slice.Bars.TryGetValue(_symbol, out var bar))
    {
        // EMA crossover: buy if price is above EMA, which indicate uptrend.
        if (_ema < bar.Close)
        {
            _ticket = LimitOrder(_symbol, 100, bar.Close * 0.995m);
        }
        // EMA crossover: sell if price is below EMA, which indicate down trend.
        else
        {
            _ticket = LimitOrder(_symbol, -100, bar.Close * 1.005m);
        }
    }
    // Exit position if it is in the portfolio for more than 45 minutes.
    else if (_ticket != null && _ticket.Time <= UtcTime.AddMinutes(-45))
    {
        Liquidate();
        _ticket = null;
    }
}
def initialize(self) -> None:
    self.set_start_date(2019, 1, 1)
    self.set_end_date(2019, 4, 1)
    self._symbol = self.add_equity("SPY").symbol
    self._ticket = None
    # Set up EMA indicator for trade signal generation.
    self._ema = self.ema("SPY", 20, Resolution.DAILY)
    # Warm up the indicator for its readiness to use immediately.
    self.warm_up_indicator(self._symbol, self._ema)
    
def on_data(self, slice: Slice) -> None:
    bar = slice.bars.get(self._symbol)
    # Place order if not order yet and save the order ticket for later retrival.
    if not self._ticket and bar:
        # EMA crossover: buy if price is above EMA, which indicate uptrend.
        if self._ema.current.value < bar.close:
            self._ticket = self.limit_order("SPY", 100, slice.bars[self._symbol].close * 0.995)
        # EMA crossover: sell if price is below EMA, which indicate down trend.
        else:
            self._ticket = self.limit_order("SPY", -100, slice.bars[self._symbol].close * 1.005)
    # Exit position if it is in the portfolio for more than 45 minutes.
    elif self._ticket and self._ticket.time + timedelta(minutes=45) < self.utc_time:
        self.liquidate()
        self._ticket = None

Example 2: Intraday Stop Loss

The following algorithm demonstrates an intraday long position with a 10% stop loss order. Before the market closes, cancel the stop loss order if it is not filled and exit the position at market close with a market on close order.

private Symbol _symbol;
// A variable to save the stop loss order and track if it is filled.
private OrderTicket _ticket;
private int _day = -1;

public override void Initialize()
{
    SetStartDate(2023, 1, 1);
    SetEndDate(2023, 7, 1);
    _symbol = AddEquity("QQQ").Symbol;

    // Schedule an event to handle exiting position before market close if stop loss is not hit.
    // Market on close order requires ordering 15 minutes before market close. 
    Schedule.On(
        DateRules.EveryDay(_symbol),
        TimeRules.BeforeMarketClose(_symbol, 16),
        ExitPosition
    );
}

public override void OnData(Slice slice)
{
    // Place order if not invested. Make sure only trade once a day to avoid chasing the loss.
    if (!Portfolio.Invested && _day != slice.Time.Day && slice.Bars.TryGetValue(_symbol, out var bar))
    {
        MarketOrder(_symbol, 10);
        // Update the day variable to avoid repeat ordering.
        _day = slice.Time.Day;
    }
}

public override void OnOrderEvent(OrderEvent orderEvent)
{
    // Make sure to order the stop loss order on the filled market order only.
    if (orderEvent.Status == OrderStatus.Filled && orderEvent.FillQuantity > 0)
    {
        // Place a 10% stop loss order to avoid large loss. Save the order ticket for tracking its status later.
        _ticket = StopMarketOrder(orderEvent.Symbol, -orderEvent.FillQuantity, orderEvent.FillPrice*0.9m);
    }
}

private void ExitPosition()
{
    // Only need to cancel the order and handle exit position if the stop loss order is not fully filled.
    if (_ticket != null && _ticket.Status != OrderStatus.Filled)
    {
        _ticket.Cancel();
        // Market on close order to exit position at market close with the remaining quantity.
        MarketOnCloseOrder(_symbol, -10 - _ticket.QuantityFilled);
    }
}
def initialize(self) -> None:
    self.set_start_date(2023, 1, 1)
    self.set_end_date(2023, 7, 1)
    self._symbol = self.add_equity("QQQ").symbol
    self._ticket = None
    self._day = -1

    # Schedule an event to handle exiting position before market close if stop loss is not hit.
    # Market on close order requires ordering 15 minutes before market close. 
    self.schedule.on(
        self.date_rules.every_day(self._symbol),
        self.time_rules.before_market_close(self._symbol, 16),
        self.exit_position
    )
    
def on_data(self, slice: Slice) -> None:
    bar = slice.bars.get(self._symbol)
    # Place order if not invested. Make sure only trade once a day to avoid chasing the loss.
    if not self.portfolio.Invested and self._day != slice.time.day and bar:
        self.market_order(self._symbol, 10)
        # Update the day variable to avoid repeat ordering.
        self._day = slice.time.day

def on_order_event(self, order_event: OrderEvent) -> None:
    # Make sure to order the stop loss order on the filled market order only.
    if order_event.status == OrderStatus.FILLED and order_event.fill_quantity > 0:
        # Place a 10% stop loss order to avoid large loss. Save the order ticket for tracking its status later.
        self._ticket = self.stop_market_order(order_event.symbol, -order_event.fill_quantity, order_event.fill_price*0.9)

def exit_position(self) -> None:
    # Only need to cancel the order and handle exit position if the stop loss order is not fully filled.
    if self._ticket and self._ticket.status != OrderStatus.FILLED:
        self._ticket.cancel()
        # Market on close order to exit position at market close with the remaining quantity.
        self.market_on_close_order(self._symbol, -10 - self._ticket.quantity_filled)

Example 3: Crypto Trailing Stop Order

This example trades BTCUSD with a similar logic of Example 2, but apply a trailing stop loss of 10% instead. Crypto exchanges does not support trailing stop loss order. Yet, we can cancel and reorder a stop loss order with the updated stop loss price. If the market price of the crypto pair is above the previous level, update the stop loss order by the above.

private Symbol _symbol;
// A variable to hold the stop loss order to track if it should be updated.
private OrderTicket _ticket;
// A day variable to avoid over-ordering (trade once per day)
private int _day = -1;
// A variable to hold the price of the high price in the lifetime of the open position for updating the stop loss price.
private decimal _highPrice = 0m;

public override void Initialize()
{
    SetStartDate(2023, 1, 1);
    SetEndDate(2023, 7, 1);
    // Set the market argument to trade in specific exchange.
    _symbol = AddCrypto("BTCUSD", market: Market.Coinbase).Symbol;
}

public override void OnData(Slice slice)
{
    if (slice.Bars.TryGetValue(_symbol, out var bar))
    {
        // Place order if not invested. Make sure only trade once a day to avoid chasing the loss.
        if (!Portfolio.Invested && _day != slice.Time.Day)
        {
            MarketOrder(_symbol, 1);
            // Update the day variable to avoid repeat ordering.
            _day = slice.Time.Day;
        }

        // Update the trailing stop order. Since order updating is not supported in crypto exchanges, we cancel and reorder.
        // Trailing stop price will only need to be updated when the market price is higher than the current price.
        if (_ticket != null && bar.High > _highPrice &&
        new OrderStatus[] { OrderStatus.Submitted, OrderStatus.PartiallyFilled }.Contains(_ticket.Status))
            _ticket.Cancel();
            _ticket = StopMarketOrder(_symbol, -Portfolio[_symbol].Quantity, bar.High*0.9m);
            _highPrice = bar.High;
    }
    
}

public override void OnOrderEvent(OrderEvent orderEvent)
{
    // Make sure to order the stop loss order on the filled market order only.
    if (orderEvent.Status == OrderStatus.Filled && orderEvent.FillQuantity > 0)
    {
        // Place a 10% stop loss order to avoid large loss. Save the order ticket for tracking its status later.
        _ticket = StopMarketOrder(orderEvent.Symbol, -orderEvent.FillQuantity, orderEvent.FillPrice*0.9m);
        // Save the price to check if the stop loss price should be increased.
        _highPrice = orderEvent.FillPrice;
    }
}
def initialize(self) -> None:
    self.set_start_date(2023, 1, 1)
    self.set_end_date(2023, 7, 1)
    # Set the market argument to trade in specific exchange.
    self._symbol = self.add_crypto("BTCUSD", market=Market.COINBASE).symbol
    # A variable to hold the stop loss order to track if it should be updated.
    self._ticket = None
    # A day variable to avoid over-ordering (trade once per day)
    self._day = -1
    # A variable to hold the price of the high price in the lifetime of the open position for updating the stop loss price.
    self.high_price = 0
    
def on_data(self, slice: Slice) -> None:
    bar = slice.bars.get(self._symbol)
    if bar:
        # Place order if not invested. Make sure only trade once a day to avoid chasing the loss.
        if not self.portfolio.Invested and self._day != slice.time.day:
            self.market_order(self._symbol, 1)
            # Update the day variable to avoid repeat ordering.
            self._day = slice.time.day

        # Update the trailing stop order. Since order updating is not supported in crypto exchanges, we cancel and reorder.
        # Trailing stop price will only need to be updated when the market price is higher than the current price.
        if self._ticket and self._ticket.status in [OrderStatus.SUBMITTED, OrderStatus.PARTIALLY_FILLED]\
        and bar.high > self.high_price:
            self._ticket.cancel()
            self._ticket = self.stop_market_order(self._symbol, -self.portfolio[self._symbol].quantity, bar.high*0.9)
            self.high_price = bar.high

def on_order_event(self, order_event: OrderEvent) -> None:
    # Make sure to order the stop loss order on the filled market order only.
    if order_event.status == OrderStatus.FILLED and order_event.fill_quantity > 0:
        # Place a 10% stop loss order to avoid large loss. Save the order ticket for tracking its status later.
        self._ticket = self.stop_market_order(order_event.symbol, -order_event.fill_quantity, order_event.fill_price*0.9)
        # Save the price to check if the stop loss price should be increased.
        self.high_price = order_event.fill_price

Other Examples

For more examples, see the following algorithms:

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: