Data source
Prices come from Yahoo Finance via yfinance with auto_adjust=True, so closes are dividend-adjusted (CASH.TO, XQQ.TO, XIU.TO distributions are included). All series are CAD daily closes (every ticker on this page is Canadian-listed).
CAGR
(end_value / start_value)^(1/years) − 1, where years = trading_days / 252.
Annualized volatility
std(daily_returns) × √252. Population std (divisor = n). Assumes daily returns are independent — leveraged ETFs have mild negative serial autocorrelation, so the true annualized vol is slightly lower than this calculation.
Sharpe ratio
(mean_daily − rfr_daily) / std_daily × √252, where rfr_daily = (1 + rfr_annual)^(1/252) − 1. The risk-free rate is whatever you set in the config above. Default 2% is a rough time-weighted average for 2015–2022 (T-bill yield was near 0% in 2015–2021, ~5% in 2023+). For period-accurate ratios, override to match your chosen backtest window. Also reported with a standard error band: SE(Sharpe) = √((1 + 0.5·Sharpe²) / T_years) (Lo 2002, IID approximation).
Sortino ratio
(mean_daily − rfr_daily) × 252 / downside_dev_annualized, where downside deviation only counts days with returns below rfr_daily, summed-of-squares divided by total n (Sortino & Price 1994 convention).
Maximum drawdown
Largest peak-to-trough percentage loss using daily close prices only. Intraday lows are not captured, so actual lived drawdowns may be a few percent worse.
Monte Carlo
Block-bootstrap resampling of historical daily portfolio returns. Default: 2,000 paths, 10-year horizon, 5-day blocks, RNG seed = 42 on first render (click "Re-run with new seed" to see how stable the bands are). Block-bootstrapping preserves short-term autocorrelation and volatility clustering — it does not assume normal/Gaussian returns. Risk of Ruin = % of paths ending below 50% of the anchor value; Probability of Loss = % ending below the anchor value.
The MC projection anchor is configurable via the toggle above the section. Default: initial capital (matches a fresh investor's mental model: "if I start today with $X, what range of outcomes might I see in 10 years?"). The alternative is anchoring on the backtest's ending value (useful when asking "if I'm already at $X, what's next?").
Limitation: 5-day blocks preserve weekly autocorrelation but not regime/crisis dependencies. Heavier-tailed events than what's in the historical sample won't appear.
Stress tests
Hardcoded historical windows (2018 vol, COVID 2020, post-COVID recovery, 2022 rate hikes, 2022 tech crash, plus GFC 2008 if data exists). Only windows with ≥5 trading days of overlapping data are shown. TQQQ.TO began trading 2025-06-17, so every one of the listed historical stress windows is outside the available data and most of them will not fire. This will improve over time as TQQQ.TO accumulates history.
Transaction frictions
Commission + slippage is applied symmetrically — the sell side receives price × (1 − f), the buy side pays price × (1 + f). Default 0.15% per side ≈ 0.30% round-trip, realistic for Canadian discount brokers on leveraged ETFs.
What's NOT modeled
- Taxes. Every rebalance is a taxable event in a non-registered account. TFSA / RRSP avoid this.
- Inflation. Returns are nominal CAD, not real. Subtract ~2 to 3% per year for real CAGR.
- TQQQ.TO daily-reset decay in choppy markets. The 3× leverage resets daily, which causes path-dependent slippage in flat-but-volatile markets. The price series captures this implicitly, but the strategy logic does not separately attribute return drag to it.
- Indirect foreign withholding. TQQQ.TO and XQQ.TO hold US securities under the hood, so US dividend withholding still applies at the ETF level (a small annual drag, typically <0.3% per year for Nasdaq exposure given low dividend yield).
- Settlement delays. Assumes T+0 cash availability.
- Bid-ask spread variability. Uses closing price, not actual fill price.
Source code
All calculations are open and inspectable in the public repository — Python tools (Monte Carlo, stress tests) and JS in this page itself.