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 Get
get
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 OnOrderEvent
on_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 Update
update
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:
UpdateLimitPrice
update_limit_price
UpdateQuantity
update_quantity
UpdateStopPrice
update_stop_price
UpdateTag
update_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 UpdateRequests
update_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 Cancel
cancel
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 CancelRequest
cancel_order_request
method. The method returns null
None
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 GetMostRecentOrderResponse
get_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: