Order Management
Order Factory
Introduction
The order factory creates order requests without submitting them. Use order requests to estimate fees and check buying power before you place orders, to submit several orders together, and to build contingent orders. When you submit an order request, you get an order ticket to manage the order.
Create Order Requests
To create an order request, call one of the methods of the OrderFactoryorder_factory member. Each method returns a SubmitOrderRequest object.
var request = OrderFactory.LimitOrder(_symbol, 100, limitPrice, tag: "Entry");
request = self.order_factory.limit_order(self._symbol, 100, limit_price, tag="Entry")
The order factory has a method for each order type. The following table shows the required arguments of each method:
| Method | Order Type |
|---|---|
MarketOrder(symbol, quantity)market_order(symbol, quantity) | Market |
LimitOrder(symbol, quantity, limitPrice)limit_order(symbol, quantity, limit_price) | Limit |
LimitIfTouchedOrder(symbol, quantity, triggerPrice, limitPrice)limit_if_touched_order(symbol, quantity, trigger_price, limit_price) | Limit if touched |
StopMarketOrder(symbol, quantity, stopPrice)stop_market_order(symbol, quantity, stop_price) | Stop market |
StopLimitOrder(symbol, quantity, stopPrice, limitPrice)stop_limit_order(symbol, quantity, stop_price, limit_price) | Stop limit |
TrailingStopOrder(symbol, quantity, trailingAmount, trailingAsPercentage)trailing_stop_order(symbol, quantity, trailing_amount, trailing_as_percentage) | Trailing stop |
TrailingStopOrder(symbol, quantity, stopPrice, trailingAmount, trailingAsPercentage)trailing_stop_order(symbol, quantity, stop_price, trailing_amount, trailing_as_percentage) | Trailing stop with an initial stop price |
MarketOnOpenOrder(symbol, quantity)market_on_open_order(symbol, quantity) | Market on open |
MarketOnCloseOrder(symbol, quantity)market_on_close_order(symbol, quantity) | Market on close |
ComboMarketOrder(legs, quantity)combo_market_order(legs, quantity) | Combo market |
ComboLimitOrder(legs, quantity, limitPrice)combo_limit_order(legs, quantity, limit_price) | Combo limit |
ComboLegLimitOrder(legs, quantity)combo_leg_limit_order(legs, quantity) | Combo leg limit |
OptionStrategyOrder(strategy, quantity)option_strategy_order(strategy, quantity) | Option strategy |
ExerciseOption(optionSymbol, quantity)exercise_option(option_symbol, quantity) | Option exercise |
Every method also accepts the optional asynchronous, tag, and orderPropertiesorder_properties arguments.
The combo and Option strategy methods return a list with one order request for each leg. The order request stores the current time and a copy of the default order properties, unless you pass order properties. If the ticker of the security changed, the order request uses the current Symbol.
Submit Order Requests
To submit order requests, pass a single request or a list of requests to the Orderorder method. The method returns a list with the order ticket of each order. For a combo order, include all the legs.
var tickets = Order(new List<SubmitOrderRequest>
{
OrderFactory.MarketOrder(_spy, 10),
OrderFactory.LimitOrder(_aapl, -20, aaplLimitPrice)
}); tickets = self.order([
self.order_factory.market_order(self._spy, 10),
self.order_factory.limit_order(self._aapl, -20, aapl_limit_price)
])
LEAN runs the pre-order checks on every request before it submits any of them. If a request fails the checks, LEAN submits none of the orders and the method returns a list with a single invalid order ticket. LEAN checks the buying power of each order after you submit it.
LEAN applies the usual order adjustments when you submit the request. For example, LEAN converts a market order into a market on open order if the market is closed. LEAN also sets the initial stop price of a trailing stop order from the market price at that time.
You can submit each order request once. After you submit a request, its OrderIdorder_id property holds the order Id. To get the order ticket of a request, get it from the transaction manager.
var ticket = Transactions.GetOrderTicket(request.OrderId);
ticket = self.transactions.get_order_ticket(request.order_id)
Estimate Order Fees
To estimate the modeled fee of an order before you place it, create an order request with the order factory and convert it into an Order object with the QuantConnect.Orders.Order.CreateOrderOrder.create_order method. Then pass the order and its security to the fee model of the security. The conversion doesn't submit the order.
var security = Securities[_symbol];
var request = OrderFactory.LimitOrder(_symbol, 100, limitPrice);
var order = QuantConnect.Orders.Order.CreateOrder(request);
var fee = security.FeeModel.GetOrderFee(new OrderFeeParameters(security, order)).Value;
Debug($"Estimated fee: {fee.Amount} {fee.Currency}"); security = self.securities[self._symbol]
request = self.order_factory.limit_order(self._symbol, 100, limit_price)
order = Order.create_order(request)
fee = security.fee_model.get_order_fee(OrderFeeParameters(security, order)).value
self.debug(f"Estimated fee: {fee.amount} {fee.currency}")
The estimate is a modeled fee. The fee model calculates it inside your algorithm with the current order and security values. In live trading, LEAN doesn't request the estimate from the brokerage. The fee that the brokerage charges can differ from the modeled fee, especially if the fee depends on the fill price or the fill quantity. To place the order after the estimate, pass the same request to the Orderorder method.
Check Buying Power
The following sections explain how to check if you have enough buying power to cover the initial margin requirements of an order before you place it.
Check Requirements of Regular Orders
To check if you have enough buying power for a regular order, create an order request with the order factory and convert it into an Order object. Then compare the absolute initial margin that the order requires with the buying power of your portfolio in the direction of the order. The initial margin is negative for sell orders and includes the modeled order fees. The buying power in the direction of the order includes the margin that the order frees when it reduces or reverses a position.
var security = Securities[_symbol];
var request = OrderFactory.MarketOrder(_symbol, 100);
var order = QuantConnect.Orders.Order.CreateOrder(request);
var initialMargin = security.BuyingPowerModel.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(Portfolio.CashBook, security, order)).Value;
if (Math.Abs(initialMargin) <= Portfolio.GetBuyingPower(_symbol, order.Direction))
{
Order(request);
} security = self.securities[self._symbol]
request = self.order_factory.market_order(self._symbol, 100)
order = Order.create_order(request)
initial_margin = security.buying_power_model.get_initial_margin_required_for_order(
InitialMarginRequiredForOrderParameters(self.portfolio.cash_book, security, order)).value
if abs(initial_margin) <= self.portfolio.get_buying_power(self._symbol, order.direction):
self.order(request)
The HasSufficientBuyingPowerForOrderhas_sufficient_buying_power_for_order method of the security buying power model requires an order ticket, so it only works for orders that you already submitted. When you submit the order, LEAN runs its own buying power check.
Check Requirements of Option Strategy Orders
To check if you have enough buying power for an Option strategy order, follow these steps:
- Create an
OptionStrategyobject with the strategy you want to trade. - Create the order requests of the strategy legs with the order factory and convert each request into an
Orderobject. - Call the
HasSufficientBuyingPowerForOrderhas_sufficient_buying_power_for_ordermethod of the portfolio with the list of orders. - If the result is sufficient, submit the order requests.
For example, create a Bull Put Spread strategy.
private Symbol _symbol;
public override void Initialize()
{
// Subscribe to option data and cache the canonical symbol to obtain the option data
_symbol = AddOption("SPY").Symbol;
}
public override void OnData(Slice slice)
{
// Trade on updated option chain data
if (!slice.OptionChains.TryGetValue(_symbol, out var chain))
{
return;
}
var itmStrike = chain.Max(x => x.Strike);
var otmStrike = chain.Min(x => x.Strike);
var expiry = chain.Min(x => x.Expiry);
var optionStrategy = OptionStrategies.BullPutSpread(_symbol, itmStrike, otmStrike, expiry); def initialize(self) -> None:
# Subscribe to option data and cache the canonical symbol to obtain the option data
self._symbol = self.add_option("SPY").symbol
def on_data(self, slice: Slice) -> None:
# Trade on updated option chain data
chain = slice.option_chains.get(self._symbol)
if not chain:
return
itm_strike = max(x.strike for x in chain)
otm_strike = min(x.strike for x in chain)
expiry = min(x.expiry for x in chain)
option_strategy = OptionStrategies.bull_put_spread(self._symbol, itm_strike, otm_strike, expiry)
var requests = OrderFactory.OptionStrategyOrder(optionStrategy, 2);
var orders = requests.Select(request => QuantConnect.Orders.Order.CreateOrder(request)).ToList(); requests = self.order_factory.option_strategy_order(option_strategy, 2)
orders = [Order.create_order(request) for request in requests]
var result = Portfolio.HasSufficientBuyingPowerForOrder(orders);
result = self.portfolio.has_sufficient_buying_power_for_order(orders)
The HasSufficientBuyingPowerForOrderhas_sufficient_buying_power_for_order method returns a HasSufficientBuyingPowerForOrderResult object, which has the following properties:
if (result.IsSufficient)
{
Order(requests);
}
else
{
Debug($"You don't have sufficient margin for this order: {result.Reason}");
}
} if result.is_sufficient:
self.order(requests)
else:
self.debug(f"You don't have sufficient margin for this order: {result.reason}")
The method combines the orders with your current holdings into position groups, so it accounts for strategy orders that reduce or close a position. If the strategy has a single leg, such as a naked put, follow the steps in the preceding section.
Examples
The following examples demonstrate common practices for using the order factory.
Example 1: Skip Expensive Rebalance Orders
The following algorithm rebalances an equal-weight portfolio every month. For each security, it creates a market order request for the difference between the target and current quantities and estimates the fee. The algorithm drops the orders whose fee exceeds 0.1% of the order value and submits the rest together, with the sell orders first.
public class OrderFactoryExampleAlgorithm : QCAlgorithm
{
private List<Symbol> _symbols;
public override void Initialize()
{
SetStartDate(2024, 1, 1);
SetEndDate(2024, 12, 31);
SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin);
_symbols = new[] { "SPY", "TLT", "GLD" }.Select(ticker => AddEquity(ticker, Resolution.Daily).Symbol).ToList();
Schedule.On(DateRules.MonthStart(_symbols[0]), TimeRules.AfterMarketOpen(_symbols[0], 30), Rebalance);
}
private void Rebalance()
{
var requests = new List<SubmitOrderRequest>();
foreach (var symbol in _symbols)
{
var quantity = CalculateOrderQuantity(symbol, 1m / _symbols.Count);
if (quantity == 0) continue;
var security = Securities[symbol];
var request = OrderFactory.MarketOrder(symbol, quantity);
// Estimate the fee without submitting the order.
var fee = security.FeeModel.GetOrderFee(new OrderFeeParameters(security, QuantConnect.Orders.Order.CreateOrder(request))).Value.Amount;
if (fee <= 0.001m * Math.Abs(quantity) * security.Price)
{
requests.Add(request);
}
}
// Place the sell orders first to free up buying power.
Order(requests.OrderBy(request => request.Quantity));
}
} class OrderFactoryExampleAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 1, 1)
self.set_end_date(2024, 12, 31)
self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
self._symbols = [self.add_equity(ticker, Resolution.DAILY).symbol for ticker in ["SPY", "TLT", "GLD"]]
self.schedule.on(self.date_rules.month_start(self._symbols[0]), self.time_rules.after_market_open(self._symbols[0], 30), self._rebalance)
def _rebalance(self) -> None:
requests = []
for symbol in self._symbols:
quantity = self.calculate_order_quantity(symbol, 1 / len(self._symbols))
if not quantity:
continue
security = self.securities[symbol]
request = self.order_factory.market_order(symbol, quantity)
# Estimate the fee without submitting the order.
fee = security.fee_model.get_order_fee(OrderFeeParameters(security, Order.create_order(request))).value.amount
if fee <= 0.001 * abs(quantity) * security.price:
requests.append(request)
# Place the sell orders first to free up buying power.
self.order(sorted(requests, key=lambda request: request.quantity))