LETF Inception Backtest: Buy-and-Hold vs SMA200 Filter
LETF Inception Backtest: Buy-and-Hold vs SMA200 Filter
Question
For SOXL, TQQQ, and UPRO, from each fund's actual inception through 2026-05-15: 1. What does pure buy-and-hold return? 2. What does a pure SMA200 trend filter (long when close > SMA200, cash otherwise) return?
How do they compare on CAGR, max drawdown, Sharpe, and the rest of the standard metric set?
Methodology
- Harness:
src/trader/backtest/engine.py - Strategy:
src/trader/strategies/price_above_sma.pywithwindow=200, long-only - Data: yfinance daily bars via
src/trader/data/yfinance_src.pywithauto_adjust=True(dividend-reinvested, split-adjusted total-return closes). This is the standard convention for backtest return computations and differs from the chart-standard convention used by sma200.trade itself. - Position sizing: 100% of stated notional when long, 100% cash when flat. No leverage beyond what the LETF provides.
- Signal timing: Position lagged by one bar (
pos.shift(1)), so a signal computed at today's close is applied to tomorrow's close-to-close return. No lookahead. - Reentry rule: Pure mechanical. The moment close > SMA200 condition flips true, position re-enters at next bar's close. No N-day confirmation, no buffer band, no anti-whipsaw block.
- Transaction costs: 1 basis point per side (2bp per round-trip).
- Risk-free rate: 0.0 for Sharpe and Sortino. Real risk-free over the window averaged ~1.5% annualized; applying a non-zero rf would lower both numerators by the same amount across both strategies without changing the ordering.
- Tax treatment: None. All figures pre-tax. The SMA200 filter generates ~3 round-trips per year of short-term capital gains in taxable accounts.
- SMA200 warmup: First 200 trading days have no SMA value. During this window the filter strategy is in cash by definition; buy-and-hold is fully invested from day one. This is the honest comparison: both strategies span the full life of the LETF.
- Starting capital: $10,000.
Results
SOXL — Direxion Daily Semiconductor Bull 3X
| Metric | Buy-and-hold | SMA200 filter |
|---|---|---|
| Inception | 2010-03-11 | 2010-03-11 |
| End date | 2026-05-15 | 2026-05-15 |
| Years | 16.15 | 16.15 |
| Final equity (from $10k) | $2,732,125 | $577,724 |
| CAGR | 41.52% | 28.54% |
| Volatility (ann.) | 89.27% | 59.53% |
| Max drawdown | -90.46% | -69.68% |
| Sharpe (rf=0) | 0.839 | 0.723 |
| Sortino | 1.182 | 0.794 |
| Calmar | 0.459 | 0.410 |
| Round-trip trades | 1 (held) | 54 |
| Trades per year | — | 3.34 |
| Round-trip win rate | — | 20.37% |
| Time in market | 100.0% | 63.23% |
Sharpe diff (filter − B&H): −0.116
TQQQ — ProShares UltraPro QQQ
| Metric | Buy-and-hold | SMA200 filter |
|---|---|---|
| Inception | 2010-02-11 | 2010-02-11 |
| End date | 2026-05-15 | 2026-05-15 |
| Years | 16.23 | 16.23 |
| Final equity (from $10k) | $3,655,934 | $734,615 |
| CAGR | 43.85% | 30.31% |
| Volatility (ann.) | 60.96% | 42.13% |
| Max drawdown | -81.66% | -50.03% |
| Sharpe (rf=0) | 0.905 | 0.842 |
| Sortino | 1.165 | 0.923 |
| Calmar | 0.537 | 0.606 |
| Round-trip trades | 1 (held) | 44 |
| Trades per year | — | 2.71 |
| Round-trip win rate | — | 31.82% |
| Time in market | 100.0% | 74.25% |
Sharpe diff (filter − B&H): −0.063
UPRO — ProShares UltraPro S&P 500
| Metric | Buy-and-hold | SMA200 filter |
|---|---|---|
| Inception | 2009-06-25 | 2009-06-25 |
| End date | 2026-05-15 | 2026-05-15 |
| Years | 16.86 | 16.86 |
| Final equity (from $10k) | $1,233,859 | $137,899 |
| CAGR | 33.06% | 16.84% |
| Volatility (ann.) | 51.27% | 31.68% |
| Max drawdown | -76.82% | -56.66% |
| Sharpe (rf=0) | 0.817 | 0.651 |
| Sortino | 1.009 | 0.706 |
| Calmar | 0.430 | 0.297 |
| Round-trip trades | 1 (held) | 49 |
| Trades per year | — | 2.91 |
| Round-trip win rate | — | 24.49% |
| Time in market | 100.0% | 73.26% |
Sharpe diff (filter − B&H): −0.166
Bonus: signal computed on the underlying ETF
For each LETF, the same SMA200 strategy but using the underlying's signal:
| Strategy | Final equity | CAGR | MDD | Sharpe | vs B&H | vs direct |
|---|---|---|---|---|---|---|
| SOXL via SOXX SMA200 | $372,705 | 25.10% | -84.18% | 0.674 | -0.165 | -0.049 |
| TQQQ via QQQ SMA200 | $1,019,548 | 32.97% | -55.99% | 0.849 | -0.056 | +0.007 |
| UPRO via SPY SMA200 | $428,603 | 24.97% | -51.31% | 0.801 | -0.016 | +0.150 |
UPRO via SPY meaningfully beats UPRO direct. TQQQ via QQQ is marginal. SOXL via SOXX is actively worse.
Interpretation
Over the full inception-to-2026 window for each LETF, pure SMA200 filtering underperforms buy-and-hold on Sharpe across all three:
- SOXL: -0.116
- TQQQ: -0.063
- UPRO: -0.166
The filter wins meaningfully on max drawdown (TQQQ -82% → -50%, UPRO -77% → -57%, SOXL -90% → -70%) and on volatility, but at real cost in CAGR (-13 to -16 percentage points).
The case for the filter is behavioral, not mathematical. Backtests assume you hold through 90% drawdowns. Most humans don't. The filter trades CAGR for the ability to actually execute the strategy through a full cycle. See Article #1 on /learn for the full narrative.
Caveats
-
Sample is biased. 2009-2026 contained one of the strongest equity bull runs in modern history with only two relatively V-shaped drawdowns (2020 COVID, 2022 rate shock). A different regime weighted toward extended bear markets (e.g., 2000-2012 NDX lost decade) would tilt this comparison meaningfully toward the filter. Confirmed by the synthetic TQQQ 1999 study (next file).
-
The filter's 10-month cash warmup is included. Skipping the first 200 days (running both strategies from the first computable SMA value) would lift filter CAGR by 1.5-3 percentage points across these names without materially changing the Sharpe story.
-
Liquidity of older bars. SOXL and TQQQ both had thin liquidity in 2010-2012. Real-world 2bp round-trip costs would have been conservative then; spreads may have been wider. We don't model time-varying costs.
-
Pre-tax only. Filter generates ~3 short-term capital gains events per year. B&H generates zero until exit. After-tax results favor B&H by another margin in taxable accounts.
-
Bonus underlying-SMA results assume frictionless cross-instrument execution (signal triggered by SPY close → UPRO position next day). Real implementation would have a tiny cross-symbol timing slip not modeled here.
Source
Saved output: /tmp/sma200_post2_data.md (this file is a clean rewrite of that snapshot).
Inline runner:
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
for letf in ["SOXL", "TQQQ", "UPRO"]:
df = fetch_daily(letf)
bh = run_backtest(f"{letf} B&H", df, BuyAndHold().generate_positions(df),
cost_bps=1.0, starting_equity=10_000.0)
sma = run_backtest(f"{letf} SMA200", df, PriceAboveSma(window=200).generate_positions(df),
cost_bps=1.0, starting_equity=10_000.0)
print(f"{letf}: BH Sharpe {bh.metrics['sharpe']:.3f}, SMA Sharpe {sma.metrics['sharpe']:.3f}")
Related studies
- 2026-05-14_sma200_extended_windows_synthetic.md — same strategy on synthetic data back to 1999/1940
- LRS family replication + walk-forward — adds tolerance + RSI to this strategy, tests whether tuning helps
- Leverage vs volatility per underlying — 1x vs 3x version of the same comparison
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 →