Contingent Orders
One Cancels Other Orders
Place Orders
To place a set of OCO orders, create an order request for each order with the order factory. Pass a list of the requests to the OneCancelsOtherOrderone_cancels_other_order method. All the orders start working right away. When one of them fills, even partially, LEAN cancels the rest of the set. The method returns a list with the order ticket of each order.
// Exit an existing long position at the first price level the market reaches.
var price = Securities[_symbol].Price;
var tickets = OneCancelsOtherOrder(new List<SubmitOrderRequest>
{
OrderFactory.LimitOrder(_symbol, -100, price * 1.02m, tag: "Take profit"),
OrderFactory.StopMarketOrder(_symbol, -100, price * 0.99m, tag: "Stop loss")
}); # Exit an existing long position at the first price level the market reaches.
price = self.securities[self._symbol].price
tickets = self.one_cancels_other_order([
self.order_factory.limit_order(self._symbol, -100, price * 1.02, tag="Take profit"),
self.order_factory.stop_market_order(self._symbol, -100, price * 0.99, tag="Stop loss")
])
The following images show the two outcomes of an OCO set that exits a long position. In the first image, the price rises to the take-profit price, so the take-profit order fills and LEAN cancels the stop-loss order:
In the second image, the price falls to the stop price, so the stop-loss order fills and LEAN cancels the take-profit order:
Place Related Orders
Each order in an OCO set can trigger other orders. To trigger orders after an OCO order fills, call the Triggerstriggers or Bracketbracket method on its order request. Call the method before you relate the request with the other orders. The brokerage holds the triggered orders until their parent completely fills. If another order in the set fills first, LEAN cancels the parent and the orders it triggers. A triggered order can trigger other orders in turn, which are grandchildren of the OCO order.
// Two breakout entries that cancel each other, each with its own exit orders.
var breakoutUp = OrderFactory.StopMarketOrder(_symbol, 100, high).Bracket(high + range, low);
var breakoutDown = OrderFactory.StopMarketOrder(_symbol, -100, low).Bracket(low - range, high);
var tickets = OneCancelsOtherOrder(new List<SubmitOrderRequest> { breakoutUp, breakoutDown }); # Two breakout entries that cancel each other, each with its own exit orders. breakout_up = self.order_factory.stop_market_order(self._symbol, 100, high).bracket(high + range_, low) breakout_down = self.order_factory.stop_market_order(self._symbol, -100, low).bracket(low - range_, high) tickets = self.one_cancels_other_order([breakout_up, breakout_down])
The method returns the order ticket of each OCO order followed by the order tickets of the orders it triggers. For a complete example, see Custom Contingent Orders.
Monitor Orders
Each order in the set has its own order ticket, which fills and closes independently. To get the relationships of an order, use the Contingencycontingency property of the order ticket.
foreach (var ticket in tickets)
{
Debug($"{ticket.OrderId} held: {ticket.Contingency.IsWaitingForTrigger}; Quantity filled: {ticket.QuantityFilled}");
} for ticket in tickets:
self.debug(f"{ticket.order_id} held: {ticket.contingency.is_waiting_for_trigger}; Quantity filled: {ticket.quantity_filled}")
The Contingencycontingency property is an OrderContingency object, which has the following attributes:
The Linkslinks property holds a ContingencyLink object for each relationship of the order, which has the following attributes:
The GetOpenOrdersRemainingQuantityget_open_orders_remaining_quantity method of the transaction manager excludes the orders that wait for their parent to fill. At most one order of an OCO or OUO set fills, so the method includes only the largest remaining quantity of each set.
Update Orders
You can update any open order in the set. To update an order, pass an UpdateOrderFields object to the Updateupdate method on the OrderTicket, or call one of its update methods.
// Move the stop loss.
var response = tickets[1].UpdateStopPrice(price * 0.985m, "Updated stop loss");
if (response.IsSuccess)
{
Debug("Order updated successfully");
} # Move the stop loss.
response = tickets[1].update_stop_price(price * 0.985, "Updated stop loss")
if response.is_success:
self.debug("Order updated successfully")
Some brokerages don't support updates to contingent orders or to their quantity. To change these orders, cancel the set and place it again.
Cancel Orders
To cancel an order in the set, call the Cancelcancel method on the OrderTicket. LEAN also cancels the orders that depend on it. If you cancel a member of an OCO or OUO set, LEAN cancels the rest of the set.
// Cancel the take profit, which also cancels the stop loss.
var response = tickets[0].Cancel("Cancel take profit, which also cancels the stop loss");
if (response.IsSuccess)
{
Debug("Order successfully cancelled");
} # Cancel the take profit, which also cancels the stop loss.
response = tickets[0].cancel("Cancel take profit, which also cancels the stop loss")
if response.is_success:
self.debug("Order successfully cancelled")
The message of the cancel order event of each dependent order names the order that caused the cancellation.
Backtest Fills
In backtests, LEAN simulates how the brokerage manages the set with the following rules:
- If an OCO or OUO order and its siblings can all fill with the same data, LEAN fills the stop orders first. For a bracket order, the stop loss fills and LEAN cancels the take profit.
For more information about how LEAN models order fills in backtests, see Trade Fills.
Buying Power
When you place a set of contingent orders, LEAN checks the buying power of the orders that start working right away. In cash accounts, LEAN reserves the buying power of only the largest order in each set of OCO or OUO orders.
Brokerage Support
Each brokerage has a set of assets and order types they support. To avoid issues with contingent 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 contingent orders, see the Orders section of the brokerage model documentation.
The following brokerages support contingent orders:
Brokerages that support contingent orders can limit the contingency types, the number of orders in a set, the securities in a set, combo order members, and chains. If the brokerage model doesn't support a set of contingent orders, LEAN invalidates every order in the set.
Examples
The following examples demonstrate common practices for using one cancels other orders.
Example 1: Exit Each Trade at a Price Range
The following algorithm buys SPY each day at 10 AM Eastern Time when it has no position. After the market order fills, the algorithm places OCO exit orders. The take-profit order is 1% above the fill price and the stop-loss order is 1% below it. The first exit order that fills closes the position and cancels the other one.
public class OneCancelsOtherExampleAlgorithm : QCAlgorithm
{
private Symbol _spy;
public override void Initialize()
{
SetStartDate(2024, 1, 1);
SetEndDate(2024, 3, 31);
_spy = AddEquity("SPY", Resolution.Minute).Symbol;
Schedule.On(DateRules.EveryDay(_spy), TimeRules.At(10, 0), Enter);
}
private void Enter()
{
if (Portfolio.Invested || Transactions.GetOpenOrders(_spy).Count > 0)
{
return;
}
var fillPrice = MarketOrder(_spy, 100).AverageFillPrice;
OneCancelsOtherOrder(new List<SubmitOrderRequest>
{
OrderFactory.LimitOrder(_spy, -100, Math.Round(fillPrice * 1.01m, 2), tag: "Take profit"),
OrderFactory.StopMarketOrder(_spy, -100, Math.Round(fillPrice * 0.99m, 2), tag: "Stop loss")
});
}
} class OneCancelsOtherExampleAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 1, 1)
self.set_end_date(2024, 3, 31)
self._spy = self.add_equity("SPY", Resolution.MINUTE).symbol
self.schedule.on(self.date_rules.every_day(self._spy), self.time_rules.at(10, 0), self._enter)
def _enter(self) -> None:
if self.portfolio.invested or self.transactions.get_open_orders(self._spy):
return
fill_price = self.market_order(self._spy, 100).average_fill_price
self.one_cancels_other_order([
self.order_factory.limit_order(self._spy, -100, round(fill_price * 1.01, 2), tag="Take profit"),
self.order_factory.stop_market_order(self._spy, -100, round(fill_price * 0.99, 2), tag="Stop loss")
])
Example 2: Buy Back After the Take Profit
The following algorithm buys SPY each day at 10 AM Eastern Time when it has no position, and then places OCO exit orders. The take-profit order triggers a limit order that buys SPY back at the entry price. The buy-back order is a bracket order, so its exit orders are grandchildren of the take-profit order. If the stop loss fills first, LEAN cancels the take profit, the buy-back order, and its exit orders.
public class OneCancelsOtherGrandchildExampleAlgorithm : QCAlgorithm
{
private Symbol _spy;
public override void Initialize()
{
SetStartDate(2024, 1, 1);
SetEndDate(2024, 3, 31);
_spy = AddEquity("SPY", Resolution.Minute).Symbol;
Schedule.On(DateRules.EveryDay(_spy), TimeRules.At(10, 0), Enter);
}
private void Enter()
{
if (Portfolio.Invested || Transactions.GetOpenOrders(_spy).Count > 0)
{
return;
}
var fillPrice = MarketOrder(_spy, 100).AverageFillPrice;
var takeProfitPrice = Math.Round(fillPrice * 1.01m, 2);
var stopLossPrice = Math.Round(fillPrice * 0.99m, 2);
// The buy-back order and its exit orders wait for the take profit to fill.
var buyBack = OrderFactory.LimitOrder(_spy, 100, Math.Round(fillPrice, 2)).Bracket(takeProfitPrice, stopLossPrice);
OneCancelsOtherOrder(new List<SubmitOrderRequest>
{
OrderFactory.LimitOrder(_spy, -100, takeProfitPrice).Triggers(buyBack),
OrderFactory.StopMarketOrder(_spy, -100, stopLossPrice)
});
}
} class OneCancelsOtherGrandchildExampleAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 1, 1)
self.set_end_date(2024, 3, 31)
self._spy = self.add_equity("SPY", Resolution.MINUTE).symbol
self.schedule.on(self.date_rules.every_day(self._spy), self.time_rules.at(10, 0), self._enter)
def _enter(self) -> None:
if self.portfolio.invested or self.transactions.get_open_orders(self._spy):
return
fill_price = self.market_order(self._spy, 100).average_fill_price
take_profit_price = round(fill_price * 1.01, 2)
stop_loss_price = round(fill_price * 0.99, 2)
# The buy-back order and its exit orders wait for the take profit to fill.
buy_back = self.order_factory.limit_order(self._spy, 100, round(fill_price, 2)).bracket(take_profit_price, stop_loss_price)
self.one_cancels_other_order([
self.order_factory.limit_order(self._spy, -100, take_profit_price).triggers(buy_back),
self.order_factory.stop_market_order(self._spy, -100, stop_loss_price)
])