From 2985be9409b1be0c830017bfd66f10dacf8d26fb Mon Sep 17 00:00:00 2001 From: Alexandre Catarino Date: Fri, 15 May 2026 23:57:38 +0100 Subject: [PATCH] Add equity permanent portfolio, dual momentum, and DCA templates --- .../equity-dollar-cost-averaging/Main.cs | 115 ++++++++++++++++++ .../equity-dual-momentum-asset-class/Main.cs | 107 ++++++++++++++++ .../csharp/equity-permanent-portfolio/Main.cs | 105 ++++++++++++++++ project-templates/csharp/templates.json | 24 ++++ .../equity-dollar-cost-averaging/main.py | 39 ++++++ .../equity-dual-momentum-asset-class/main.py | 32 +++++ .../python/equity-permanent-portfolio/main.py | 34 ++++++ project-templates/python/templates.json | 24 ++++ project-templates/template-ideas.json | 39 ------ 9 files changed, 480 insertions(+), 39 deletions(-) create mode 100644 project-templates/csharp/equity-dollar-cost-averaging/Main.cs create mode 100644 project-templates/csharp/equity-dual-momentum-asset-class/Main.cs create mode 100644 project-templates/csharp/equity-permanent-portfolio/Main.cs create mode 100644 project-templates/python/equity-dollar-cost-averaging/main.py create mode 100644 project-templates/python/equity-dual-momentum-asset-class/main.py create mode 100644 project-templates/python/equity-permanent-portfolio/main.py diff --git a/project-templates/csharp/equity-dollar-cost-averaging/Main.cs b/project-templates/csharp/equity-dollar-cost-averaging/Main.cs new file mode 100644 index 0000000000..b13b93f8f0 --- /dev/null +++ b/project-templates/csharp/equity-dollar-cost-averaging/Main.cs @@ -0,0 +1,115 @@ +#region imports + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Globalization; + using System.Drawing; + using QuantConnect; + using QuantConnect.Algorithm.Framework; + using QuantConnect.Algorithm.Framework.Selection; + using QuantConnect.Algorithm.Framework.Alphas; + using QuantConnect.Algorithm.Framework.Portfolio; + using QuantConnect.Algorithm.Framework.Portfolio.SignalExports; + using QuantConnect.Algorithm.Framework.Execution; + using QuantConnect.Algorithm.Framework.Risk; + using QuantConnect.Algorithm.Selection; + using QuantConnect.Api; + using QuantConnect.Parameters; + using QuantConnect.Benchmarks; + using QuantConnect.Brokerages; + using QuantConnect.Commands; + using QuantConnect.Configuration; + using QuantConnect.Util; + using QuantConnect.Interfaces; + using QuantConnect.Algorithm; + using QuantConnect.Indicators; + using QuantConnect.Data; + using QuantConnect.Data.Auxiliary; + using QuantConnect.Data.Consolidators; + using QuantConnect.Data.Custom; + using QuantConnect.Data.Custom.IconicTypes; + using QuantConnect.DataSource; + using QuantConnect.Data.Fundamental; + using QuantConnect.Data.Market; + using QuantConnect.Data.Shortable; + using QuantConnect.Data.UniverseSelection; + using QuantConnect.Notifications; + using QuantConnect.Orders; + using QuantConnect.Orders.Fees; + using QuantConnect.Orders.Fills; + using QuantConnect.Orders.OptionExercise; + using QuantConnect.Orders.Slippage; + using QuantConnect.Orders.TimeInForces; + using QuantConnect.Python; + using QuantConnect.Scheduling; + using QuantConnect.Securities; + using QuantConnect.Securities.Equity; + using QuantConnect.Securities.Future; + using QuantConnect.Securities.Option; + using QuantConnect.Securities.Positions; + using QuantConnect.Securities.Forex; + using QuantConnect.Securities.Crypto; + using QuantConnect.Securities.CryptoFuture; + using QuantConnect.Securities.IndexOption; + using QuantConnect.Securities.Interfaces; + using QuantConnect.Securities.Volatility; + using QuantConnect.Storage; + using QuantConnect.Statistics; + using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; + using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; + using Calendar = QuantConnect.Data.Consolidators.Calendar; +#endregion +public class VooWeeklyDcaAlgorithm : QCAlgorithm +{ + private const decimal _dollarAmount = 5000m; + + private Equity _voo; + + public override void Initialize() + { + SetStartDate(2022, 1, 1); + SetEndDate(2024, 12, 31); + SetCash(200000); + + Settings.SeedInitialPrices = true; + + _voo = AddEquity("VOO", Resolution.Minute); + + Schedule.On( + DateRules.WeekStart(_voo.Symbol), + TimeRules.AfterMarketOpen(_voo.Symbol, 1), + BuyVoo); + + } + + public override void OnWarmupFinished() + { + BuyVoo(); + } + + public override void OnOrderEvent(OrderEvent orderEvent) + { + if (orderEvent.Status == OrderStatus.Filled) + { + Plot("Fills", "VOO", orderEvent.FillPrice * orderEvent.FillQuantity); + } + } + + private void BuyVoo() + { + if (Portfolio.Cash < _dollarAmount) + { + return; + } + + var holdingsValue = _voo.Holdings.HoldingsValue; + var targetFraction = (holdingsValue + _dollarAmount) / Portfolio.TotalPortfolioValue; + var quantity = CalculateOrderQuantity(_voo.Symbol, targetFraction); + + if (quantity > 0) + { + MarketOrder(_voo.Symbol, quantity); + } + } +} diff --git a/project-templates/csharp/equity-dual-momentum-asset-class/Main.cs b/project-templates/csharp/equity-dual-momentum-asset-class/Main.cs new file mode 100644 index 0000000000..249f83ee8d --- /dev/null +++ b/project-templates/csharp/equity-dual-momentum-asset-class/Main.cs @@ -0,0 +1,107 @@ +#region imports + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Globalization; + using System.Drawing; + using QuantConnect; + using QuantConnect.Algorithm.Framework; + using QuantConnect.Algorithm.Framework.Selection; + using QuantConnect.Algorithm.Framework.Alphas; + using QuantConnect.Algorithm.Framework.Portfolio; + using QuantConnect.Algorithm.Framework.Portfolio.SignalExports; + using QuantConnect.Algorithm.Framework.Execution; + using QuantConnect.Algorithm.Framework.Risk; + using QuantConnect.Algorithm.Selection; + using QuantConnect.Api; + using QuantConnect.Parameters; + using QuantConnect.Benchmarks; + using QuantConnect.Brokerages; + using QuantConnect.Commands; + using QuantConnect.Configuration; + using QuantConnect.Util; + using QuantConnect.Interfaces; + using QuantConnect.Algorithm; + using QuantConnect.Indicators; + using QuantConnect.Data; + using QuantConnect.Data.Auxiliary; + using QuantConnect.Data.Consolidators; + using QuantConnect.Data.Custom; + using QuantConnect.Data.Custom.IconicTypes; + using QuantConnect.DataSource; + using QuantConnect.Data.Fundamental; + using QuantConnect.Data.Market; + using QuantConnect.Data.Shortable; + using QuantConnect.Data.UniverseSelection; + using QuantConnect.Notifications; + using QuantConnect.Orders; + using QuantConnect.Orders.Fees; + using QuantConnect.Orders.Fills; + using QuantConnect.Orders.OptionExercise; + using QuantConnect.Orders.Slippage; + using QuantConnect.Orders.TimeInForces; + using QuantConnect.Python; + using QuantConnect.Scheduling; + using QuantConnect.Securities; + using QuantConnect.Securities.Equity; + using QuantConnect.Securities.Future; + using QuantConnect.Securities.Option; + using QuantConnect.Securities.Positions; + using QuantConnect.Securities.Forex; + using QuantConnect.Securities.Crypto; + using QuantConnect.Securities.CryptoFuture; + using QuantConnect.Securities.IndexOption; + using QuantConnect.Securities.Interfaces; + using QuantConnect.Securities.Volatility; + using QuantConnect.Storage; + using QuantConnect.Statistics; + using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; + using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; + using Calendar = QuantConnect.Data.Consolidators.Calendar; +#endregion +public class DualMomentumAlgorithm : QCAlgorithm +{ + public override void Initialize() + { + SetStartDate(2022, 1, 1); + SetEndDate(2024, 12, 31); + SetCash(100000); + Settings.SeedInitialPrices = true; + Settings.AutomaticIndicatorWarmUp = true; + + var tickers = new[] { "SPY", "EFA", "AGG" }; + foreach (var ticker in tickers) + { + dynamic equity = AddEquity(ticker, Resolution.Minute); + equity.Rocp = ROCP(equity.Symbol, 252, Resolution.Daily); + } + + Schedule.On(DateRules.MonthStart("SPY"), TimeRules.BeforeMarketOpen("SPY", 5), Rebalance); + } + + public override void OnWarmupFinished() + { + Rebalance(); + } + + private void Rebalance() + { + var returns = new Dictionary(); + foreach (dynamic security in Securities.Values) + { + if (!security.Rocp.IsReady) + { + continue; + } + returns[security.Symbol] = security.Rocp.Current.Value; + } + if (returns.Count == 0) + { + return; + } + + var winner = returns.OrderByDescending(kvp => kvp.Value).First().Key; + SetHoldings(new List { new PortfolioTarget(winner, 1m) }, true); + } +} diff --git a/project-templates/csharp/equity-permanent-portfolio/Main.cs b/project-templates/csharp/equity-permanent-portfolio/Main.cs new file mode 100644 index 0000000000..589112ee9c --- /dev/null +++ b/project-templates/csharp/equity-permanent-portfolio/Main.cs @@ -0,0 +1,105 @@ +#region imports + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Globalization; + using System.Drawing; + using QuantConnect; + using QuantConnect.Algorithm.Framework; + using QuantConnect.Algorithm.Framework.Selection; + using QuantConnect.Algorithm.Framework.Alphas; + using QuantConnect.Algorithm.Framework.Portfolio; + using QuantConnect.Algorithm.Framework.Portfolio.SignalExports; + using QuantConnect.Algorithm.Framework.Execution; + using QuantConnect.Algorithm.Framework.Risk; + using QuantConnect.Algorithm.Selection; + using QuantConnect.Api; + using QuantConnect.Parameters; + using QuantConnect.Benchmarks; + using QuantConnect.Brokerages; + using QuantConnect.Commands; + using QuantConnect.Configuration; + using QuantConnect.Util; + using QuantConnect.Interfaces; + using QuantConnect.Algorithm; + using QuantConnect.Indicators; + using QuantConnect.Data; + using QuantConnect.Data.Auxiliary; + using QuantConnect.Data.Consolidators; + using QuantConnect.Data.Custom; + using QuantConnect.Data.Custom.IconicTypes; + using QuantConnect.DataSource; + using QuantConnect.Data.Fundamental; + using QuantConnect.Data.Market; + using QuantConnect.Data.Shortable; + using QuantConnect.Data.UniverseSelection; + using QuantConnect.Notifications; + using QuantConnect.Orders; + using QuantConnect.Orders.Fees; + using QuantConnect.Orders.Fills; + using QuantConnect.Orders.OptionExercise; + using QuantConnect.Orders.Slippage; + using QuantConnect.Orders.TimeInForces; + using QuantConnect.Python; + using QuantConnect.Scheduling; + using QuantConnect.Securities; + using QuantConnect.Securities.Equity; + using QuantConnect.Securities.Future; + using QuantConnect.Securities.Option; + using QuantConnect.Securities.Positions; + using QuantConnect.Securities.Forex; + using QuantConnect.Securities.Crypto; + using QuantConnect.Securities.CryptoFuture; + using QuantConnect.Securities.IndexOption; + using QuantConnect.Securities.Interfaces; + using QuantConnect.Securities.Volatility; + using QuantConnect.Storage; + using QuantConnect.Statistics; + using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; + using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; + using Calendar = QuantConnect.Data.Consolidators.Calendar; +#endregion +public class PermanentPortfolioAlgorithm : QCAlgorithm +{ + private readonly List _targets = new(); + private const decimal _weight = 0.25m; + private const decimal _driftThreshold = 0.03m; + + public override void Initialize() + { + SetStartDate(2022, 1, 1); + SetEndDate(2024, 12, 31); + SetCash(100000); + Settings.SeedInitialPrices = true; + + foreach (var ticker in new[] { "SPY", "TLT", "GLD", "SHY" }) + { + var symbol = AddEquity(ticker, Resolution.Minute).Symbol; + _targets.Add(new PortfolioTarget(symbol, _weight)); + } + + Schedule.On( + DateRules.YearStart("SPY", 2), + TimeRules.AfterMarketOpen("SPY", 5), + Rebalance); + } + + public override void OnWarmupFinished() + { + SetHoldings(_targets, liquidateExistingHoldings: true); + } + + private void Rebalance() + { + foreach (var target in _targets) + { + var currentWeight = Portfolio[target.Symbol].HoldingsValue / Portfolio.TotalHoldingsValue; + if (Math.Abs(currentWeight - _weight) > _driftThreshold) + { + SetHoldings(_targets, liquidateExistingHoldings: true); + return; + } + } + } +} diff --git a/project-templates/csharp/templates.json b/project-templates/csharp/templates.json index d615550a22..1b783c4cdb 100644 --- a/project-templates/csharp/templates.json +++ b/project-templates/csharp/templates.json @@ -583,6 +583,30 @@ "spec": "Asset: IWM. Minute resolution. 2022-01-01 to 2024-12-31, $100k. WilliamsPercentR(14). Long when %R < -80; exit when %R > -20. Demonstrates: Williams %R indicator, oversold reversion logic, plotting against -80/-20 reference lines. Edge case: hold cap of 5 trading days.", "tags": ["Equity", "indicators", "williams-r", "oscillator"], "reference_template": "equities-static-universe" + }, + { + "folder": "equity-permanent-portfolio", + "name": "Equity Permanent Portfolio", + "description": "Holds 25% each of SPY, TLT, GLD, and SHY with annual rebalance — Browne's Permanent Portfolio.", + "spec": "Assets: SPY, TLT, GLD, SHY. Minute. 2022-01-01 to 2024-12-31, $100k. Equal-weight 25% on first bar; annual rebalance every January 2nd. Demonstrates: 4-asset allocation, annual scheduled event, scheduled-event rule chaining. Edge case: skip rebalance if drift < 3% from target.", + "tags": ["Equity", "permanent-portfolio", "asset-allocation", "scheduled-event"], + "reference_template": "equities-static-universe" + }, + { + "folder": "equity-dual-momentum-asset-class", + "name": "Equity Dual Momentum", + "description": "Implements Antonacci dual momentum across SPY, EFA, and AGG: pick the top 12-month return, fall back to AGG if absolute momentum is negative.", + "spec": "Assets: SPY, EFA, AGG. Minute resolution. 2022-01-01 to 2024-12-31, $100k. Monthly: compute 12-month return for each; pick the highest; if highest <= AGG return then hold AGG; else hold winner 100%. Demonstrates: history-based momentum, monthly schedule, conditional hold. Edge case: rebalance only on first trading day of month.", + "tags": ["Equity", "dual-momentum", "asset-allocation", "history"], + "reference_template": "history-and-etf-universe" + }, + { + "folder": "equity-dollar-cost-averaging", + "name": "Equity Dollar Cost Averaging", + "description": "Buys $5,000 worth of VOO every Monday morning regardless of price, accumulating shares over the backtest.", + "spec": "Asset: VOO. Minute. 2022-01-01 to 2024-12-31, $200k starting cash. Schedule: every Monday after market open buy VOO with $5,000 of cash via market_order using calculate_order_quantity. Demonstrates: weekly schedule, fixed-dollar buys, calculate_order_quantity, no liquidation. Edge case: skip if insufficient cash; never sell.", + "tags": ["Equity", "dca", "scheduled-event", "buy-and-hold"], + "reference_template": "equities-static-universe" } ] } diff --git a/project-templates/python/equity-dollar-cost-averaging/main.py b/project-templates/python/equity-dollar-cost-averaging/main.py new file mode 100644 index 0000000000..ca0a46b473 --- /dev/null +++ b/project-templates/python/equity-dollar-cost-averaging/main.py @@ -0,0 +1,39 @@ +# region imports +from AlgorithmImports import * +# endregion + +class VooWeeklyDcaAlgorithm(QCAlgorithm): + _dollar_amount = 5000 + + def initialize(self) -> None: + self.set_start_date(2022, 1, 1) + self.set_end_date(2024, 12, 31) + self.set_cash(200000) + + self.settings.seed_initial_prices = True + + self._voo = self.add_equity("VOO", Resolution.MINUTE) + + self.schedule.on( + self.date_rules.week_start(self._voo.symbol), + self.time_rules.after_market_open(self._voo.symbol, 1), + self._buy_voo + ) + + def on_warmup_finished(self) -> None: + self._buy_voo() + + def on_order_event(self, order_event: OrderEvent) -> None: + if order_event.status == OrderStatus.FILLED: + self.plot("Fills", "VOO", order_event.fill_price * order_event.fill_quantity) + + def _buy_voo(self) -> None: + if self.portfolio.cash < self._dollar_amount: + return + + holdings_value = self._voo.holdings.holdings_value + target_fraction = (holdings_value + self._dollar_amount) / self.portfolio.total_portfolio_value + quantity = self.calculate_order_quantity(self._voo.symbol, target_fraction) + + if quantity > 0: + self.market_order(self._voo.symbol, quantity) diff --git a/project-templates/python/equity-dual-momentum-asset-class/main.py b/project-templates/python/equity-dual-momentum-asset-class/main.py new file mode 100644 index 0000000000..5227233675 --- /dev/null +++ b/project-templates/python/equity-dual-momentum-asset-class/main.py @@ -0,0 +1,32 @@ +# region imports +from AlgorithmImports import * +# endregion + +class DualMomentumAlgorithm(QCAlgorithm): + + def initialize(self) -> None: + self.set_start_date(2022, 1, 1) + self.set_end_date(2024, 12, 31) + self.set_cash(100000) + self.settings.seed_initial_prices = True + self.settings.automatic_indicator_warm_up = True + + for ticker in ["SPY", "EFA", "AGG"]: + equity = self.add_equity(ticker, Resolution.MINUTE) + equity.rocp = self.rocp(equity.symbol, 252, Resolution.DAILY) + + self.schedule.on( + self.date_rules.month_start("SPY"), + self.time_rules.before_market_open("SPY", 5), + self._rebalance + ) + + def on_warmup_finished(self) -> None: + self._rebalance() + + def _rebalance(self) -> None: + returns = {sym: sec.rocp.current.value for sym, sec in self.securities.items() if sec.rocp.is_ready} + if not returns: + return + winner = max(returns, key=lambda s: returns[s]) + self.set_holdings([PortfolioTarget(winner, 1.0)], liquidate_existing_holdings=True) diff --git a/project-templates/python/equity-permanent-portfolio/main.py b/project-templates/python/equity-permanent-portfolio/main.py new file mode 100644 index 0000000000..a0986a89a3 --- /dev/null +++ b/project-templates/python/equity-permanent-portfolio/main.py @@ -0,0 +1,34 @@ +# region imports +from AlgorithmImports import * +# endregion + +class PermanentPortfolioAlgorithm(QCAlgorithm): + _targets: list[PortfolioTarget] = [] + _weight: float = 0.25 + _drift_threshold = 0.03 + + def initialize(self) -> None: + self.set_start_date(2022, 1, 1) + self.set_end_date(2024, 12, 31) + self.set_cash(100000) + self.settings.seed_initial_prices = True + + for ticker in {"SPY", "TLT", "GLD", "SHY"}: + symbol = self.add_equity(ticker, Resolution.MINUTE).symbol + self._targets.append(PortfolioTarget(symbol, self._weight)) + + self.schedule.on( + self.date_rules.year_start("SPY", 2), + self.time_rules.after_market_open("SPY", 5), + self._rebalance + ) + + def on_warmup_finished(self) -> None: + self.set_holdings(self._targets, liquidate_existing_holdings=True) + + def _rebalance(self) -> None: + for target in self._targets: + current_weight = self.portfolio[target.symbol].holdings_value / self.portfolio.total_holdings_value + if abs(current_weight - self._weight) > self._drift_threshold: + self.set_holdings(self._targets, liquidate_existing_holdings=True) + return \ No newline at end of file diff --git a/project-templates/python/templates.json b/project-templates/python/templates.json index e34ec9a024..b71516aec4 100644 --- a/project-templates/python/templates.json +++ b/project-templates/python/templates.json @@ -583,6 +583,30 @@ "spec": "Asset: IWM. Minute resolution. 2022-01-01 to 2024-12-31, $100k. WilliamsPercentR(14). Long when %R < -80; exit when %R > -20. Demonstrates: Williams %R indicator, oversold reversion logic, plotting against -80/-20 reference lines. Edge case: hold cap of 5 trading days.", "tags": ["Equity", "indicators", "williams-r", "oscillator"], "reference_template": "equities-static-universe" + }, + { + "folder": "equity-permanent-portfolio", + "name": "Equity Permanent Portfolio", + "description": "Holds 25% each of SPY, TLT, GLD, and SHY with annual rebalance — Browne's Permanent Portfolio.", + "spec": "Assets: SPY, TLT, GLD, SHY. Minute. 2022-01-01 to 2024-12-31, $100k. Equal-weight 25% on first bar; annual rebalance every January 2nd. Demonstrates: 4-asset allocation, annual scheduled event, scheduled-event rule chaining. Edge case: skip rebalance if drift < 3% from target.", + "tags": ["Equity", "permanent-portfolio", "asset-allocation", "scheduled-event"], + "reference_template": "equities-static-universe" + }, + { + "folder": "equity-dual-momentum-asset-class", + "name": "Equity Dual Momentum", + "description": "Implements Antonacci dual momentum across SPY, EFA, and AGG: pick the top 12-month return, fall back to AGG if absolute momentum is negative.", + "spec": "Assets: SPY, EFA, AGG. Minute resolution. 2022-01-01 to 2024-12-31, $100k. Monthly: compute 12-month return for each; pick the highest; if highest <= AGG return then hold AGG; else hold winner 100%. Demonstrates: history-based momentum, monthly schedule, conditional hold. Edge case: rebalance only on first trading day of month.", + "tags": ["Equity", "dual-momentum", "asset-allocation", "history"], + "reference_template": "history-and-etf-universe" + }, + { + "folder": "equity-dollar-cost-averaging", + "name": "Equity Dollar Cost Averaging", + "description": "Buys $5,000 worth of VOO every Monday morning regardless of price, accumulating shares over the backtest.", + "spec": "Asset: VOO. Minute. 2022-01-01 to 2024-12-31, $200k starting cash. Schedule: every Monday after market open buy VOO with $5,000 of cash via market_order using calculate_order_quantity. Demonstrates: weekly schedule, fixed-dollar buys, calculate_order_quantity, no liquidation. Edge case: skip if insufficient cash; never sell.", + "tags": ["Equity", "dca", "scheduled-event", "buy-and-hold"], + "reference_template": "equities-static-universe" } ] } diff --git a/project-templates/template-ideas.json b/project-templates/template-ideas.json index 69fb8e84d8..752336cbd9 100644 --- a/project-templates/template-ideas.json +++ b/project-templates/template-ideas.json @@ -104,32 +104,6 @@ ], "reference_template": "history-and-etf-universe" }, - { - "folder": "equity-dual-momentum-asset-class", - "name": "Equity Dual Momentum", - "description": "Implements Antonacci dual momentum across SPY, EFA, and AGG: pick the top 12-month return, fall back to AGG if absolute momentum is negative.", - "spec": "Assets: SPY, EFA, AGG. Minute resolution. 2022-01-01 to 2024-12-31, $100k. Monthly: compute 12-month return for each; pick the highest; if highest <= AGG return then hold AGG; else hold winner 100%. Demonstrates: history-based momentum, monthly schedule, conditional hold. Edge case: rebalance only on first trading day of month.", - "tags": [ - "Equity", - "dual-momentum", - "asset-allocation", - "history" - ], - "reference_template": "history-and-etf-universe" - }, - { - "folder": "equity-permanent-portfolio", - "name": "Equity Permanent Portfolio", - "description": "Holds 25% each of SPY, TLT, GLD, and SHY with annual rebalance — Browne's Permanent Portfolio.", - "spec": "Assets: SPY, TLT, GLD, SHY. Daily. 2022-01-01 to 2024-12-31, $100k. Equal-weight 25% on first bar; annual rebalance every January 2nd. Demonstrates: 4-asset allocation, annual scheduled event, scheduled-event rule chaining. Edge case: skip rebalance if drift < 3% from target.", - "tags": [ - "Equity", - "permanent-portfolio", - "asset-allocation", - "scheduled-event" - ], - "reference_template": "equities-static-universe" - }, { "folder": "equity-vix-timed-spy", "name": "Equity VIX-Timed SPY", @@ -156,19 +130,6 @@ ], "reference_template": "equities-static-universe" }, - { - "folder": "equity-dollar-cost-averaging", - "name": "Equity Dollar Cost Averaging", - "description": "Buys $5,000 worth of VOO every Monday morning regardless of price, accumulating shares over the backtest.", - "spec": "Asset: VOO. Minute. 2022-01-01 to 2024-12-31, $200k starting cash. Schedule: every Monday after market open buy VOO with $5,000 of cash via market_order using calculate_order_quantity. Demonstrates: weekly schedule, fixed-dollar buys, calculate_order_quantity, no liquidation. Edge case: skip if insufficient cash; never sell.", - "tags": [ - "Equity", - "dca", - "scheduled-event", - "buy-and-hold" - ], - "reference_template": "equities-static-universe" - }, { "folder": "equity-gap-and-go", "name": "Equity Gap and Go",