Order Types
Limit Orders
Introduction
Limit orders are instructions to trade a quantity of an asset at a specific price or a better price. A marketable limit order has a limit price that crosses the spread. An unmarketable limit order has a limit price that doesn't cross the spread. For example, if an asset has an ask price of $100 and you place a limit order to buy at $100 or higher, that's a marketable limit order that should immediately fill. If you place a limit order to buy at $99.99 or lower, that's an unmarketable limit order that should go on the order book. Unmarketable limit orders save you from paying spread costs. Some brokerages even charge you a lower transaction fee if you use unmarketable limit orders rather than marketable limit orders.
Limit orders are helpful in illiquid markets. You can use them to get a good entry price or to set a take-profit level on an existing holding. However, if the market trades away from your limit price, you may have to adjust your limit price or wait for the market to trade back to your limit price to fill the order.
Place Orders
To send a limit order, call the LimitOrder
limit_order
method with a Symbol
, quantity, and limit price. You can also provide a tag and order properties to the LimitOrder
limit_order
method. If you do not have sufficient capital for the order, it's rejected.
LimitOrder(symbol, quantity, limitPrice, tag, orderProperties);
self.limit_order(symbol, quantity, limit_price, tag, order_properties)
To buy an asset with a marketable limit order, set the limit price to the current ask price or higher.
To sell an asset with a marketable limit order, set the limit price to the current bid price or lower.
To buy an asset with an unmarketable limit order, set the limit price below the current ask price.
// Buy 1 Bitcoin when the price drops to $34,000 LimitOrder("BTCUSD", 1, 34000);
# Buy 1 Bitcoin when the price drops to $34,000 self.limit_order("BTCUSD", 1, 34000)
To sell an asset with an unmarketable limit order, set the limit price above the current bid price.
// Sell 1 Bitcoin when the price moves up to $60,000 LimitOrder("BTCUSD", -1, 60000);
# Sell 1 Bitcoin when the price moves up to $60,000 self.limit_order("BTCUSD", -1, 60000)
Monitor Order Fills
Limit orders fill when the security price passes the limit price. To monitor the fills of your order, save a reference to the order ticket.
// Buy 10 shares of XLK at $140
var ticket = LimitOrder("XLK", 10, 140); Debug($"Quantity filled: {ticket.QuantityFilled}; Fill price: {ticket.AverageFillPrice}");
# Buy 10 shares of XLK at $140 ticket = self.limit_order("XLK", 10, 140) 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, limit price, and tag of limit 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 = LimitOrder("SPY", 100, 221.05m, tag: "original tag"); // Update the order var response = ticket.Update(new UpdateOrderFields() { Quantity = 80, LimitPrice = 222.00m, Tag = "new tag" }); // Check if the update was successful if (response.IsSuccess) { Debug("Order updated successfully"); }
# Create a new order and save the order ticket ticket = self.limit_order("SPY", 100, 221.05, tag="original tag") # Update the order update_settings = UpdateOrderFields() update_settings.quantity = 80 update_settings.limit_price = 222.00 update_settings.tag = "new tag" response = ticket.update(update_settings) # Check if the update was successful 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
UpdateTag
update_tag
var limitResponse = ticket.UpdateLimitPrice(limitPrice, tag); var quantityResponse = ticket.UpdateQuantity(quantity, tag); var tagResponse = ticket.UpdateTag(tag);
response = ticket.update_limit_price(limit_price, tag) response = ticket.update_quantity(quantity, 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 limit 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 limit orders, set the brokerage model to a brokerage that supports them.
SetBrokerageModel(BrokerageName.QuantConnectBrokerage);
self.set_brokerage_model(BrokerageName.QuantConnectBrokerage)
To check if your brokerage has any special requirements for limit orders, see the Orders section of the brokerage model documentation.
Examples
The following backtest verifies the LimitOrder
limit_order
method behavior. On the first day, the algorithm buys SPY with an unmarketable limit order. On the second day, the algorithm sells SPY with an unmarketable limit order and places another unmarketable limit order to buy SPY, which doesn't fill. The following table shows the first three trades in the backtest:
Submitted Time | Filled Time | Symbol | Limit Price | Filled Price | Quantity | Type | Status | Value | Tag |
---|---|---|---|---|---|---|---|---|---|
2021-07-01T09:31:00Z | 2021-07-01T09:32:00Z | SPY | 429.00 | 429.00 | 10 | Limit | Filled | 4290.00 | Limit Price: ¤429.00 |
2021-07-02T09:31:00Z | 2021-07-02T09:35:00Z | SPY | 431.70 | 431.70 | -10 | Limit | Filled | -4317.00 | Limit Price: ¤431.70 |
2021-07-02T09:31:00Z | / | SPY | 400.00 | / | 10 | Limit | Submitted | / | Limit Price: ¤400.00 |
On July 1, 2021 at 9:31 AM Eastern Time (ET), the algorithm places a buy limit order with a limit price of $429 when the ask low price is $428.81 and the ask high price is $429.15. The order fills at 9:32 AM ET at a price of $429. The fill model fills buy limit orders when the ask low price is less than the limit price. It sets the fill price of the order to the minimum of ask high price and the limit price.
On July 2, 2021 at 9:31 AM ET, the algorithm places a sell limit order at $431.70 and a buy limit order at $400. At the time of the order, the bid high price is $431.65, the bid low price is $431.49, and the ask low price is $431.50. The sell limit order fills at 9:35 AM ET at a price of $431.70 and the buy limit order doesn't fill. The fill model fills sell limit order when the bid high price is greater than the limit price. It sets the fill price of the order to the maximum of the bid low price and the limit price. The buy limit order doesn't fill because the ask low price is above the limit price for the remainder of the backtest period.
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) { LimitOrder(_symbol, 10, 429.0m); } else if (Time.Day == 2 && Time.Hour == 9 && Time.Minute == 31) { LimitOrder(_symbol, -10, 431.7m); LimitOrder(_symbol, 10, 400.0m); } } }
class LimitOrderAlgorithm(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_order("SPY", 10, 429.0) elif self.time.day == 2 and self.time.hour == 9 and self.time.minute == 31: self.limit_order("SPY", -10, 431.7) self.limit_order("SPY", 10, 400)