Order Types
Limit if Touched Orders
Introduction
Limit if touched (LIT) orders are instructions to place a limit order once an asset touches a specific price level. A buy LIT order has a trigger price below the current market price and a sell LIT order has a trigger price above the current market price. In effect, LIT orders are the opposite of stop limit orders.
Place Orders
To send a LIT order, call the LimitIfTouchedOrder
limit_if_touched_order
method and provide a Symbol
, quantity, trigger price, and limit price. You can also provide a tag and order properties to the LimitIfTouchedOrder
limit_if_touched_order
method. If you do not have sufficient capital for the order, it's rejected.
LimitIfTouchedOrder(symbol, quantity, triggerPrice, limitPrice, tag, orderProperties);
self.limit_if_touched_order(symbol, quantity, trigger_price, limit_price, tag, order_properties)
To buy an asset with a LIT order that has a marketable limit price, set the trigger price below the current market price and set the limit price above the trigger price.
// Once Bitcoin trades down to $36,000, place a limit order to buy 1 Bitcoin at $38,000 LimitIfTouchedOrder("BTCUSD", 1, 33000, 36000);
# Once Bitcoin trades down to $36,000, place a limit order to buy 1 Bitcoin at $38,000 self.limit_if_touched_order("BTCUSD", 1, 33000, 36000)

To sell an asset with a LIT order that has a marketable limit price, set the trigger price above the current market price and set the limit price below the trigger price.
// Once Bitcoin trades up to $62,000, place a limit order to sell 1 Bitcoin at $59,000 LimitIfTouchedOrder("BTCUSD", -1, 62000, 59000);
# Once Bitcoin trades up to $62,000, place a limit order to sell 1 Bitcoin at $59,000 self.limit_if_touched_order("BTCUSD", -1, 62000, 59000)

To buy an asset with a LIT order that has an unmarketable limit price, set the trigger price below the current market price and set the limit price below the trigger price.
// Once Bitcoin trades down to $46,000, place a limit order to buy 1 Bitcoin at $36,000 LimitIfTouchedOrder("BTCUSD", 1, 46000, 36000);
# Once Bitcoin trades down to $46,000, place a limit order to buy 1 Bitcoin at $36,000 self.limit_if_touched_order("BTCUSD", 1, 46000, 36000)

To sell an asset with a LIT order that has an unmarketable limit price, set the trigger price above the current market price and set the limit price above the trigger price.
// Once Bitcoin trades up to $54,000, place a limit order to sell 1 Bitcoin at $62,000 LimitIfTouchedOrder("BTCUSD", -1, 54000, 62000);
# Once Bitcoin trades up to $54,000, place a limit order to sell 1 Bitcoin at $62,000 self.limit_if_touched_order("BTCUSD", -1, 54000, 62000)

Monitor Order Fills
LIT orders fill when the security price touches the trigger price and is at least as favorable as the limit price. To monitor the fills of your order, save a reference to the order ticket.
// Once SPY trades down to $425, place a limit order to buy 10 shares at $415 var ticket = LimitIfTouchedOrder("SPY", 10, 425, 415); Debug($"Quantity filled: {ticket.QuantityFilled}; Fill price: {ticket.AverageFillPrice}");
# Once SPY trades down to $425, place a limit order to buy 10 shares at $415 ticket = self.limit_if_touched_order("SPY", 10, 425, 415) self.debug(f"Quantity filled: {ticket.quantity_filled}; Fill price: {ticket.average_fill_price}")
For more information about how LEAN models order fills in backtests, see Trade Fills.
Update Orders
You can update the quantity, trigger price, limit price, and tag of LIT orders until the order fills or the brokerage prevents modifications. To update an order, pass an UpdateOrderFields
object to the Update
update
method on the OrderTicket
. If you don't have the order ticket, get it from the transaction manager. The Update
update
method returns an OrderResponse to signal the success or failure of the update request.
// Create a new order and save the order ticket var ticket = LimitIfTouchedOrder("SPY", 100, 350, 340, tag: "Original tag"); // Update the order var updateOrderFields = new UpdateOrderFields() { Quantity = 80, TriggerPrice = 380, LimitPrice = 370, Tag = "New tag" } var response = ticket.Update(updateOrderFields); // Check the OrderResponse if (response.IsSuccess) { Debug("Order updated successfully"); }
# Create a new order and save the order ticket ticket = self.limit_if_touched_order("SPY", 100, 350, 340, tag="Original tag") # Update the order update_order_fields = UpdateOrderFields() update_order_fields.quantity = 80 update_order_fields.trigger_price = 380 update_order_fields.limit_price = 370 update_order_fields.tag = "New tag" response = ticket.update(update_settings) # Check the OrderResponse if response.is_success: self.debug("Order updated successfully")
To update individual fields of an order, call any of the following methods:
UpdateLimitPrice
update_limit_price
UpdateQuantity
update_quantity
UpdateTriggerPrice
update_trigger_price
UpdateTag
update_tag
var limitResponse = ticket.UpdateLimitPrice(limitPrice, tag); var quantityResponse = ticket.UpdateQuantity(quantity, tag); var triggerResponse = ticket.UpdateTriggerPrice(triggerPrice, tag); var tagResponse = ticket.UpdateTag(tag);
response = ticket.update_limit_price(limit_price, tag) response = ticket.update_quantity(quantity, tag) response = ticket.update_trigger_price(trigger_price, tag) response = ticket.update_tag(tag)
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()
Cancel Orders
To cancel a LIT order, call the Cancel
cancel
method on the OrderTicket
. If you don't have the order ticket, get it from the transaction manager. The Cancel
cancel
method returns an OrderResponse
object to signal the success or failure of the cancel request.
var response = ticket.Cancel("Cancelled trade"); if (response.IsSuccess) { Debug("Order successfully cancelled"); }
response = ticket.cancel("Cancelled Trade") if response.is_success: self.debug("Order successfully cancelled")
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 on the order ticket. The method returns null
None
if the order hasn't been cancelled.
var request = ticket.cancel_order_request();
request = ticket.cancel_order_request()
Brokerage Support
Each brokerage has a set of assets and order types they support. To avoid issues with LIT orders, set the brokerage model to a brokerage that supports them.
SetBrokerageModel(BrokerageName.QuantConnectBrokerage);
self.set_brokerage_model(BrokerageName.QUANTCONNECT_BROKERAGE)
To check if your brokerage has any special requirements for LIT orders, see the Orders section of the brokerage model documentation.
Examples
The following backtest verifies the
LimitIfTouchedOrder
limit_if_touched_order
behavior. The following table shows the trades in the backtest:
Submitted Time | Filled Time | Symbol | Trigger Price | Limit Price | Filled Price | Quantity | Type | Status | Value | Tag |
---|---|---|---|---|---|---|---|---|---|---|
2021-07-01T09:31:00Z | 2021-07-01T09:38:00Z | SPY | 429.00 | 428.95 | 428.95 | 10 | Limit if touched | Filled | 4285.00 |
Trigger Price: ¤429.00
Limit Price: ¤428.95 |
2021-07-02T09:31:00Z | 2021-07-02T09:49:00Z | SPY | 431.70 | 431.75 | 431.84 | -10 | Limit if touched | Filled | -4318.40 |
Trigger Price: ¤431.70
Limit Price: ¤431.75 |
2021-07-02T09:31:00Z | / | SPY | 431.70 | 400.00 | / | 10 | Limit if touched | Submitted | / |
Trigger Price: ¤431.70
Limit Price: ¤400.00 |
/ | / | SPY | 400.00 | 400.00 | / | 10 | Limit if touched | Submitted | / |
Trigger Price: ¤400.00
Limit Price: ¤400.00 |
On July 1, 2021 at 9:31 AM Eastern Time (ET), the algorithm places a LIT order to buy SPY with a trigger price of $429 and a limit price of $428.95. At the time of the order submission, the low price was $428.80 and the bid close price was $429.10. The order fills at 9:38 AM ET at a price of $428.95. The fill model sets the limit price for buy orders when the low price is less than or equal to the trigger price, fills the order when the bid close price is less than or equal to the limit price, and sets the fill price to the minimum of the ask price and the limit price.
On July 2, 2021 at 9:31 AM ET, the algorithm places a LIT order to sell SPY with a trigger price of $431.70 and a limit price of $431.75. At the time of the order submission, the high price was $431.78 and the ask close price was $431.55. The order fills at 9:49 AM ET at a price of $431.84. The fill model sets the limit price for sell orders when the high price is greater than or equal to the trigger price, fills the order when the ask close price is greater than or equal to the limit price, and sets the fill price to the maximum of the bid price and the limit price.
On July 2, 2021, the algorithm places a third LIT order to buy SPY with a trigger price of $431.70 and a limit price of $400. The fill model sets the fill price, but it doesn't fill the order because the limit price is too low. On the same day, the algorithm places a fourth LIT order to buy SPY with a trigger price of $400 and a limit price of $400. The fill model doesn't set the limit price for this order because SPY doesn't touch the trigger price before the backtest ends.
To reproduce these results, backtest the following algorithm:
public class LimitOrderAlgorithm : QCAlgorithm { private Symbol _symbol; public override void Initialize() { SetStartDate(2021, 7, 1); SetEndDate(2021, 7, 4); SetCash(100000); UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw; _symbol = AddEquity("SPY").Symbol; } public override void OnData(Slice data) { if (Time.Day == 1 && Time.Hour == 9 && Time.Minute == 31) { LimitIfTouchedOrder(_symbol, 10, 429.0m, 428.95m); } else if (Time.Day == 2 && Time.Hour == 9 && Time.Minute == 31) { LimitIfTouchedOrder(_symbol, -10, 431.7m, 431.75m); LimitIfTouchedOrder(_symbol, 10, 431.7m, 400.0m); LimitIfTouchedOrder(_symbol, 10, 400.0m, 400.0m); } } }
class LimitIfTouchedOrderAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2021, 7, 1) self.set_end_date(2021, 7, 3) self.set_cash(100000) self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW self.add_equity("SPY") def on_data(self, data): if self.time.day == 1 and self.time.hour == 9 and self.time.minute == 31: self.limit_if_touched_order("SPY", 10, 429.0, 428.95) elif self.time.day == 2 and self.time.hour == 9 and self.time.minute == 31: self.limit_if_touched_order("SPY", -10, 431.7, 431.75) self.limit_if_touched_order("SPY", 10, 431.7, 400) self.limit_if_touched_order("SPY", 10, 400, 400)