A Python backtesting framework that tests a cross-sectional momentum trading strategy — one of the most well-researched edges in quantitative finance (Jegadeesh & Titman, 1993). The core idea: assets that have been going up tend to keep going up. The project buys the top-ranked assets by 12-month return, rebalances monthly, and measures performance against a benchmark (SPY).
Backtesting means simulating a strategy on historical data to see how it would have performed — stress-testing an idea before risking real money.
momentum_backtester/
├── data/
│ └── fetcher.py # Downloads price data via yfinance
├── strategies/
│ └── momentum.py # Signal generation & weight computation
├── backtest/
│ ├── engine.py # Orchestrates the full pipeline
│ └── portfolio.py # Portfolio simulation with transaction costs
├── analysis/
│ ├── metrics.py # Performance metrics
│ └── visualizer.py # Equity curve & charts
├── main.py # Entry point — configure and run here
└── requirements.txt
Every run flows through four stations in sequence:
data/fetcher.py) — Downloads historical adjusted close prices from Yahoo Finance using yfinance. You give it tickers and a date range; it returns a table of daily prices.strategies/momentum.py) — Computes each asset's 12-month return (skipping the most recent month to avoid short-term reversal). Ranks all assets from worst to best. Selects the top-N to hold in equal weight.backtest/portfolio.py) — Day-by-day simulation. Once a month it rebalances: sells dropped assets, buys newly-ranked ones. Deducts 10 basis points per trade for transaction costs.analysis/) — Computes and charts all key metrics.| Metric | What it means |
|---|---|
| CAGR | Average yearly return, compounded. 12% CAGR = grew like 12%/year |
| Sharpe Ratio | Return per unit of risk. Above 1.0 is decent, 1.5+ is great |
| Max Drawdown | Worst peak-to-trough drop. Your "stomach test" |
| Sortino Ratio | Like Sharpe, but only penalizes downside volatility |
| Calmar Ratio | CAGR ÷ max drawdown. Annual return per unit of worst-case pain |
| Information Ratio | How consistently you beat the benchmark, not just luckily |
| Parameter | Default | What it controls |
|---|---|---|
tickers |
S&P sector ETFs | Universe of assets to trade |
start / end |
2015–2024 | Backtest date range |
lookback |
252 (days) | Momentum window (~12 months) |
skip |
21 (days) | Days skipped before lookback (~1 month) |
top_n |
3 | Number of top assets to hold |
rebalance_freq |
"ME" |
Monthly rebalancing |
transaction_cost |
0.001 | 10 basis points per trade |
long_only |
True | Set False to also short the bottom-N |