A Python playground for testing passive ETF portfolios built entirely with in-house logic. The project provides both a CLI and a web interface for simulating share-based portfolios with rebalancing, transaction costs, and benchmark comparisons.
- Multiple interfaces: Command-line tool and REST API with web UI
- Flexible data sources: Load local CSV files or download historical data from Yahoo Finance
- Intelligent data handling: Auto-detect date/price columns, deduplicate dates, and align price series using intersection, union, forward fill, or backward fill
- Realistic simulation: Share-based portfolio tracking with configurable rebalancing (monthly/quarterly/yearly) and proportional transaction costs
- Benchmark comparison: Compare your portfolio against any single-asset benchmark
- Comprehensive metrics: Total return, CAGR, volatility, max drawdown, and Sharpe ratio
- Interactive visualization: Bokeh dashboard with equity curves, drawdown charts, weight evolution, and KPI cards
main.py # CLI entry point
src/backtest/
CLI.py # Command-line interface
api.py # Public API facade (used by CLI & WebUI)
data_loader.py # CSV loading, Yahoo Finance downloads, alignment
portfolio.py # PortfolioConfig, weight validation
engine.py # BacktestEngine simulation logic
metrics.py # Performance calculations
plotting.py # Bokeh visualizations
apps/webui/
src/backtest_webui/
api.py # FastAPI REST endpoints
schemas.py # Pydantic request/response models
static/ # HTML/JS/CSS frontend
data/ # CSV files (Date, Close columns)
- Python 3.13+
- Managed by uv
- Core dependencies: pandas, numpy, bokeh, yfinance
Install the CLI and library:
uv syncInstall the web UI (from apps/webui/):
cd apps/webui
uv syncUse the CLI to fetch historical data automatically:
uv run python main.py download --symbols SPY QQQ --start 2020-01-01Or use the web UI's download endpoint.
Place CSV files in the data/ directory (e.g., data/SPY.csv, data/STOXX50.csv).
Required columns:
- A date column:
Date,Datetime, etc. - At least one price column (priority:
Adj Close>Close> first numeric column)
Dates are auto-parsed by pandas; duplicates are dropped (keeping the first occurrence).
uv run python main.py \
--symbols SPY STOXX50 \
--weights 0.5 0.5 \
--initial 10000 \
--rebalance yearly \
--transaction-cost 0.001 \
--align inner \
--benchmark SPY \
--plot| Flag | Description |
|---|---|
--symbols |
Space-separated tickers (default: SPY STOXX50) |
--weights |
Portfolio weights (must sum to 1; defaults to equal weight) |
--initial |
Initial capital in base currency (default: 10,000) |
--rebalance |
Rebalancing frequency: none, monthly, quarterly, yearly (default: yearly) |
--transaction-cost |
Proportional cost per trade, e.g., 0.001 = 0.1% (default: 0.001) |
--data-dir |
Directory containing CSV files (default: data) |
--align |
Price series alignment method: inner, outer, ffill, bfill (default: inner) |
--benchmark |
Optional benchmark symbol for comparison (e.g., SPY) |
--plot |
Open interactive Bokeh dashboard in browser |
The CLI outputs formatted metrics (Total Return, CAGR, Volatility, Max Drawdown, Sharpe Ratio) to the console. When --benchmark is provided, both portfolio and benchmark metrics are displayed side-by-side. If --plot is enabled, an interactive dashboard opens with equity curves, drawdown charts, and weight evolution.
Start the web server:
cd apps/webui
uv run uvicorn backtest_webui.api:app --reloadAccess the interface at http://localhost:8000
GET /api/symbols - List available symbols in the data directory
POST /api/backtest - Run a portfolio backtest
{
"symbols": ["SPY", "STOXX50"],
"weights": [0.5, 0.5],
"initial_capital": 10000,
"rebalance_frequency": "yearly",
"transaction_cost": 0.001,
"align_method": "inner",
"benchmark": "SPY"
}POST /api/download - Download historical data from Yahoo Finance
{
"symbols": ["SPY", "QQQ"],
"start": "2020-01-01",
"end": "2024-01-01"
}BACKTEST_DATA_DIR: Path to CSV data directory (default:datarelative to current directory)
# Format code
uv run ruff format .
# Lint
uv run ruff check .
# Type check
uv run mypy src/
# Run a sample backtest
uv run python main.py --symbols SPY STOXX50 --weights 0.5 0.5 --plotTests are not yet implemented. Add pytest cases under tests/ and run:
uv run pytest- The project uses a clean separation between the core library (
src/backtest/) and interfaces (CLI inmain.py, Web UI inapps/webui/) - The
api.pymodule provides a stable facade for both CLI and Web UI to consume - All simulation logic is manual (no external backtest engines); share-based accounting with explicit rebalancing
- Type hints are used throughout (Python 3.13+ style with
list[...],dict[...]) - Italian comments are acceptable; code remains in English
Ideas for upcoming iterations:
- Periodic contributions and withdrawals (dollar-cost averaging scenarios)
- Multi-currency support with FX conversion
- Additional metrics: Sortino ratio, Calmar ratio, rolling statistics
- Export results to CSV/JSON/Excel
- Tax-aware simulations (capital gains, dividends)
- Monte Carlo simulation and scenario analysis