Case study · Backtest
🗓 Backtest period: 2020-01-01..2025-10-08
Spec ID: spec-continuous-macro-stress-style-timing-us-etfs-1786122720 · Generated: 2026-08-07 17:54 UTC
Cluster: Tactical Allocation · Sub Cluster: Macro-Stress Growth Defensive Etf Timing
Dynamically tilts a U.S. ETF portfolio between growth/technology and defensive/value-income baskets using a continuous macro-stress score built from rates, equity drawdown, VIX regime, VIX relief, and recent growth extension. The score is transformed through a tanh tilt and EWMA-smoothed before rebalancing.
The trade expresses the idea that growth-vs-defensive leadership is partly state-dependent. Falling long rates, equity drawdown relief, and volatility stress relief can improve the relative payoff to long-duration growth assets, while crowded growth performance in low-volatility/rate-quiet regimes can make defensive/value-income exposure more attractive.
The strategy does not forecast single-name alpha. It rotates between diversified ETF style baskets, aiming to capture broad regime changes while avoiding binary all-in/all-out timing through continuous scoring and slow weight smoothing.
[code omitted from public view]
| Param | Value | Notes |
|---|---|---|
| Strategy variant | Main selected smooth-score policy | No optional bond/credit overlay. |
| Growth basket | QQQ, XLK, VGT, SPYG, VUG | Equal-weight within basket. |
| Defensive basket | SCHD, VYM, VTV, FDVV, COWZ | Equal-weight within basket. |
| Signal-only ETF | SPY | Used for drawdown feature; not part of traded basket in the target allocation. |
| Macro inputs | DGS10, VIX | 10Y yield and volatility regime/relief inputs. |
| Backtest window | 2020-01-01 to 2025-10-08 | Daily bars. |
| alpha | 0.50 | Blend between rate relief and SPY drawdown core score. |
| lambda_s | 0.50 | Weight on stress interaction score. |
| lambda_c | 0.05 | Penalty on crowded growth/low-volatility score. |
| MaxTilt | 0.50 | Growth weight range is theoretically 0% to 100% before any platform position caps. |
| tau_w | 0.75 | Tanh score-temperature for target weight. |
| eta | 0.05 | EWMA smoothing speed for actual growth weight. |
| softplus_tau | 1.00 | Softplus smoothing parameter. |
| Rebalance | Daily close | Continuous target update and close rebalance convention. |
| Trading cost assumption | 10 bps paper turnover-cost parameter; platform costs include $0.004/share commission, $1 minimum, 1% max commission, 0 bps slippage | The code notes cost_bps is parsed for paper fidelity and may not be applied as explicit trade slippage/commission in generated orders. |
| Price policy | Real ETF closes for execution | Short missing signal gaps may be forward-filled for analysis features only, not for execution prices. |
| # | Concern | Status |
|---|---|---|
| 1 | Close-derived ETF, SPY, VIX, and DGS10 features | Uses information through the signal date only; no future returns are required for feature construction. |
| 2 | Execution timing | Rebalances at the close under the platform close/MOC convention; no same-day open execution from same-day close signals is specified. |
| 3 | Expanding z-scores and running peaks | Expanding calculations are causal and initialized with available warmup history only. |
| 4 | Macro data availability | DGS10 uses an observation-date point-in-time filter; release-time lag risk remains if the database timestamp is only date-level. |
| 5 | Missing prices | Forward-fill is limited to analysis signal construction; execution prices must be real closes or trades are skipped. |
cost_bps parameter is parsed for paper fidelity while returned trades do not directly include that turnover-cost formula; realized costs may therefore differ from the paper's cost convention.The backtest produced a positive total return of 111.24% with Sharpe 0.92, Sortino 1.20, and beta to SPY of 0.76. Drawdown control was broadly in line with the paper-style objective, with max drawdown of -31.61%, but realized volatility was still equity-like at 21.08%. The high win rate and profit factor are favorable diagnostics, while the 13,932 trades highlight meaningful implementation and cost sensitivity.
Recomputed here from the run's stored daily returns, because every annualized figure in the table above is derived from the LENGTH of that array (years = n / 252). Only total return and max drawdown do not depend on the row count, so when the recomputed and stored values differ by a common factor, those two are the numbers to trust.
| Check | From the run's own returns | Note |
|---|---|---|
| Daily-return rows | 1,450 | 1.00x the 1,454 trading days in 2020-01-01..2025-10-08 |
| Distinct dates | 1,450 | one row per date |
| Date span | 2020-01-02 .. 2025-10-08 | |
| Sum of daily returns | 111.24% | matches the reported total return |
| Sharpe from these rows | 0.92 | stored 0.92 |
| Volatility from these rows | 21.08% | stored 21.08% |
| Max drawdown from these rows | -31.61% | stored -31.61% |
| CAGR from these rows | 13.88% | stored 13.88% |
| Metric | Value |
|---|---|
| Total Return | 111.24% |
| Sharpe | 0.92 |
| Sortino | 1.20 |
| Calmar | 0.44 |
| Max Drawdown | -31.61% |
| Volatility | 21.08% |
| Beta vs SPY | 0.76 |
| Win Rate | 83.00% |
| Profit Factor | 11.83 |
| Total Trades | 13932 |
| Symbols | 11 (COWZ, FDVV, QQQ, SCHD, SPY, SPYG, VGT, VTV, VUG, VYM, +1 more) |