From 71c5520ad89a499f07c24b73b34d67e8713a8ad9 Mon Sep 17 00:00:00 2001 From: Alexandre Catarino Date: Fri, 25 Sep 2026 13:28:23 +0100 Subject: [PATCH] Add grandchild orders to One Triggers Other Orders Co-Authored-By: Claude Opus 5.5 (1M context) --- .../02 Place Orders.html | 4 +- .../99 Examples.html | 54 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/03 Writing Algorithms/22 Trading and Orders/03 Order Types/13 Contingent Orders/03 One Triggers Other Orders/02 Place Orders.html b/03 Writing Algorithms/22 Trading and Orders/03 Order Types/13 Contingent Orders/03 One Triggers Other Orders/02 Place Orders.html index 9b225a99c9..986ffab6aa 100644 --- a/03 Writing Algorithms/22 Trading and Orders/03 Order Types/13 Contingent Orders/03 One Triggers Other Orders/02 Place Orders.html +++ b/03 Writing Algorithms/22 Trading and Orders/03 Order Types/13 Contingent Orders/03 One Triggers Other Orders/02 Place Orders.html @@ -16,7 +16,7 @@ ]) -

The child orders are independent of each other and can be for different securities. To relate the child orders, pass them through the OrderFactory.OneCancelsOtherorder_factory.one_cancels_other or OrderFactory.OneUpdatesOtherorder_factory.one_updates_other method first, as described in Custom Contingent Orders. A child order can trigger other orders in turn. To create a chain, call the Triggerstriggers method on the child order request.

+

The child orders are independent of each other and can be for different securities. To relate the child orders, pass them through the OrderFactory.OneCancelsOtherorder_factory.one_cancels_other or OrderFactory.OneUpdatesOtherorder_factory.one_updates_other method first, as described in Custom Contingent Orders. A child order can trigger its own child orders, which are grandchild orders of the parent. To create a chain, call the Triggerstriggers method on the child order request.

// The IBM order triggers the BAC order once it fills.
@@ -27,6 +27,8 @@
 tickets = self.one_triggers_other_order(parent, [child])
+

The brokerage holds the grandchild order until the child order completely fills. The method returns the order tickets in the order parent, child, and grandchild, with each parent before the orders it triggers. If you cancel the parent, LEAN cancels the child and the grandchild.

+

The following images show the two outcomes of an OTO set. In the first image, the parent order fills and triggers the child order, which fills later:

An OTO set has a parent buy limit order at 45,000 and a child sell limit order at 58,000. The brokerage holds the child order until the price falls to 45,000 and the parent fills. The price then rises to 58,000 and the child order fills. diff --git a/03 Writing Algorithms/22 Trading and Orders/03 Order Types/13 Contingent Orders/03 One Triggers Other Orders/99 Examples.html b/03 Writing Algorithms/22 Trading and Orders/03 Order Types/13 Contingent Orders/03 One Triggers Other Orders/99 Examples.html index 5531652dc3..776e36f7a0 100644 --- a/03 Writing Algorithms/22 Trading and Orders/03 Order Types/13 Contingent Orders/03 One Triggers Other Orders/99 Examples.html +++ b/03 Writing Algorithms/22 Trading and Orders/03 Order Types/13 Contingent Orders/03 One Triggers Other Orders/99 Examples.html @@ -43,3 +43,57 @@

Example 1: Trail the Exit After a Limit Entry

self.order_factory.trailing_stop_order(self._spy, -100, 0.03, True) ]) + +

Example 2: Enter, Take Profit, and Re-Enter

+

The following algorithm trades SPY in a price range with a chain of three orders. The parent buys SPY 1% below the close price. When the parent fills, its child sells the position 1% above the close price. When the child fills, the grandchild buys SPY again at the entry price. While the algorithm holds the position of the grandchild, it places a new take-profit order.

+
+
public class OneTriggersOtherChainExampleAlgorithm : QCAlgorithm
+{
+    private Symbol _spy;
+
+    public override void Initialize()
+    {
+        SetStartDate(2024, 1, 1);
+        SetEndDate(2024, 12, 31);
+        _spy = AddEquity("SPY", Resolution.Daily).Symbol;
+    }
+
+    public override void OnData(Slice slice)
+    {
+        if (Transactions.GetOpenOrders(_spy).Count > 0 || !slice.Bars.TryGetValue(_spy, out var bar))
+        {
+            return;
+        }
+        var entryPrice = Math.Round(bar.Close * 0.99m, 2);
+        var exitPrice = Math.Round(bar.Close * 1.01m, 2);
+        if (Portfolio[_spy].Invested)
+        {
+            LimitOrder(_spy, -Portfolio[_spy].Quantity, exitPrice);
+            return;
+        }
+        // The take profit triggers the re-entry, which is a grandchild of the entry.
+        var takeProfit = OrderFactory.LimitOrder(_spy, -100, exitPrice).Triggers(OrderFactory.LimitOrder(_spy, 100, entryPrice));
+        OneTriggersOtherOrder(OrderFactory.LimitOrder(_spy, 100, entryPrice), new List<SubmitOrderRequest> { takeProfit });
+    }
+}
+
class OneTriggersOtherChainExampleAlgorithm(QCAlgorithm):
+
+    def initialize(self) -> None:
+        self.set_start_date(2024, 1, 1)
+        self.set_end_date(2024, 12, 31)
+        self._spy = self.add_equity("SPY", Resolution.DAILY).symbol
+
+    def on_data(self, slice: Slice) -> None:
+        bar = slice.bars.get(self._spy)
+        if self.transactions.get_open_orders(self._spy) or not bar:
+            return
+        entry_price = round(bar.close * 0.99, 2)
+        exit_price = round(bar.close * 1.01, 2)
+        if self.portfolio[self._spy].invested:
+            self.limit_order(self._spy, -self.portfolio[self._spy].quantity, exit_price)
+            return
+        # The take profit triggers the re-entry, which is a grandchild of the entry.
+        take_profit = self.order_factory.limit_order(self._spy, -100, exit_price).triggers(
+            self.order_factory.limit_order(self._spy, 100, entry_price))
+        self.one_triggers_other_order(self.order_factory.limit_order(self._spy, 100, entry_price), [take_profit])
+