Order Management
Order Tickets
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 Type | Updatable Properties | ||||
|---|---|---|---|---|---|
| Tag | Quantity | LimitPrice | TriggerPrice | StopPrice | |
| Market Order | |||||
| Limit Order | ![]() | ![]() | ![]() | ||
| Limit If Touched Order | ![]() | ![]() | ![]() | ![]() | |
| Stop Market Order | ![]() | ![]() | ![]() | ||
| Stop Limit Order | ![]() | ![]() | ![]() | ![]() | |
| Market On Open Order | ![]() | ![]() | |||
| Market On Close Order | ![]() | ![]() | |||
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_priceUpdateQuantityupdate_quantityUpdateStopPriceupdate_stop_priceUpdateTagupdate_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.
public class OrderTicketExampleAlgorithm : QCAlgorithm
{
private Symbol _symbol;
private ExponentialMovingAverage _ema;
private OrderTicket _ticket;
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
_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;
}
}
} class OrderTicketExampleAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
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.
public class OrderTicketExampleAlgorithm : QCAlgorithm
{
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(2024, 9, 1);
SetEndDate(2024, 12, 31);
_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);
}
}
} class OrderTicketExampleAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
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.
public class OrderTicketExampleAlgorithm : QCAlgorithm
{
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;
// Update the stop loss when new high is 1% higher
private decimal _updateStep = 1.01m;
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
// 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)) return;
// Place order if not invested. Make sure only trade once a day to avoid chasing the loss.
if (!Portfolio.Invested && _day != Time.Day)
{
MarketOrder(_symbol, 1);
// Place a 10% stop loss order to avoid large loss. Save the order ticket for tracking its status later.
_ticket = StopMarketOrder(_symbol, -1, bar.Close*0.9m);
// Update the day variable to avoid repeat ordering.
_day = 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 &&_ticket.Status.IsOpen() && bar.High > _highPrice)
{
_ticket.Cancel();
_ticket = StopMarketOrder(_symbol, -Portfolio[_symbol].Quantity, bar.High*0.9m);
_highPrice = bar.High * _updateStep;
}
}
} class OrderTicketExampleAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
# 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
# Update the stop loss when new high is 1% higher
self.update_step = 1.01
def on_data(self, slice: Slice) -> None:
bar = slice.bars.get(self._symbol)
if not bar: return
# 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 != self.time.day:
self.market_order(self._symbol, 1)
# 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(self._symbol, -1, bar.close*0.9)
self.high_price = bar.high * self.update_step
# Update the day variable to avoid repeat ordering.
self._day = self.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 * self.update_step
Other Examples
For more examples, see the following algorithms:
