What this project is

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.


Project structure

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

The pipeline (4 steps)

Every run flows through four stations in sequence:

  1. Fetch data (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.
  2. Generate signal (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.
  3. Simulate portfolio (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.
  4. Measure performance (analysis/) — Computes and charts all key metrics.

Performance metrics explained

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

Key configuration (main.py)

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