SMA200 on Extended/Synthetic Long Windows
SMA200 on Extended/Synthetic Long Windows
Question
Reddit commenter on Post #2 challenged the regime bias of the 2010-2026 LETF inception data, arguing that extending the backtest through dotcom + GFC would flip the filter-vs-B&H comparison.
This study tests that claim by extending the SMA200 vs buy-and-hold analysis on three longer windows:
- Synthetic TQQQ since 1999 (~27 years, includes dotcom + GFC + COVID + 2022)
- Unleveraged S&P 500 since 1940 (~86 years, includes WWII, 1970s stagflation, dotcom, GFC, COVID, 2022)
- Synthetic 3x S&P 500 since 1940 (same window, leveraged version, illustrative not realistic)
Methodology
- Harness:
src/trader/backtest/engine.py, same as the inception study - Data: yfinance for QQQ (back to 1999-03-10), ^GSPC (back to 1927)
- Synthesis: Synthetic LETFs constructed as
(1 + leverage × daily_return − expense_ratio/252).cumprod(). TQQQ expense modeled at 0.86% annual; UPRO/SPXL at 0.91%. No financing cost modeled (this is the biggest pre-2009 unrealism). - Strategy: Pure SMA200 (long when close > SMA200, cash otherwise). Same lag, costs, no-lookahead conventions as the inception study.
- Cost: 1bp per side.
- Starting capital: $10,000.
Results
Synthetic TQQQ since 1999-03-10 (27.1 years)
| Buy-and-hold | SMA200 filter | |
|---|---|---|
| Final equity (from $10k) | $90,612 | $1,605,618 |
| CAGR | 8.46% | 20.58% |
| Max drawdown | -99.96% | -77.51% |
| Volatility | 80.83% | 44.11% |
| Sharpe (rf=0) | 0.506 | 0.647 |
| Sortino | 0.669 | 0.667 |
Sharpe diff (filter − B&H): +0.141. Compare to real TQQQ 2010-2026: −0.063. Extending back through dotcom flips the sign by ~0.20 Sharpe points.
Unleveraged S&P 500 since 1940-01-02 (86.2 years)
| Buy-and-hold | SMA200 filter | |
|---|---|---|
| Final equity (from $10k) | $5,830,383 | $4,174,993 |
| CAGR | 7.67% | 7.25% |
| Max drawdown | -56.78% | -28.56% |
| Volatility | 15.73% | 10.30% |
| Sharpe (rf=0) | 0.549 | 0.731 |
| Sortino | 0.693 | 0.807 |
| Calmar | 0.135 | 0.254 |
Sharpe diff (filter − B&H): +0.183. The canonical academic result for trend-following on the broad market, reproduced over the longest credible window.
Decade-by-decade on unleveraged S&P 500 (the real story)
| Decade | B&H CAGR | SMA CAGR | B&H MDD | SMA MDD | Sharpe diff |
|---|---|---|---|---|---|
| 1940s (WWII) | +2.91% | +5.20% | -41.5% | -16.9% | +0.322 |
| 1950s (boom) | +13.70% | +11.19% | -21.5% | -10.6% | +0.024 |
| 1960s (sideways) | +4.45% | +6.55% | -28.0% | -7.3% | +0.575 |
| 1970s (stagflation) | +1.50% | +2.96% | -48.2% | -14.5% | +0.230 |
| 1980s (bull + 1987) | +12.78% | +10.37% | -33.5% | -18.0% | +0.122 |
| 1990s (dotcom run-up) | +15.06% | +12.68% | -19.9% | -15.3% | -0.000 |
| 2000s (lost decade) | -2.63% | +2.64% | -56.8% | -21.5% | +0.353 |
| 2010s (QE bull) | +11.07% | +6.06% | -19.8% | -15.5% | -0.170 |
| 2020s (so far) | +13.21% | +9.90% | -33.9% | -19.8% | +0.228 |
The 2010s is the ONLY decade in 9 where the filter underperforms on Sharpe. And that's the decade dominating the LETF inception data (since LETFs only existed from 2009 forward).
Synthetic 3x S&P 500 since 1940 (illustrative, not realistic)
| Buy-and-hold | SMA200 filter | |
|---|---|---|
| Final equity (from $10k) | $1,202,884,233 | $15,697,944,713 |
| CAGR | 14.54% | 18.01% |
| Max drawdown | -97.84% | -65.06% |
| Sharpe | 0.530 | 0.711 |
Sharpe diff: +0.182 — basically the same gap as unleveraged.
Interpretation
The Reddit commenter was right. The original Post #2's "filter underperforms on Sharpe" framing was a 2010-2026 sample artifact, not a general truth.
Across 86 years of S&P 500 data: - Filter wins meaningfully in 5 of 9 decades (1940s, 1960s, 1970s, 2000s, 2020s) - Ties or marginal in 2 decades (1950s, 1980s) - Marginal loss in 1990s (clean dotcom run-up) - Meaningful loss in 2010s (QE bull)
The filter is structurally a drawdown manager that wins in bear/sideways regimes and loses in clean bull regimes. LETF inception backtests are biased toward the 2010s because that's when the funds existed; the structural bias of the sample misrepresents the long-run case for the filter.
For the synthetic TQQQ comparison specifically: extending from 16 years to 27 years (adding dotcom) was sufficient to flip the Sharpe ordering. The 86-year synthetic 3x SP result of +0.182 Sharpe is consistent with the unleveraged result, suggesting the trend filter's edge is robust across the leverage dimension.
Caveats
-
Pre-2009 LETF synthesis ignores financing costs. Real borrow at 4-15% prime rates during 1940-1980 would have eaten meaningful CAGR from any 3x daily-rebalance strategy. The synthetic 3x numbers (especially the $1.2B and $15.7B headline final values) are directional, not what you'd have actually earned.
-
The $1.2B → $15.7B headline numbers are visually absurd because we're compounding 86 years at high CAGR without realistic frictions. Use them only to compare strategies against each other, not as predictions.
-
^GSPC is the S&P 500 PRICE INDEX (no dividends). The "real" S&P with dividends reinvested (^SP500TR) only goes back to 1988. Using ^GSPC understates absolute returns for both B&H and the filter, but the COMPARATIVE answer is approximately right since dividends accrue proportionally to time-in-market.
-
The synthetic TQQQ vs testfol.io discrepancy: our model gives $90k B&H ending value; testfol shows ~$19k. Both methodologies hit ~99.97% max drawdown. The difference is recovery math from near-wipeout being extremely sensitive to modeling choices (financing, rebalance, slippage). Qualitative conclusion (B&H got eaten in 2000-2002, filter survived) is robust either way.
Source
Inline runner (SP500 1940 version):
import sys
sys.path.insert(0, '/Volumes/Mac External/Claudes/trader/src')
from trader.data.yfinance_src import fetch_daily
from trader.backtest.engine import run_backtest
from trader.strategies.buy_and_hold import BuyAndHold
from trader.strategies.price_above_sma import PriceAboveSma
spx = fetch_daily("^GSPC", start="1940-01-01")
bh = run_backtest("SPX B&H", spx, BuyAndHold().generate_positions(spx), cost_bps=1.0)
sma = run_backtest("SPX SMA200", spx, PriceAboveSma(window=200).generate_positions(spx), cost_bps=1.0)
print(f"BH Sharpe {bh.metrics['sharpe']:.3f}, SMA Sharpe {sma.metrics['sharpe']:.3f}")
Saved logs: /tmp/sma200_1940.log, /tmp/synthetic_tqqq.log.
Related studies
- 2026-05-13_letf_inception_sma200_vs_bh.md — the original 2010-2026 inception study this one corrects
- Leverage vs volatility per underlying — 1x vs 3x per underlying on same long window
This is research output, not investment advice. Backtest results do not predict future returns. Specific portfolio compositions discussed here are illustrative test cases, not allocation recommendations. Do your own research and consult a licensed advisor for personalized advice. Full disclaimer →