{"id":49441730,"url":"https://github.com/lopadova/portfolio-backtest","last_synced_at":"2026-04-29T20:36:10.640Z","repository":{"id":353212735,"uuid":"1218411624","full_name":"lopadova/portfolio-backtest","owner":"lopadova","description":"Portfolio backtest engine with montecarlo simulation, charts, performance optimization, max drowdown, etc..","archived":false,"fork":false,"pushed_at":"2026-04-23T00:22:00.000Z","size":71,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-23T00:26:30.779Z","etag":null,"topics":["backtest","backtesting","montecarlo-simulation","portfolio-optimization"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lopadova.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T21:12:36.000Z","updated_at":"2026-04-22T22:58:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lopadova/portfolio-backtest","commit_stats":null,"previous_names":["lopadova/portfolio-backtest"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/lopadova/portfolio-backtest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lopadova%2Fportfolio-backtest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lopadova%2Fportfolio-backtest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lopadova%2Fportfolio-backtest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lopadova%2Fportfolio-backtest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lopadova","download_url":"https://codeload.github.com/lopadova/portfolio-backtest/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lopadova%2Fportfolio-backtest/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32443567,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T20:22:27.477Z","status":"ssl_error","status_checked_at":"2026-04-29T20:22:26.507Z","response_time":110,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backtest","backtesting","montecarlo-simulation","portfolio-optimization"],"created_at":"2026-04-29T20:36:07.025Z","updated_at":"2026-04-29T20:36:10.626Z","avatar_url":"https://github.com/lopadova.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Portfolio Backtest Engine\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE)\n[![Python: 3.11–3.14](https://img.shields.io/badge/Python-3.11%E2%80%933.14-blue.svg)](https://www.python.org/)\n[![Tests](https://img.shields.io/badge/tests-267%20passing-brightgreen.svg)](#testing)\n[![Status: v2.0](https://img.shields.io/badge/Status-v2.0-success.svg)](#whats-new)\n[![Streamlit](https://img.shields.io/badge/Dashboard-Streamlit-FF4B4B.svg)](#dashboard-local)\n[![HuggingFace](https://img.shields.io/badge/Deploy-HF%20Spaces-yellow.svg)](#deploy-public)\n[![FIRE](https://img.shields.io/badge/%F0%9F%94%A5-FIRE%20calculator-orange.svg)](#fire)\n[![AI Analysis](https://img.shields.io/badge/AI-OpenRouter%20%7C%20OpenAI%20%7C%20Anthropic%20%7C%20Local-blueviolet.svg)](#ai)\n[![Article](https://img.shields.io/badge/Medium-Read%20the%20article-black.svg)](https://medium.com/@padosoft)\n[![Screenshots](https://img.shields.io/badge/%F0%9F%93%B8-More%20screenshots-blueviolet.svg)](#-more-screenshots)\n\n\u003e **The open-source, fund-manager-grade toolkit for backtesting a multi-asset portfolio over 20 years, stress-testing it with Monte Carlo + rolling windows + Markowitz frontier + Italian tax + FIRE projection, and getting an LLM to review the results — either from the CLI or through a beautiful Streamlit dashboard you can deploy in one click.**\n\nIf you are an investor, quant curious, aspiring FIRE retiree, or data scientist who wants to understand what *really* happens to a defensive portfolio across GFC 2008, COVID 2020 and Stagflation 2022 — this repo is your sandbox. **Clone, `pip install`, and you're minutes away from a full report.** You can run it from the terminal, from a one-click dashboard, or deploy it publicly on Streamlit Cloud / HuggingFace Spaces.\n\nThe engine currently runs the **Four Umbrellas** defensive preset — the strategy from the companion Medium article [*\"The Four Umbrellas Portfolio\"*](https://medium.com/@padosoft) — as the default (and only) portfolio; customization for now requires editing `src/portfolio.py`. Every allocation decision, every metric, every chart in that article is reproducible here. **Generic multi-portfolio support (CLI `--portfolio` flag, interactive asset picker, save/load) is tracked in the upcoming PR2+ refactor.**\n\n![dashboard — Charts tab with equity curve and drawdown](resources/result-charts.png)\n![dashboard](resources/dashboard.png)\n\n---\n\n\u003ca id=\"tldr\"\u003e\u003c/a\u003e\n## ⚡ TL;DR — try it in 30 seconds\n\n\u003e **Python newcomers, read this first.** Every `python …` command below assumes you've already activated the project virtual environment in your current terminal. If you haven't, the script will stop with a clear error explaining the two ways to fix it. The full step-by-step install (venv creation + activation + deps) is in [**Installing from scratch**](#install) — do that once, then come back here.\n\n```bash\ngit clone https://github.com/padosoft/portfolio-backtest.git\ncd portfolio-backtest\n\n# 1) Create the virtual environment (once)\npython -m venv .venv\n\n# 2) Activate it IN THIS TERMINAL (needed every time you open a new terminal)\n#    Windows PowerShell:  .venv\\Scripts\\Activate.ps1\n#    Windows cmd.exe:     .venv\\Scripts\\activate.bat\n#    macOS / Linux:       source .venv/bin/activate\n\n# 3) Install deps (once)\npip install -r requirements.txt\n\n# OPTION A — full CLI report with synthetic data (no data files needed)\npython backtest.py --synthetic --monte-carlo\n# → open output/REPORT.md — 13 charts, stats tables, MC fan chart\n\n# OPTION B — interactive dashboard (sliders, tabs, AI analysis)\npip install -r requirements-dashboard.txt\nstreamlit run streamlit_app.py\n# → browser opens at http://localhost:8501\n\n# OPTION C — plan your FIRE / retirement\npython fire.py --age 45 --sex M --capital 100000 --contributions 1000 \\\n    --fire-age 60 --spending 2500 --pension --pension-amount 1500 --pension-age 67\n\n# OPTION D — have an LLM critique your portfolio\n#    Put your key in a .env file (see the \"AI analysis\" section), then:\npython backtest.py \u0026\u0026 python analyze.py --results output/\n```\n\n\u003e **Don't want to activate the venv?** You can always invoke the venv's Python directly instead of `python` — skip activation entirely and use:\n\u003e - Windows: `.venv\\Scripts\\python.exe backtest.py --synthetic`\n\u003e - macOS / Linux: `.venv/bin/python backtest.py --synthetic`\n\u003e\n\u003e That form works in any shell, any terminal, with zero configuration.\n\nJump to: [**Install**](#install) · [**CLI reference**](#cli) · [**GUI launch**](#dashboard-local) · [**FIRE**](#fire) · [**AI**](#ai) · [**Deploy to Cloud/HF**](#deploy-public)\n\n---\n\n## Table of contents\n\n- [⚡ TL;DR — try it in 30 seconds](#tldr)\n- [🛠️ Installing from scratch (step-by-step)](#install)\n- [📋 CLI reference — everything you can do from the terminal](#cli)\n- [🧰 Custom portfolios — `--portfolio` flag](#custom-portfolios)\n- [What's new in v2.0 🚀](#whats-new)\n- [🌟 Why you'll actually enjoy using this](#why)\n- [What this is](#what-this-is)\n- [Why it exists](#why-it-exists)\n- [Features](#features)\n- [What it calculates](#what-it-calculates)\n- [Example output](#example-output)\n- [Quick start (5 minutes)](#quick-start-5-minutes)\n- [Running the interactive dashboard locally](#dashboard-local)\n- [📸 Screenshots](#screenshots)\n- [🚀 Deploy the dashboard publicly](#deploy-public)\n- [🔥 FIRE calculator — plan your retirement](#fire)\n- [🤖 AI analysis — LLM-driven qualitative review](#ai)\n- [Advanced analyses (sensitivity, rolling window, frontier)](#advanced-analyses)\n- [Full onboarding](#full-onboarding)\n- [Usage recipes](#usage-recipes)\n- [Configuration](#configuration)\n- [Methodology](#methodology)\n- [Architecture](#architecture)\n- [FAQ](#faq)\n- [📸 More screenshots](#-more-screenshots)\n- [Testing](#testing)\n- [Roadmap](#roadmap)\n- [Contributing](#contributing)\n- [Citation](#citation)\n- [License \u0026 legal](#license--legal)\n\n---\n\n\u003ca id=\"install\"\u003e\u003c/a\u003e\n## 🛠️ Installing from scratch (step-by-step)\n\nWritten for readers who \"just want to double-click run\"— if you already know what a venv is, skip to the [CLI reference](#cli).\n\n### 1. Prerequisites\n\n- **Python 3.11, 3.12, 3.13, or 3.14** — download from [python.org](https://www.python.org/downloads/) if you don't have it. On Windows, tick *\"Add python.exe to PATH\"* in the installer.\n- **Git** — [git-scm.com](https://git-scm.com/downloads)\n- A terminal. On Windows, either **PowerShell** (built-in) or **Git Bash** (ships with Git) both work.\n\nCheck that Python is installed:\n\n```bash\npython --version\n# expected: Python 3.11.x  (or 3.12 / 3.13 / 3.14)\n```\n\n### 2. Clone the project and enter the folder\n\n```bash\ngit clone https://github.com/padosoft/portfolio-backtest.git\ncd portfolio-backtest\n```\n\nEverything below assumes your terminal is inside this `portfolio-backtest/` folder.\n\n### 3. Create the virtual environment (once, ~10 seconds)\n\nA virtual environment is a private, project-local copy of Python that keeps this project's dependencies isolated from the rest of your system.\n\n```bash\npython -m venv .venv\n```\n\nYou now have a `.venv/` folder in the project. Do not edit it; Git ignores it.\n\n### 4. Activate the venv IN YOUR CURRENT TERMINAL\n\n**This is the step most people miss.** You must activate the venv every time you open a new terminal window. If you skip it, the scripts will stop with a clear error and point you back here.\n\n| Terminal | Activation command |\n|---|---|\n| **Windows PowerShell** | `.venv\\Scripts\\Activate.ps1` |\n| **Windows cmd.exe** | `.venv\\Scripts\\activate.bat` |\n| **Windows Git Bash / WSL** | `source .venv/Scripts/activate` |\n| **macOS / Linux** | `source .venv/bin/activate` |\n\nYou'll know activation worked when your prompt gets a `(.venv)` prefix:\n\n```text\n(.venv) PS C:\\Users\\you\\portfolio-backtest\u003e\n```\n\n\u003e **PowerShell says \"running scripts is disabled\"?** Run this once, answer `Y`, then retry activation:\n\u003e\n\u003e ```powershell\n\u003e Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned\n\u003e ```\n\n\u003e **Prefer zero activation?** Replace every `python …` command in this README with `.venv\\Scripts\\python.exe …` (Windows) or `.venv/bin/python …` (macOS/Linux). It's the same thing — the venv's Python knows to use its own packages.\n\n### 5. Install the dependencies (once, ~1 minute)\n\n```bash\npip install -r requirements.txt\n```\n\nFor the Streamlit dashboard, also install:\n\n```bash\npip install -r requirements-dashboard.txt\n```\n\nFor running tests locally:\n\n```bash\npip install -r requirements-dev.txt\n```\n\n### 6. First run — verify the pipeline works\n\n```bash\npython backtest.py --synthetic\n```\n\nYou should see progress logs and `output/REPORT.md` appear at the end. If this works, you're done installing.\n\n### 7. (Optional) Set up an `.env` for AI analysis\n\nIf you want [AI analysis](#ai), create a `.env` file at the project root:\n\n```bash\n# Windows (PowerShell)\nCopy-Item .env.example .env\n\n# macOS / Linux\ncp .env.example .env\n```\n\nThen open `.env` in any text editor and paste your API key(s). See the [AI section](#ai) for the full guide.\n\n### 8. Every session after that\n\nEach new terminal window:\n\n```bash\ncd portfolio-backtest\n.venv\\Scripts\\Activate.ps1    # (or the right activation command for your shell)\npython backtest.py --synthetic --monte-carlo   # or any other command\n```\n\nThat's the entire loop. If something ever fails with `ModuleNotFoundError: No module named 'pandas'`, it means step 4 (activation) was skipped in that terminal — just run it again.\n\n---\n\n\u003ca id=\"cli\"\u003e\u003c/a\u003e\n## 📋 CLI reference — everything you can do from the terminal\n\nThe toolkit exposes three top-level commands, each fully `--help`-able. This table lists the most useful flags; run `python \u003ccommand\u003e.py --help` for the complete list.\n\n### `backtest.py` — the main engine\n\n| Flag | What it does | Example |\n|---|---|---|\n| *(none)* | Full 20-year run, options overlay on, no MC | `python backtest.py` |\n| `--start YYYY-MM-DD` / `--end YYYY-MM-DD` | Constrain the period | `--start 2008-01-01 --end 2023-12-31` |\n| `--nav AMOUNT` | Starting NAV in EUR (default 100 000) | `--nav 250000` |\n| `--synthetic` | Use random-walk data (no real files needed) | `python backtest.py --synthetic` |\n| `--no-options` | Disable the semi-annual put-spread overlay | `python backtest.py --no-options` |\n| `--monte-carlo` | Run block-bootstrap Monte Carlo — auto-includes **Prudente / Mediana / Ottimista (10°/50°/90°)** scenarios table + chart (NEW v2) | `python backtest.py --monte-carlo --mc-paths 10000 --mc-years 20` |\n| `--mc-paths N` · `--mc-years Y` · `--mc-block-size M` | Tune MC (defaults: 5000 paths, auto horizon, 3-month blocks) | `--mc-paths 20000 --mc-years 30 --mc-block-size 3` |\n| `--sensitivity PARAM --range LO HI --step S` | Sweep a parameter | `--sensitivity gold --range 0.10 0.25 --step 0.025` |\n| `--sensitivity PARAM --values V1 V2 ...` | Sweep specific values | `--sensitivity rebalance_freq --values 1 2 4 12` |\n| `--rolling-window --window-years N --step-months M` | Rolling-window stress test | `--rolling-window --window-years 10 --step-months 1` |\n| `--efficient-frontier --n-random N` | Markowitz + Dirichlet sampling | `--efficient-frontier --n-random 50000` |\n| `--output-dir DIR` | Where to write outputs (default `output/`) | `--output-dir output/custom-run` |\n\n\u003e **Tax modeling** is available programmatically via `src/tax.py` (`TaxLedger`, `TaxConfig` — 26% CGT with 4-year *minusvalenze* FIFO carry, 12.5% gov-bond rate). Wiring it behind a `--tax` CLI flag is on the [roadmap](#roadmap); for now, `import` it from your own script or test to apply tax drag on realized rebalance flows.\n\n### `fire.py` — FIRE / retirement planner (NEW v2)\n\n```bash\npython fire.py --age 45 --sex M --capital 100000 --contributions 1000 \\\n    --fire-age 60 --spending 2500 --inflation 0.02 \\\n    --pension --pension-amount 1500 --pension-age 67 --pension-revaluation 0.75 \\\n    --tax-on-withdrawals --simulations 5000\n```\n\nSee the [full FIRE section](#-fire-calculator-plan-your-retirement) for every parameter.\n\n### `analyze.py` — standalone AI analysis runner (NEW v2)\n\n```bash\n# Analyze a REPORT.md you already produced\npython analyze.py --results output/\npython analyze.py --results output/ --provider anthropic --model claude-opus-4-7\npython analyze.py --results output/ --provider local --model kimi-k2\npython analyze.py --results output/ --extra output/sensitivity/gold.csv\n```\n\nSee the [AI analysis section](#-ai-analysis-llm-driven-qualitative-review) for provider setup.\n\n### `streamlit_app.py` — the GUI (NEW v2)\n\n```bash\npip install -r requirements-dashboard.txt\nstreamlit run streamlit_app.py\n# → opens http://localhost:8501\n```\n\nSee [Running the interactive dashboard locally](#running-the-interactive-dashboard-locally) for the tour.\n\n---\n\n\u003ca id=\"custom-portfolios\"\u003e\u003c/a\u003e\n## 🧰 Custom portfolios — `--portfolio` flag (PR2)\n\nStarting with PR2 the engine accepts **any portfolio composition** from the CLI, not just the hardcoded Four Umbrellas preset. The default is unchanged — `python backtest.py --synthetic` still runs the Four Umbrellas preset and produces byte-identical output.\n\n### How to pick a portfolio\n\n```bash\n# 1) No flag → default preset (Four Umbrellas). Zero diff from pre-PR2.\npython backtest.py --synthetic\n\n# 2) By NAME — resolves to portfolios/\u003cname\u003e.toml. Ship your own TOML\n#    in portfolios/my_strategy.toml and load it by name:\npython backtest.py --synthetic --portfolio my_strategy\n\n# 3) By PATH — any path ending in .toml or containing a /:\npython backtest.py --synthetic --portfolio portfolios/four_umbrellas.toml\npython backtest.py --synthetic --portfolio /absolute/path/to/some.toml\n\n# 4) Inline JSON — anything starting with '{':\npython backtest.py --synthetic --portfolio \\\n  '{\"name\":\"Toy\",\"assets\":[{\"key\":\"gold\",\"weight\":0.5},{\"key\":\"cash\",\"weight\":0.5}]}'\n\n# List every preset in portfolios/ and exit\npython backtest.py --list-portfolios\n```\n\n### Preset file format (TOML)\n\nA preset is a plain TOML file. Weights MUST sum to 1.0 ±0.002 — the loader rejects anything else with a clear error. The available asset keys come from [`data/catalog.toml`](./data/catalog.toml); add your own catalog entries to register new datasets.\n\n```toml\n# portfolios/my_strategy.toml\nname = \"My defensive strategy\"\nnotes = \"40/40/20 gold/equity/cash — example\"\noptions_overlay = false             # SPY/QQQ put-spread overlay (needs SPY+QQQ+VIX data)\nrebalance_months = [1, 7]           # January + July (semi-annual)\ntransaction_cost_bps = 20.0\n\n[[assets]]\nkey    = \"gold\"\nweight = 0.4\n\n[[assets]]\nkey    = \"quality\"\nweight = 0.4\n\n[[assets]]\nkey    = \"cash\"\nweight = 0.2\n```\n\n### Combine `--portfolio` with the advanced analyses\n\nSince PR3 every advanced analysis accepts the `--portfolio` spec and\noperates on that portfolio — no more \"uses the default preset\" warning:\n\n```bash\n# Efficient frontier restricted to your portfolio's asset universe\npython backtest.py --synthetic --portfolio my_strategy --efficient-frontier\n\n# Rolling 10-year windows simulated against your portfolio\npython backtest.py --synthetic --portfolio my_strategy --rolling-window --window-years 10\n\n# Sensitivity sweep on a weight inside your portfolio (not the globals)\npython backtest.py --synthetic --portfolio my_strategy --sensitivity gold --range 0.10 0.25 --step 0.05\n```\n\nTwo caveats on the sensitivity path when a custom portfolio is in use:\n\n- **`--sensitivity options_budget` on a custom portfolio** is not yet\n  supported — the options overlay still reads the global `OPTIONS` config;\n  a per-portfolio `OptionsConfig` is tracked for a later PR. Workaround:\n  sweep `options_budget` on the default preset (no `--portfolio`).\n- **Weight-like params** (`gold`, `dbi`, `put_write`, `nasdaq_top30`,\n  `momentum`, `quality`) require the portfolio to contain both a matching\n  asset and an explicit `cash` sleeve (the delta is absorbed by cash).\n  The error message points at the specific missing piece.\n\n### Save your portfolio for later (PR6)\n\nAfter a run you can save the portfolio to `portfolios/\u003cslug\u003e.toml` and\nreload it later — either from the CLI or from the Streamlit dashboard.\n\n```bash\n# Save the just-completed run. NAME is slugified (lowercase + '_') so\n# 'My Defensive Strategy' becomes portfolios/my_defensive_strategy.toml.\npython backtest.py --synthetic --portfolio my_strategy --save-as \"My Defensive Strategy\"\n\n# Collision = exit 2 unless you pass --overwrite.\npython backtest.py --synthetic --save-as \"My Defensive Strategy\" --overwrite\n\n# List every saved preset with its cached CAGR / Vol / MaxDD / Period.\npython backtest.py --list-portfolios\n```\n\nThe saved TOML includes an optional `[metrics]` section with CAGR,\nannualized vol, max drawdown, the simulated period, and a UTC timestamp\nof the run. Reload with `--portfolio my_defensive_strategy` (bare name)\nor via the `📂 Portafogli salvati` tab in the dashboard.\n\n**Protected names**: the shipped `four_umbrellas.toml` preset is\nreserved — the CLI and UI refuse to save/overwrite/delete it. Update it\nonly via a repo commit.\n\n---\n\n\u003ca id=\"whats-new\"\u003e\u003c/a\u003e\n## What's new in v2.0 🚀\n\nA massive feature rollout delivered in **11 reviewed-and-merged PRs** on top of the 1.0 baseline, **267 tests passing** across the matrix:\n\n| # | Feature | What it unlocks |\n|---|---|---|\n| 1 | 📊 **Metrics++** | Average Drawdown, Max DD duration (months), **UPI** (Ulcer Performance Index, Martin 1989) — drawdown-aware risk assessment |\n| 2 | 🎲 **MC scenarios** | Explicit **Prudente (10°) / Mediana (50°) / Ottimista (90°)** percentiles with € values and implied CAGR — what clients actually need to see |\n| 3 | 🏛️ **Classic benchmarks** | Golden Butterfly (Tyler), Harry Browne Permanent, Dalio All-Weather (official), Swensen Lazy — compare against the giants |\n| 4 | 🎚️ **Sensitivity sweep** | One CLI flag iterates any parameter (`gold`, `dbi`, `options_budget`, `rebalance_freq`, `put_write`, `nasdaq_top30`, `momentum`, `quality`) across a range — instant \"what if\" analysis |\n| 5 | 🌀 **Rolling-window backtest** | N-year window slid monthly/quarterly/annually — answers *\"would this portfolio have worked regardless of when I started investing?\"* |\n| 6 | 📐 **Efficient Frontier** | Markowitz MVO + **50k Dirichlet-sampled random portfolios**, Max Sharpe / Min Vol / Max Return markers, Four Umbrellas reference dot. **Interactive Plotly chart** — hover any point to see its allocation breakdown |\n| 7 | 🇮🇹 **Italian tax modeling** | 26% CGT, 4-year *minusvalenze* carry-forward with FIFO compensation, 12.5% gov-bond rate, pension exempt during accumulation — proper after-tax numbers |\n| 8 | 🔥 **FIRE calculator** | 2-phase Monte Carlo (accumulation → decumulation) with **ISTAT 2023 mortality sampling**, INPS-style pension revaluation, CGT on withdrawals, 4 dedicated charts |\n| 9 | 🤖 **AI analysis** | Pluggable LLM provider: **OpenRouter** (default) / OpenAI / Anthropic / Local (Ollama, vLLM, LM Studio) — structured Italian review |\n| 10 | 🖥️ **Streamlit dashboard** | Full interactive UI: sidebar form, Run/Save buttons, tabs for Summary / Charts / MC / AI. Deploy configs for local, **Streamlit Cloud**, and **HuggingFace Spaces** (Dockerfile) |\n| 11 | ⚙️ **CI/CD + docs** | GitHub Actions matrix (3 OS × 2 Python) runs pytest + 6 smoke tests + artifact upload on every PR |\n\n**All v2 features are additive and flag-gated — default behavior preserves v1 compatibility.** Existing users can `git pull` with zero surprises.\n\n---\n\n\u003ca id=\"why\"\u003e\u003c/a\u003e\n## 🌟 Why you'll actually enjoy using this\n\nMost \"portfolio backtesters\" on the web are either (a) black-box web tools where you cannot see the methodology, (b) deep quant libraries (backtrader, vectorbt) with a steep learning curve for a single-portfolio study, or (c) academic code dumps that require significant rewiring to fit a custom allocation.\n\n**This project is deliberately in the middle — and tries very hard to be joyful to use:**\n\n- 🎯 **One command, full report** — `python backtest.py` → `output/REPORT.md` with 13 charts and all stats. No plumbing.\n- 🖱️ **Dashboard for the GUI people** — `streamlit run streamlit_app.py` and you're clicking sliders.\n- 📚 **Readable source** — every business-logic module is \u003c 300 lines. Audit it. Fork it. Teach with it.\n- 🔬 **Academically grounded** — citations for every methodological choice (Israelov \u0026 Nielsen on options, Politis \u0026 White on block bootstrap, Martin on UPI).\n- 🧪 **267 tests**, CI on Ubuntu / macOS / Windows × Python 3.11 / 3.12. PRs blocked if tests fail.\n- 🇮🇹 **Localized for Italy** — tax rules, ISTAT mortality tables, INPS pension revaluation, Italian LLM output. Also works for anyone else; flag-gate what you don't need.\n- 🆓 **MIT license** on the code. No strings.\n\nIf you are building your own portfolio, writing about markets, studying for a CFA, teaching a finance class, or just curious whether all these scenarios actually matter — **clone it, run it, fork it, publish your numbers**. PRs welcome.\n\n---\n\n## What this is\n\nA self-contained Python backtest engine that:\n\n1. **Simulates a multi-asset portfolio** with configurable weights across 19 sleeves (equity factors, gold, managed futures, bonds, crypto, EM satellites, options overlay).\n2. **Benchmarks it** against five reference portfolios out of the box: 100% S\u0026P 500 TR (EUR), 100% SWDA (MSCI World EUR), 60/40, All-Weather proxy, and the portfolio without the options overlay.\n3. **Computes 14 performance metrics** per portfolio — CAGR, annualized vol, Sharpe, Sortino, Max Drawdown, Calmar, Ulcer Index, CVaR 5%, longest underwater period (months), recovery months, best/worst month, % positive months, total return.\n4. **Generates 13 charts** covering equity curves, drawdowns, underwater periods, rolling statistics, crisis zooms, annual returns, risk-return positioning, metric comparisons, sleeve correlations, Monte Carlo simulations.\n5. **Produces a unified Markdown report** (`output/REPORT.md`) that bundles every chart and table into one consumable document — no data viewer required beyond a Markdown renderer.\n6. **Runs a block-bootstrap Monte Carlo** simulation (configurable paths and horizon) with a fan chart and terminal-wealth distribution.\n7. **Is fully reproducible**: configurable date range, deterministic random seed for the MC simulation, every parameter exposed in one file.\n\n---\n\n## Why it exists\n\nMost \"portfolio backtesters\" on the web are either (a) black-box web tools where you cannot see the methodology, (b) deep quant libraries (backtrader, vectorbt) with a steep learning curve for a single-portfolio study, or (c) academic code dumps that require significant rewiring to fit a custom allocation.\n\nThis project sits deliberately in the middle:\n\n- **Opinionated** — it implements one portfolio family very well, instead of being a generic framework.\n- **Auditable** — every line of business logic (rebalancing, options pricing, metric computation) is readable in \u003c 300 lines per module.\n- **Defensible** — citations for every methodological choice, alignment with published practitioner literature (AQR, CBOE, SG).\n- **Redistributable** (the code; not the data — see [License \u0026 legal](#license--legal)).\n\nIf you are building your own portfolio and want a starting point that is academically grounded but not dogmatic, this is a good scaffold to fork.\n\n---\n\n## Features\n\n### Portfolio modeling\n- ✅ 19-sleeve allocation with independent weights (configurable in `src/portfolio.py`)\n- ✅ EUR / USD hedging per sleeve (each sleeve flagged hedged or unhedged)\n- ✅ TER (annual expense ratio) deduction per sleeve\n- ✅ Semi-annual rebalancing with configurable months (default Jan/Jul)\n- ✅ Rebalance-by-buying philosophy (flow-driven, not sell-driven) — the alternative to traditional rebalancing is in the article\n- ✅ BTC activation date awareness (BTC sleeve is 0% pre-2014, target weight post-2014)\n- ✅ Transaction cost modeling (20 bps round-trip, configurable)\n\n### Defensive overlays\n- ✅ **Options overlay** — SPY + QQQ debit put spreads, semi-annual rolls, Black-Scholes + VIX (skew-adjusted) pricing per *Israelov \u0026 Nielsen (2015)*\n- ✅ **2×/3× take-profit rule** — mechanical daily check, closes 50% at 2× premium, 100% at 3× (solves the whipsaw problem — see article §15)\n- ✅ **Fixed budget discipline** — options overlay hard-capped at 0.30% NAV/year\n\n### Benchmarks (out of the box)\n- ✅ **100% S\u0026P 500 TR EUR** — US large-cap equity reference\n- ✅ **100% SWDA / MSCI World EUR** — global equity reference (iShares Core MSCI World proxy)\n- ✅ **60/40** — 60% MSCI World + 40% Bloomberg Euro Aggregate\n- ✅ **All-Weather proxy** — 30/40/15/7.5/7.5 equity/long-bonds/short-bonds/gold/commodities\n- ✅ **Four Umbrellas (no options)** — for isolating the contribution of the options overlay\n\n### Analytics\n- ✅ 14 performance metrics (see [What it calculates](#what-it-calculates))\n- ✅ 13 publication-quality charts (see [Example output](#example-output))\n- ✅ Rolling 3-year Sharpe and return series\n- ✅ Crisis-period peak-to-trough drawdowns (GFC 2008, COVID 2020, Stagflation 2022)\n- ✅ Block-bootstrap Monte Carlo with percentile fan chart and terminal-wealth distribution\n\n### Operational\n- ✅ **Configurable date range** — `--start` and `--end` CLI flags\n- ✅ **Configurable NAV** — `--nav` CLI flag (default €100,000 — but the math is scale-invariant)\n- ✅ **Synthetic data mode** — `--synthetic` flag to test the pipeline without real data\n- ✅ **Unified Markdown report** — `output/REPORT.md` ties all outputs together\n- ✅ **Google Colab notebook** — one-click browser-based execution\n- ✅ Deterministic seeds for reproducibility\n- ✅ MIT license — no strings on the code\n\n---\n\n## What it calculates\n\n### Performance metrics (per portfolio)\n\n| Metric | Formula / interpretation |\n|---|---|\n| **CAGR** | Compound Annual Growth Rate: `(W_end / W_start)^(1/years) - 1` |\n| **Annualized Volatility** | `σ(monthly returns) × √12` |\n| **Sharpe Ratio** | `(CAGR - RF) / annualized_vol` with annualized excess returns |\n| **Sortino Ratio** | `(CAGR - RF) / downside_deviation` — penalizes only negative returns |\n| **Max Drawdown** | Largest peak-to-trough loss over the period |\n| **Calmar Ratio** | `CAGR / |Max DD|` — return per unit of worst loss |\n| **Ulcer Index** | RMS drawdown depth — integrates depth × duration |\n| **CVaR 5%** (Expected Shortfall) | Average return in the worst 5% of months |\n| **Worst Month / Best Month** | Min / max of the monthly return series |\n| **% Positive Months** | Fraction of months with positive returns |\n| **Total Return** | End-to-end cumulative return over the period |\n| **Longest Underwater Period** | Longest consecutive stretch (in months) below the prior all-time high |\n| **Recovery Months** | Months from worst drawdown bottom to a new all-time high |\n| **Average Drawdown** (v2.0) | Mean of underwater values — less noisy than Max DD |\n| **Max DD Duration** (v2.0) | Duration in months of the single worst peak-to-trough event |\n| **UPI** (v2.0) | Ulcer Performance Index: `(CAGR - RF) / Ulcer Index` — drawdown-aware Sharpe (Martin 1989) |\n\n### Crisis-period analysis\n\nPeak-to-trough drawdown within each of:\n- **GFC (Jan 2008 – Jun 2009)**\n- **COVID (2020)**\n- **Stagflation (2022)**\n\n...for every portfolio modeled.\n\n### Monte Carlo statistics (when `--monte-carlo` is enabled)\n\n- Terminal wealth percentiles: 5th, 25th, 50th (median), 75th, 95th\n- Probability of positive nominal return over horizon\n- Probability of Max Drawdown exceeding 20%\n- Probability of Max Drawdown exceeding 40%\n- Median Max Drawdown across all simulated paths\n- Worst-5% Max Drawdown (95th percentile of drawdown magnitude)\n\n### Correlation analysis\n\n- Pearson correlation matrix between all sleeves of the main portfolio (monthly returns). Shows diversification quality and concentration risks within the allocation.\n\n---\n\n## Example output\n\n\u003e **Note.** Real outputs are generated only after you source the data and run the backtest. Below are the 13 chart types the engine produces, rendered in the order they appear in `output/REPORT.md`.\n\n| # | Chart | What you learn |\n|---|---|---|\n| 1 | `equity_curve.png` | Log-scale wealth paths of every portfolio side-by-side over 20 years |\n| 2 | `drawdown.png` | How deep each portfolio went into drawdown, running through time |\n| 3 | `underwater.png` | How long each portfolio stayed under the prior all-time high (one subplot per portfolio) |\n| 4 | `rolling_sharpe.png` | Rolling 3-year Sharpe ratio — shows where the risk-adjusted return came from |\n| 5 | `rolling_returns.png` | Rolling 3-year cumulative return — helps spot regimes of outperformance |\n| 6 | `crisis_zoom.png` | Side-by-side comparison during GFC, COVID, and Stagflation 2022 |\n| 7 | `annual_returns.png` | Grouped bar chart: each year, one bar per portfolio |\n| 8 | `return_distribution.png` | Overlapped histograms of monthly returns — shows tail behavior |\n| 9 | `risk_return_scatter.png` | CAGR vs Vol, marker size = |Max DD| — efficient-frontier-like positioning |\n| 10 | `metrics_comparison.png` | Grouped bar chart of CAGR, Vol, |Max DD|, Sharpe, Sortino, Calmar |\n| 11 | `correlation_heatmap.png` | Correlation matrix between all sleeves — diversification check |\n| 12 | `monte_carlo_fan.png` | Percentile fan chart from N bootstrap paths over a future horizon |\n| 13 | `monte_carlo_distribution.png` | Terminal-wealth histogram from the MC simulation |\n\nPlus `REPORT.md` bundling all of them in one Markdown document, and two CSV exports (`summary_statistics.csv`, `monthly_returns.csv`) for spreadsheet analysis.\n\n---\n\n## Quick start (5 minutes)\n\nWorks end-to-end with **synthetic data** so you can verify the plumbing before sourcing the real data.\n\n```bash\n# 1. Clone and install\ngit clone https://github.com/padosoft/portfolio-backtest.git\ncd portfolio-backtest\n\n# 2. Create virtual environment\npython -m venv .venv\n# macOS / Linux:\nsource .venv/bin/activate\n# Windows (PowerShell):\n.venv\\Scripts\\Activate.ps1\n\n# 3. Install dependencies\npip install -r requirements.txt\n\n# 4. Run on synthetic data (2-year, random-walk pricing, for plumbing check ONLY)\npython backtest.py --synthetic --start 2023-01-31 --end 2024-12-31\n\n# 5. Inspect outputs\nls output/\n# -\u003e REPORT.md + 13 PNG charts + 2 CSVs\n# Open output/REPORT.md in any Markdown viewer (VS Code works great).\n```\n\nIf `REPORT.md` opens and shows all 13 charts inline, the pipeline works. Now you're ready for real data.\n\n---\n\n\u003ca id=\"dashboard-local\"\u003e\u003c/a\u003e\n## Running the interactive dashboard locally\n\nThe Streamlit dashboard gives you a **full interactive UI** with sliders, checkboxes, portfolio selection, tabbed charts, AI analysis button, and a \"Save to output/\" checkbox. No CLI needed.\n\n### Install and launch (2 commands)\n\n```bash\npip install -r requirements-dashboard.txt\nstreamlit run streamlit_app.py\n```\n\nOpens automatically at [http://localhost:8501](http://localhost:8501) in your default browser.\n\n### What you can do in the dashboard\n\nSince PR4, the sidebar is just brand + links. Everything happens in the tabs at the top.\n\n**⚙️ Impostazioni tab — build your portfolio and run:**\n\n- **Carica preset** dropdown loads any `portfolios/*.toml` (Four Umbrellas ships by default).\n- **🎯 Portafoglio** — dynamic asset picker backed by `data/catalog.toml`:\n  - Search / select any asset (with its catalog key and start date)\n  - Add with a weight %\n  - Edit weights inline (each row editable via `st.data_editor`)\n  - 🗑️ per-row delete\n  - Live **Totale** badge: 🟢 at 100% ±0.2%, 🔴 otherwise — the Run button is disabled until the sum is valid.\n- **📅 Periodo** — date range picker. If your start predates any asset's data availability, a yellow ⚠️ warning identifies the constraining asset and the effective start date.\n- **⚙️ Motore** — starting NAV, options overlay toggle + budget (bps/year), rebalance frequency (Annual / Semi-annual / Quarterly / Monthly), transaction-cost bps.\n- **🎲 Analisi avanzate** — Monte Carlo toggle (paths + horizon + block). Efficient Frontier / FIRE / Walk-forward toggles are present but locked (🔒 arriva in PR5).\n- **Benchmarks** multi-select + **▶ Run backtest** + **💾 Save to output/**.\n\n**Post-run tabs (populated after a successful run):**\n\n- 📊 **Summary** — stats table + crisis-period drawdowns per portfolio.\n- 📈 **Charts** — 10 inline charts (equity curve, drawdown, underwater, rolling Sharpe/returns, crisis zoom, annual returns, return distribution, risk/return scatter, metrics comparison).\n- 🎲 **Monte Carlo** — Prudente / Mediana / Ottimista metric cards + fan chart.\n- 🤖 **AI Analysis** — pick a provider (OpenRouter / OpenAI / Anthropic / Local) and get structured Italian feedback inline.\n\n\u003e **Note.** The screenshots in the next section are from the pre-PR4 sidebar layout; they'll be refreshed once PR5 brings Frontier / FIRE / walk-forward into the UI.\n\n---\n\n\u003ca id=\"screenshots\"\u003e\u003c/a\u003e\n## 📸 Screenshots\n\n\n![result-charts.png](resources/result-charts.png)\n![dashboard.png](resources/dashboard.png)\n![result-summary.png](resources/result-summary.png)\n![result-efficient-frontier.png](resources/result-efficient-frontier.png)\n![result-montecarlo.png](resources/result-montecarlo.png)\n![result-fire.png](resources/result-fire.png)\n![result-walk-forward.png](resources/result-walk-forward.png)\n![result-Ai-Analysis.png](resources/result-Ai-Analysis.png)\n![saved-portfolio.png](resources/saved-portfolio.png)\n\n\n---\n\n\u003ca id=\"deploy-public\"\u003e\u003c/a\u003e\n## 🚀 Deploy the dashboard publicly\n\nThree deploy targets ship pre-configured. Pick your favorite.\n\n### Option 1 — Streamlit Cloud (one click, free for public repos)\n\n1. Push this repo to GitHub (public or private with access granted)\n2. Go to [share.streamlit.io](https://share.streamlit.io)\n3. New app → select your repo → main file: `streamlit_app.py`\n4. Add secrets (optional, for AI analysis): `OPENROUTER_API_KEY = \"...\"`\n5. Deploy. You get a public URL in ~2 minutes.\n\nThe bundled `.streamlit/config.toml` + `requirements-dashboard.txt` are picked up automatically.\n\n### Option 2 — HuggingFace Spaces (Docker-based, free tier)\n\n1. Create a new Space with SDK: **Docker**\n2. Clone this repo into the Space (or push a fork)\n3. The bundled `Dockerfile` builds the dashboard on HF's runner (port 7860)\n4. Push — the Space builds and serves at your HF URL automatically\n\n### Option 3 — your own server (Docker)\n\n```bash\ndocker build -t four-umbrellas-dashboard .\ndocker run -p 7860:7860 four-umbrellas-dashboard\n# → open http://localhost:7860\n```\n\nThe `Dockerfile` is tuned for HuggingFace Spaces (port 7860) but works anywhere Docker runs.\n\n---\n\n\u003ca id=\"fire\"\u003e\u003c/a\u003e\n## 🔥 FIRE calculator — plan your retirement\n\nStandalone FIRE (Financial Independence, Retire Early) Monte Carlo simulator with **Italian mortality tables** (ISTAT 2023) and INPS-style pension revaluation.\n\n### Basic usage\n\n```bash\npython fire.py --age 45 --sex M --capital 100000 --contributions 1000 \\\n  --fire-age 60 --spending 2500 --inflation 0.02 --simulations 1000\n```\n\n### With pension + tax\n\n```bash\npython fire.py --age 45 --sex M --capital 100000 --contributions 1000 \\\n  --fire-age 60 --spending 2500 --inflation 0.02 \\\n  --pension --pension-amount 1500 --pension-age 67 --pension-revaluation 0.75 \\\n  --tax-on-withdrawals --simulations 5000\n```\n\n### Configurable parameters\n\n| Group | Parameters |\n|---|---|\n| **Personal** | `--age`, `--sex`, `--fixed-end-age` (skip mortality sampling) |\n| **Wealth** | `--capital`, `--contributions`, `--frequency` (`month` or `year`) |\n| **Goal** | `--fire-age`, `--spending`, `--inflation` |\n| **Pension** | `--pension`, `--pension-amount`, `--pension-age`, `--pension-revaluation` (1.0 = 100% INPS perequazione, 0.75 = 75%, 0 = no adjustment) |\n| **Tax** | `--tax-on-withdrawals`, `--tax-rate` |\n| **Simulation** | `--simulations`, `--block-size`, `--seed` |\n\n### What it produces\n\nFour dedicated charts + statistics panel:\n\n1. **Portfolio projection** — percentile bands (5/25/50/75/95) from age to death, with FIRE age + pension start markers\n2. **Success probability** — % of simulations where portfolio \u003e 0 at each age\n3. **Failure age distribution** — histogram (or confirmation message if all successes)\n4. **Legacy distribution** — nominal + real final wealth histograms\n5. **Console summary** — success probability, median legacy (nominal + real), worst-case failure age\n\n---\n\n\u003ca id=\"ai\"\u003e\u003c/a\u003e\n## 🤖 AI analysis — LLM-driven qualitative review\n\nSend backtest results to an LLM for qualitative analysis (punti di forza, debolezze, raccomandazioni con numeri specifici, caveat metodologici, verdetto finale).\n\n### Provider support\n\n| Provider | Env var for the key | Default model (change with `--model`) |\n|---|---|---|\n| **OpenRouter** (default — one key, many models) | `OPENROUTER_API_KEY` | `anthropic/claude-opus-4-7` |\n| OpenAI | `OPENAI_API_KEY` | `gpt-4o` |\n| Anthropic | `ANTHROPIC_API_KEY` | `claude-opus-4-7` |\n| Local (Ollama / vLLM / LM Studio) | — (usually no key) | `kimi-k2-0.6b` |\n\nFor a **local** provider, optionally set `LOCAL_API_BASE_URL` (default `http://localhost:11434/v1` for Ollama).\n\n\u003ca id=\"ai-env-setup\"\u003e\u003c/a\u003e\n### 🔑 Configure your API key with a `.env` file (recommended)\n\nThe project reads API keys from a plain-text `.env` file at the project root. This is the easiest path for everyone who isn't already comfortable with `export` / `setx`.\n\n#### 1. Where does the file go?\n\nAt the **project root** — the same folder that contains `backtest.py`, `fire.py`, `analyze.py`, and `requirements.txt`:\n\n```text\nportfolio-backtest/\n├── backtest.py\n├── fire.py\n├── analyze.py\n├── .env              ← HERE (you create it)\n├── .env.example      ← template shipped with the repo\n├── requirements.txt\n├── src/\n└── …\n```\n\nThe file is named **exactly `.env`** — with the leading dot, no extension. On Windows Explorer, enable \"File name extensions\" to avoid accidentally creating `.env.txt`.\n\n`.env` is already listed in `.gitignore`, so your keys will never be committed.\n\n#### 2. Create it from the template\n\n```bash\n# Windows (PowerShell)\nCopy-Item .env.example .env\n\n# Windows (cmd.exe)\ncopy .env.example .env\n\n# macOS / Linux\ncp .env.example .env\n```\n\n#### 3. Edit `.env` and paste your key(s)\n\nOpen `.env` in any text editor (Notepad, VS Code, nano, …) and replace the placeholder with your real key. Set ONLY the provider(s) you actually use — leave the others as placeholders or delete their lines.\n\n```dotenv\n# Example — OpenRouter (recommended, single key for many models)\nOPENROUTER_API_KEY=sk-or-v1-abc123yourrealkeyhere\n\n# If you'd rather use OpenAI directly:\n# OPENAI_API_KEY=sk-proj-xxxxxx\n\n# If you'd rather use Anthropic directly:\n# ANTHROPIC_API_KEY=sk-ant-xxxxxx\n\n# Local (Ollama), no key needed. Only uncomment if your endpoint isn't the default:\n# LOCAL_API_BASE_URL=http://localhost:11434/v1\n```\n\nFormat rules:\n- `KEY=value`, one per line.\n- No spaces around `=`.\n- Quotes (`\"…\"`) are allowed but not required.\n- Lines starting with `#` are comments. Blank lines are ignored.\n- Real shell environment variables (if you already `export`ed them) **always win** over `.env`, so you can override per-session.\n\n#### 4. Where do I get the keys?\n\n| Provider | Sign up | Key page |\n|---|---|---|\n| OpenRouter | [openrouter.ai](https://openrouter.ai) | [openrouter.ai/keys](https://openrouter.ai/keys) |\n| OpenAI | [platform.openai.com](https://platform.openai.com) | [platform.openai.com/api-keys](https://platform.openai.com/api-keys) |\n| Anthropic | [console.anthropic.com](https://console.anthropic.com) | [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys) |\n\n### 💡 How to pick the AI model\n\nThe `--model` flag on `analyze.py` (or the \"Model\" input in the Streamlit dashboard) lets you switch models without touching the code. If you don't pass `--model`, the provider's default (see table above) is used.\n\n```bash\n# Use the provider's default model (OpenRouter → claude-opus-4-7)\npython analyze.py --results output/\n\n# Explicit model via OpenRouter — any model IDslug the provider supports\npython analyze.py --results output/ --provider openrouter --model anthropic/claude-opus-4-7\npython analyze.py --results output/ --provider openrouter --model openai/gpt-4o\npython analyze.py --results output/ --provider openrouter --model google/gemini-2.5-pro\npython analyze.py --results output/ --provider openrouter --model meta-llama/llama-3.3-70b-instruct\n\n# Direct OpenAI\npython analyze.py --results output/ --provider openai --model gpt-4o-mini\n\n# Direct Anthropic\npython analyze.py --results output/ --provider anthropic --model claude-sonnet-4-6\n\n# Local (Ollama — just run `ollama pull \u003cmodel\u003e` first)\npython analyze.py --results output/ --provider local --model llama3.1\n```\n\nFull list of OpenRouter model slugs: [openrouter.ai/models](https://openrouter.ai/models).\n\n### Usage (end-to-end)\n\n```bash\n# (one-time) Put your key in .env — see section above.\n\n# 1) Generate the report\npython backtest.py\n\n# 2) Send it to the LLM — default provider OpenRouter, default model Claude Opus 4.7\npython analyze.py --results output/\n\n# Same thing, different provider\npython analyze.py --results output/ --provider anthropic\npython analyze.py --results output/ --provider local --model kimi-k2\n\n# Include a sensitivity CSV as extra context for the model\npython analyze.py --results output/ --extra output/sensitivity/gold.csv\n```\n\nOutput: `output/AI_ANALYSIS.md` — structured Italian Markdown with 5 sections (diagnosi forze, debolezze, raccomandazioni numeriche, caveat, verdetto).\n\n### Alternative: shell environment variables (no `.env` file)\n\nIf you prefer not to use a `.env` file, export the variables directly in your shell. These override anything in `.env`:\n\n```bash\n# macOS / Linux / Git Bash\nexport OPENROUTER_API_KEY=\"sk-or-v1-...\"\n\n# Windows PowerShell (current session only)\n$env:OPENROUTER_API_KEY = \"sk-or-v1-...\"\n\n# Windows PowerShell (persisted across sessions)\n[System.Environment]::SetEnvironmentVariable(\"OPENROUTER_API_KEY\", \"sk-or-v1-...\", \"User\")\n\n# Windows cmd.exe (current session only)\nset OPENROUTER_API_KEY=sk-or-v1-...\n```\n\n### Streamlit Cloud / HuggingFace Spaces\n\nWhen deploying the dashboard, configure the same variable names as *secrets* in the hosting platform's UI — they land in `os.environ` automatically and are picked up by the same code path as the local `.env`. The `.env` file itself is `.gitignored` and never reaches the deployment.\n\n---\n\n## Advanced analyses\n\n### Sensitivity sweep\n\nVary one parameter across a range, see how CAGR/MaxDD/Sharpe/Calmar respond:\n\n```bash\npython backtest.py --sensitivity gold --range 0.10 0.25 --step 0.025\npython backtest.py --sensitivity dbi --range 0.03 0.10 --step 0.01\npython backtest.py --sensitivity options_budget --range 0.0 0.01 --step 0.001\npython backtest.py --sensitivity rebalance_freq --values 1 2 4 12\n```\n\n**Supported parameters**: `gold`, `dbi`, `options_budget`, `rebalance_freq`, `put_write`, `nasdaq_top30`, `momentum`, `quality`\n\nOutputs: `output/sensitivity/\u003cparam\u003e.csv` + 2×2 subplot chart (CAGR/MaxDD/Sharpe/Calmar vs param).\n\n### Rolling-window backtest\n\nStress-test the strategy across different starting dates:\n\n```bash\npython backtest.py --rolling-window --window-years 10 --step-months 1     # monthly step (default)\npython backtest.py --rolling-window --window-years 15 --step-months 12    # annual step (faster)\npython backtest.py --rolling-window --window-years 5 --step-months 3      # quarterly step\n```\n\nOutputs: `output/rolling_window/rolling_\u003cN\u003ey.csv` + 3-panel chart (CAGR/MaxDD/Sharpe distribution by starting year), console summary with `% windows with positive CAGR`, `% windows with CAGR \u003e 4%`, median/worst/best.\n\n### Efficient Frontier\n\nMarkowitz mean-variance optimization + 50k Dirichlet-sampled random portfolios:\n\n```bash\npython backtest.py --efficient-frontier --n-random 50000   # default\npython backtest.py --efficient-frontier --n-random 10000   # faster for dev iterations\n```\n\nOutput: scatter plot with 50k portfolios colored by Sharpe + smooth Markowitz frontier line + markers for Max Sharpe (★), Min Vol (◆), Max Return (▲) + **Four Umbrellas reference position** (🔵 circle) for direct comparison.\n\n---\n\n## Full onboarding\n\n### Prerequisites\n\n- **Python 3.11 or newer** (tested on 3.11, 3.12, 3.13, 3.14)\n- **500 MB free disk space** for data (more if you store multiple runs)\n- **Internet connection** for the initial data fetch\n- **Registration accounts (all free)** for MSCI, CBOE, SG, S\u0026P, FTSE data portals — see data/README.md\n- **~1 hour** for first-time data sourcing (mostly navigating providers' websites)\n\n### Step 1 — Install\n\nSee the full, step-by-step guide in [**Installing from scratch**](#install). The short version (if you already know venvs):\n\n```bash\ngit clone https://github.com/padosoft/portfolio-backtest.git\ncd portfolio-backtest\npython -m venv .venv\nsource .venv/bin/activate   # Windows PowerShell: .venv\\Scripts\\Activate.ps1\npip install -r requirements.txt\n```\n\n### Step 2 — Fetch public data automatically\n\n```bash\npython fetch_data.py\n```\n\nThis script downloads ~6 CSV files that are legally auto-fetchable (SPY, QQQ, VIX, BTC-USD from Yahoo Finance; EUR/USD and 3-month Treasury from FRED; computed S\u0026P 500 and Nasdaq 100 total-return series). It then **prints explicit instructions** for the remaining ~15 files that require manual download from their respective providers.\n\n### Step 3 — Download the remaining data manually\n\nSee [`data/README.md`](./data/README.md) for the full list. For each missing file:\n\n1. Visit the source URL printed by `fetch_data.py`\n2. Register (all free) if needed\n3. Download the monthly time series (2005–today, or your preferred range)\n4. Save it to `data/raw/` with **exactly** the filename specified in `data/README.md`\n\nThe most common files to fetch manually:\n\n- **MSCI indices** (Quality, Momentum, World ex-USA, Healthcare, Europe): [msci.com/end-of-day-data-search](https://www.msci.com/end-of-day-data-search)\n- **CBOE PUT Index**: [cboe.com/us/indices/dashboard/PUT](https://www.cboe.com/us/indices/dashboard/PUT/)\n- **SG CTA Index**: [wholesale.banking.societegenerale.com/en/prime-services-indices](https://wholesale.banking.societegenerale.com/en/prime-services-indices/)\n- **LBMA Gold**: [lbma.org.uk/prices-and-data](https://www.lbma.org.uk/prices-and-data/precious-metal-prices)\n- **S\u0026P 500 Healthcare TR**: [spglobal.com/spdji](https://www.spglobal.com/spdji/en/)\n\n### Step 4 — Validate\n\n```bash\npython -m src.data_loader --validate\n```\n\nThis prints `OK` if every expected file is present, or a list of missing filenames with instructions on where to fetch each one.\n\n### Step 5 — Run the backtest\n\n```bash\n# Default: full 20-year run (2005-2024), with options overlay, no Monte Carlo\npython backtest.py\n\n# Tailored: custom date range\npython backtest.py --start 2008-01-01 --end 2023-12-31\n\n# With Monte Carlo (adds ~1 minute of runtime for 10k paths)\npython backtest.py --monte-carlo --mc-paths 10000 --mc-years 20\n\n# All options\npython backtest.py --help\n```\n\n### Step 6 — Read the report\n\nOpen `output/REPORT.md` in any Markdown viewer. Recommended:\n\n- **VS Code** — built-in Markdown preview (Ctrl/Cmd+Shift+V)\n- **GitHub** — push the output folder to a branch, browse it there\n- **Typora / Obsidian** — nice for long-form reading\n- **Browser with Markdown extension** — e.g., \"Markdown Viewer\" on Chrome\n\nThe report contains:\n- Full run configuration (period, NAV, options on/off)\n- Summary statistics table\n- All 13 charts with captions\n- Crisis peak-to-trough table\n- Monte Carlo section (if enabled)\n- A short disclaimer\n\n---\n\n## Usage recipes\n\n### Run a specific decade\n\n```bash\npython backtest.py --start 2010-01-01 --end 2019-12-31 --output-dir output/2010s\n```\n\n### Exclude the options overlay (simpler variant)\n\n```bash\npython backtest.py --no-options --output-dir output/no-options\n```\n\n### Use a different starting NAV\n\n```bash\npython backtest.py --nav 250000\n```\n\nThe math is scale-invariant. The NAV only changes the displayed EUR values in charts and reports; all percentages and ratios are unchanged.\n\n### Monte Carlo sensitivity: how robust is the portfolio?\n\n```bash\n# 10,000 paths, 20-year future horizon, 3-month blocks\npython backtest.py --monte-carlo --mc-paths 10000 --mc-years 20 --mc-block-size 3\n\n# Longer horizon, more paths (slower)\npython backtest.py --monte-carlo --mc-paths 20000 --mc-years 30\n```\n\n### Sensitivity on portfolio parameters\n\nFor now, edit `src/portfolio.py` (`WEIGHTS`, `EQUITY`, `OPTIONS`, `REBALANCE`, `CASH`) and re-run with a different `--output-dir`. Compare the resulting `summary_statistics.csv` files across runs. A programmatic sweep tool is on the roadmap — contributions welcome.\n\n### Using synthetic data (development / plumbing check)\n\n```bash\npython backtest.py --synthetic --start 2023-01-31 --end 2024-12-31\n```\n\nGenerates random-walk returns. **Not real results** — useful only to verify that the code runs end-to-end on your machine.\n\n---\n\n## Configuration\n\nAll portfolio parameters live in **`src/portfolio.py`**. The main editable dictionaries:\n\n### Macro allocation\n\n```python\nWEIGHTS = {\n    \"pension\":  0.11,\n    \"gold\":     0.1825,\n    \"equity\":   0.4672,\n    \"crypto\":   0.0365,\n    \"bonds\":    0.0365,\n    \"em_sat\":   0.0073,\n    \"dbi\":      0.05,\n    \"cash\":     0.11,\n}\n```\n\n### Equity sleeve breakdown\n\n```python\nEQUITY = {\n    \"put_write\":    0.131,\n    \"nasdaq_top30\": 0.117,\n    \"hc_us_hedged\": 0.022,\n    \"hc_world\":     0.051,\n    \"quality\":      0.073,\n    \"momentum\":     0.058,\n    \"ex_usa\":       0.015,\n}\n```\n\n### Options overlay\n\n```python\n@dataclass\nclass OptionsConfig:\n    enabled: bool = True\n    budget_nav_per_year: float = 0.003        # 0.30% NAV/year\n    hedge_ratio_of_equity: float = 0.30       # 30% of equity notional\n    spy_qqq_split: Tuple[float, float] = (0.70, 0.30)\n    long_strike_pct: float = 0.87             # long put at 13% OTM\n    short_strike_pct: float = 0.70            # short put at 30% OTM\n    tenor_months: int = 6\n    take_profit_partial_multiple: float = 2.0\n    take_profit_full_multiple: float = 3.0\n    commission_per_contract: float = 1.0\n    iv_skew_adjustment: float = 1.15          # OTM IV ≈ 1.15 × ATM VIX\n```\n\n### Cash management\n\n```python\n@dataclass\nclass CashConfig:\n    target: float = 0.11\n    floor_hard: float = 0.08\n    comfort: float = 0.10\n    upper_band: float = 0.14\n```\n\n### Rebalancing\n\n```python\n@dataclass\nclass RebalanceConfig:\n    months: Tuple[int, ...] = (1, 7)          # January, July\n    transaction_cost_bps: float = 20.0        # 0.20% round-trip\n    band_relative_pct: float = 0.20           # ±20% of target weight\n    band_minimum_absolute: float = 0.005\n```\n\nEdit any of these and re-run — there is no \"compile\" step.\n\n---\n\n## Methodology\n\n### Core principles\n\n- **Monthly granularity for portfolio backtest** — standard practice in long-horizon multi-asset research; reduces noise and avoids daily-rebalancing survivorship artifacts.\n- **Daily granularity for options overlay** — required for path-dependent 2×/3× take-profit rule.\n- **EUR reporting currency** — unhedged USD positions converted using monthly end-of-month EUR/USD rates.\n- **Proxy indices** where ETFs did not exist for the full 20-year window (e.g., use CBOE PUT Index pre-2016 instead of PUTW ETF; use SG CTA Index pre-2019 instead of DBMF).\n- **Transaction costs modeled at 20 bps round-trip** per rebalance (reasonable retail approximation).\n- **TER deducted monthly** per sleeve using realistic real-world values.\n\n### Options pricing — Black-Scholes + VIX\n\nFor historical put spread pricing, we use the **Black-Scholes formula** with:\n- Spot = SPY/QQQ closing price\n- Risk-free rate = 3-month US Treasury yield (FRED)\n- Dividend yield = 1.5% (conservative approximation of SPY's historical yield)\n- **Implied volatility** = VIX × skew adjustment factor (to model the well-documented OTM put vol premium — the \"smile\")\n\nThis methodology is defensible for relative comparisons and is the standard in practitioner papers that do not have access to CBOE DataShop SPX option chains. See:\n\n\u003e **Israelov, R. \u0026 Nielsen, L. N. (2015).** *\"Still Not Cheap: Portfolio Protection in Calm Markets\"*. AQR / Journal of Portfolio Management.\n\nFor full-fidelity options pricing, purchase CBOE DataShop SPX options EOD data (~$200–500 for 20 years) and replace `src/options_overlay.py`'s pricing function.\n\n### Monte Carlo — block bootstrap\n\nThe MC simulation uses **block bootstrap** (default block size = 3 months) rather than IID resampling. Rationale: financial returns exhibit short-term autocorrelation (momentum, volatility clustering) that IID resampling destroys. Block bootstrap preserves these features.\n\n\u003e **Politis, D. N. \u0026 White, H. (2004).** *\"Automatic Block-Length Selection for the Dependent Bootstrap\"*. Econometric Reviews.\n\n### Rebalancing doctrine\n\n- **Semi-annual** (January + July) for macro and intra-sleeve drift\n- **Bands** (±20% of target weight, min ±0.5% NAV) — no rebalance if within band\n- **Annual** for benchmark portfolios (standard convention)\n- **Flow-driven preference** (rebalance-by-buying) — philosophical discussion in article §14\n\n### Known limitations\n\nAll of these are discussed in article §16 and §18. Briefly:\n- Survivorship bias in ETF selection (mitigated by index proxies)\n- Black-Scholes + VIX approximation of historical option prices\n- Stylized transaction costs\n- Pre-tax returns (no tax modeling)\n- No slippage during crises\n- Idealized rebalancing execution\n\nNet effect: the backtest is **slightly optimistic** vs real-world execution by ~20–40 bps/year over 20 years.\n\n---\n\n## Architecture\n\n```\nportfolio-backtest/\n├── backtest.py              # main entry point; CLI parsing, orchestration\n├── fetch_data.py            # public-data fetcher (Yahoo + FRED)\n├── requirements.txt\n├── LICENSE                  # MIT\n├── README.md                # this file\n├── src/\n│   ├── __init__.py\n│   ├── portfolio.py         # all configurable weights and parameters\n│   ├── data_loader.py       # CSV ingestion, currency conversion, alignment\n│   ├── rebalance.py         # semi-annual rebalancing engine\n│   ├── options_overlay.py   # BS + VIX put-spread simulator with 2×/3× rule\n│   ├── metrics.py           # CAGR, Sharpe, Max DD, Ulcer, CVaR, underwater\n│   ├── plots.py             # 13 chart generators (matplotlib)\n│   ├── monte_carlo.py       # block-bootstrap MC simulator\n│   └── report.py            # Markdown report generator\n├── data/\n│   ├── README.md            # data sourcing guide (22 CSV manifest)\n│   └── raw/                 # user populates (.gitignored)\n├── notebooks/\n│   └── colab_backtest.ipynb # one-click Google Colab version\n└── output/                  # results go here (.gitignored)\n    ├── REPORT.md            # bundled Markdown report\n    ├── summary_statistics.csv\n    ├── monthly_returns.csv\n    └── *.png                # 13 charts\n```\n\nPlus the testing infrastructure:\n\n```\n├── tests/\n│   ├── __init__.py\n│   ├── conftest.py                  # shared pytest fixtures\n│   ├── test_metrics.py              # 25+ tests on CAGR/Sharpe/MaxDD/...\n│   ├── test_portfolio.py            # 20+ tests on config integrity (weights sum to 1.0, etc.)\n│   ├── test_options_overlay.py      # Black-Scholes validation, put spread math, IV-from-VIX\n│   ├── test_rebalance.py            # portfolio + benchmark simulation\n│   ├── test_monte_carlo.py          # block bootstrap determinism, MC stats\n│   ├── test_data_loader.py          # synthetic bundle + date slicing\n│   ├── test_report.py               # Markdown report structure\n│   └── test_plots.py                # smoke test for all 11 chart generators\n├── pytest.ini                       # pytest configuration\n├── requirements-dev.txt             # pytest + pytest-cov + runtime deps\n└── .github/\n    └── workflows/\n        └── tests.yml                # CI: 3 OS × 2 Python versions\n```\n\n### Module responsibilities\n\n- **`backtest.py`** — entry point. Parses CLI, orchestrates the pipeline: data load → portfolio simulation → options overlay → metrics → charts → report.\n- **`src/portfolio.py`** — single source of truth for all configurable parameters (weights, option parameters, rebalance rules, cash thresholds, TER, symbol mapping).\n- **`src/data_loader.py`** — reads CSVs from `data/raw/`, aligns to end-of-month dates, converts USD returns to EUR where needed, produces a single `DataBundle` passed to the rest of the pipeline.\n- **`src/rebalance.py`** — the core portfolio simulation: tracks sleeve weights through time, applies TER, executes rebalance events with transaction costs.\n- **`src/options_overlay.py`** — Black-Scholes put spread simulator with daily mark-to-market, 2×/3× take-profit, semi-annual rolls, hard budget discipline.\n- **`src/metrics.py`** — every performance metric. Pure functions; easy to test; each metric in \u003c 20 lines.\n- **`src/plots.py`** — 13 chart generators, all matplotlib, all saved as 120-DPI PNG.\n- **`src/monte_carlo.py`** — block-bootstrap engine with configurable block size; generates wealth paths and summary statistics.\n- **`src/report.py`** — assembles `output/REPORT.md` from tables and PNG paths.\n\n---\n\n## FAQ\n\n### Q: Do I need to pay for any data?\n**A:** No. All data sources are free with registration (MSCI, CBOE, SG, S\u0026P, FTSE) or fully public (Yahoo Finance, FRED, LBMA). If you want higher-fidelity options pricing, CBOE DataShop is optional and paid.\n\n### Q: Why can't I get more recent data than the date ranges in the tests?\n**A:** The backtest uses your CSV files' actual date ranges. If you downloaded MSCI data only through 2023, the backtest will end at 2023. Re-download with fresher ranges to extend.\n\n### Q: Why are my backtest numbers slightly different from the article's numbers?\n**A:** Data can differ based on source (MSCI vs iShares-ETF-proxy, for instance), on whether \"Net TR\" or \"Gross TR\" was selected, on weekend/holiday handling, and on fetching date. Differences of 10–30 bps in CAGR are normal.\n\n### Q: The options overlay returns look odd in early periods (2005–2010). Why?\n**A:** The Black-Scholes + VIX approximation is less accurate for the deep historical period because implied-vol skew behavior differed materially pre-GFC. The approximation is sufficient for relative comparisons but not for precise P\u0026L replication of any specific year.\n\n### Q: Can I add my own sleeve (e.g., private equity, infrastructure)?\n**A:** Yes. Add the return series to `data/raw/`, add an entry in `SYMBOL_MAP`, `HEDGED`, `TER_ANNUAL`, and the appropriate `WEIGHTS`/`EQUITY`/other dict in `src/portfolio.py`. Make sure total weights sum to 1.0 (check in `build_target_weights()` in `src/rebalance.py`).\n\n### Q: Can I model TAX drag?\n**A:** Not yet. See [Roadmap](#roadmap). For now, all returns are pre-tax. For Italian residents, approximate after-tax results by applying 26% on realized capital gains at each rebalance (hard to do exactly because lots matter).\n\n### Q: Does this work on macOS / Linux / Windows?\n**A:** Yes, all three. Tested on Windows 11, macOS Sonoma, Ubuntu 24.04. The only OS-specific thing is the venv activation command.\n\n### Q: Is this production-ready for running my actual portfolio?\n**A:** **Absolutely not.** This is a research and educational tool. It is **not** a portfolio management system, not a trading system, and not a source of personalized investment advice. See [License \u0026 legal](#license--legal).\n\n### Q: Why Python and not R / Julia / MATLAB?\n**A:** Accessibility. More data scientists, quants, and curious readers know Python than the alternatives. The algorithms are straightforward to port if you prefer another language.\n\n### Q: Is the options overlay modeled realistically enough to trust for execution planning?\n**A:** Realistic enough for relative comparisons and order-of-magnitude sizing. Not realistic enough for precise trade execution forecasting. If you plan to actually run the overlay, validate your specific broker's behavior on a few test trades first.\n\n---\n\n## Testing\n\nThe project ships with a **pytest suite** covering every module. Tests run on every push to `main` and on every pull request via GitHub Actions (Ubuntu, macOS, Windows × Python 3.11, 3.12).\n\n### Running tests locally\n\n```bash\n# Install dev dependencies (includes pytest + pytest-cov)\npip install -r requirements-dev.txt\n\n# Run the full suite\npytest\n\n# Run with coverage report\npytest --cov=src --cov-report=term-missing\n\n# Run a single test file\npytest tests/test_metrics.py\n\n# Run a single test\npytest tests/test_options_overlay.py::TestBSPutPrice::test_atm_put_known_value\n\n# Skip slow / integration tests\npytest -m \"not slow\"\n```\n\n### Test coverage\n\n| Test file | Covers | What it verifies |\n|---|---|---|\n| `test_metrics.py` | `src/metrics.py` | CAGR / Sharpe / Sortino / Max DD / Calmar / Ulcer / CVaR / Underwater / Recovery — using analytically-known cases |\n| `test_portfolio.py` | `src/portfolio.py` | **Weights sum to 1.0**, equity sleeve sums match, enum integrity, hedging flags, symbol map completeness, benchmark validity |\n| `test_options_overlay.py` | `src/options_overlay.py` | Black-Scholes put pricing against Hull-textbook test case (S=K=100, T=1, r=5%, σ=20% → put ≈ 5.57), put-spread math, VIX-skew IV, end-to-end simulator |\n| `test_rebalance.py` | `src/rebalance.py` | Target weight construction, portfolio simulation, benchmark simulation, TER deduction |\n| `test_monte_carlo.py` | `src/monte_carlo.py` | Block bootstrap shape + determinism + reproducibility, wealth path simulation, MC stats |\n| `test_data_loader.py` | `src/data_loader.py` | Synthetic bundle generation, date slicing, data bundle structure |\n| `test_report.py` | `src/report.py` | Markdown report creation + section presence + portfolio name inclusion |\n| `test_plots.py` | `src/plots.py` | Smoke test for all 11 chart generators (file produced, non-zero size) |\n\n**Important invariants enforced by tests:**\n\n- `WEIGHTS` dict **must sum to 1.00** — any PR that breaks this fails CI\n- Equity sleeve breakdown **must sum** to `WEIGHTS[\"equity\"]`\n- Put spread value **must always be less** than the long put alone (monotonicity)\n- Options budget must be in `(0, 1%]` NAV/year (safety bound)\n- Long strike must be strictly greater than short strike\n- Block bootstrap **must be deterministic** given the seed\n- All Monte Carlo wealth paths start at 1.0\n- Every sleeve in `EQUITY` must have an entry in `HEDGED`, `SYMBOL_MAP`, `TER_ANNUAL`\n\n### Continuous integration\n\nThe `.github/workflows/tests.yml` workflow:\n\n1. Runs the full pytest suite on **Ubuntu, macOS, and Windows**\n2. Tests against **Python 3.11 and 3.12**\n3. Runs the synthetic-data smoke test end-to-end (`python backtest.py --synthetic`)\n4. Verifies that `output/REPORT.md`, at least one chart PNG, and `summary_statistics.csv` are produced\n5. Reports to GitHub and blocks PRs that fail any check\n\nTo see the latest CI run: [Actions tab](https://github.com/padosoft/portfolio-backtest/actions).\n\n### Writing new tests\n\nWhen adding a new feature or fixing a bug, include a corresponding test:\n\n- Tests live in `tests/test_\u003cmodule\u003e.py`\n- Use pytest fixtures in `tests/conftest.py` for shared test data\n- Prefer **analytically-known cases** (e.g., +1%/month for 12 months → CAGR ≈ 12.68%) over random-sampling tests\n- Use pytest's `@pytest.mark.slow` marker for tests that take \u003e 5 seconds\n\n---\n\n## Roadmap\n\n**✅ Shipped in v2.0** (see [What's new in v2.0](#whats-new-in-v20-)):\nMetrics++ · MC scenarios · extra benchmarks · sensitivity sweep · rolling-window · Efficient Frontier · Italian tax · FIRE calculator · AI analysis · Streamlit dashboard · CI/CD.\n\n**🛣 Next up — contributions welcome:**\n\n- [ ] **`--tax` CLI flag** — wire the existing `TaxLedger` / `TaxConfig` (already in `src/tax.py`, fully tested) to `backtest.py` so tax drag is a one-flag opt-in\n- [ ] **Higher-fidelity options pricing** — optional CBOE DataShop integration (user provides API key)\n- [ ] **Factor attribution** — Fama-French 5 + Momentum attribution per sleeve\n- [ ] **Tax lot optimization** — HIFO / specific-lot selection instead of FIFO for CGT minimization\n- [ ] **Monte Carlo regime-switching** — Hamilton-style 2-regime MC (calm / crisis) with transition probabilities\n- [ ] **PyPI package** — so users can `pip install portfolio-backtest`\n- [ ] **English AI output toggle** — today the LLM writes in Italian; add an `--lang en` flag\n- [ ] **Live data adapters** — polygon.io / EOD Historical Data optional connectors for fresh data without manual downloads\n- [ ] **Notebook gallery** — example notebooks showing each analysis interactively\n\nCommunity contributions on any of these are welcome — see [Contributing](#contributing).\n\n---\n\n## Contributing\n\nThis is an active research project with a growing community. **Contributions are very welcome**, especially:\n\n- 🐛 **Bug fixes** — open an issue with a reproduction; PRs welcome\n- 📊 **Additional benchmarks** — add a portfolio to `BENCHMARKS` in `src/portfolio.py` with a PR\n- 📝 **Documentation** — typos, clarifications, English or other language translations, additional usage recipes\n- 🚀 **Performance optimizations** — the current code is clarity-optimized, not speed-optimized; vectorized rewrites are welcome\n- 🧪 **More test coverage** — 267 tests today, always room for more — especially property-based tests for the options overlay\n- 🎨 **Dashboard UX** — Streamlit tweaks to improve the flow\n- 🤖 **New AI providers** — Google Gemini, Mistral, Groq would be trivial adds to `src/ai_analyzer.py`\n\n### Development workflow\n\n```bash\n# 1. Fork and clone your fork\ngit clone https://github.com/YOUR_USERNAME/portfolio-backtest.git\ncd portfolio-backtest\npython -m venv .venv \u0026\u0026 source .venv/bin/activate\npip install -r requirements-dev.txt     # installs pytest + pytest-cov + runtime deps\n\n# 2. Create a feature branch\ngit checkout -b feat/short-descriptive-name\n\n# 3. Make your changes. Keep commits small and focused.\n\n# 4. Run the full test suite before pushing\npytest                                   # must pass all tests\npytest --cov=src                         # optional: check coverage delta\n\n# 5. Verify the synthetic-data pipeline still runs end-to-end\npython backtest.py --synthetic --start 2023-01-31 --end 2024-12-31\n\n# 6. (Optional but recommended) Install the local pre-commit hooks\n#    so commit-time blocks unused imports + non-ASCII print() literals\npip install pre-commit\npre-commit install\n\n# 7. Push and open a PR against main (CI will run the full matrix +\n#    a `lint` job: ruff F401 + ASCII-only print check)\ngit push origin feat/short-descriptive-name\n```\n\n**PRs must pass CI to be merged.** The CI runs on Ubuntu / macOS / Windows × Python 3.11 / 3.12 (matrix tests) plus a single Ubuntu / 3.12 `lint` job that enforces no unused imports (ruff F401) and ASCII-only `print()` literals on the CLI surface (covers plain string constants AND f-string literal segments; computed expression slots inside f-strings are out of scope). If your PR adds new behavior, please add corresponding unit tests — see [Testing](#testing) for conventions.\n\n### Repository conventions\n\nThis repo accumulated 23 review patterns across 7 PRs of refactor work, distilled into:\n\n- **`CLAUDE.md`** — standing rules for any AI-assisted contributor (and a useful checklist for human contributors too). Imperative, grouped by moment of code change (\"before you commit\", \"when you change a signature\", \"when you parse external input\", \"when you write Streamlit code\", etc.). Read it once before your first PR.\n- **`LESSONS.md`** — per-incident archive of every Copilot review finding with the PR# and fix. CLAUDE.md is the digest; LESSONS.md is the canonical history.\n- **`.claude/skills/pr-self-review/SKILL.md`** — Claude Code skill that runs the 23-theme checklist against a staged diff. Useful before pushing.\n- **`scripts/check_ascii_print.py`** + **ruff F401** — automated lint enforcement. Optional local hooks via `.pre-commit-config.yaml`; mandatory CI gate.\n\n### Issue reporting\n\nUse GitHub Issues for:\n- **Bugs** — include reproduction command, expected vs actual output, environment (`python --version`, OS)\n- **Feature requests** — open for discussion before implementation\n- **Data sourcing help** — I may be able to point to an alternative source if one provider changes their access model\n\nPlease do **not** use issues for personalized investment advice requests. I can't and won't provide those.\n\n---\n\n## Citation\n\nIf you use this code or reproduce any result from it in your own writing, please cite:\n\n```bibtex\n@misc{padovani_four_umbrellas_2026,\n  author  = {Padovani, Lorenzo},\n  title   = {The Four Umbrellas Portfolio: An Enterprise-Grade, Academically-Grounded Global Allocation with Layered Defensive Convexity — 20-Year Backtest},\n  year    = {2026},\n  url     = {https://github.com/padosoft/portfolio-backtest},\n  note    = {Open-source Python backtest. MIT license.},\n}\n```\n\nPlain text:\n\u003e Padovani, L. (2026). *The Four Umbrellas Portfolio: A 20-Year Backtest of a Multi-Layer Defensive Global Allocation*. GitHub: padosoft/portfolio-backtest.\n\n---\n\n## License \u0026 legal\n\n### Code license\n\n**MIT License** — see [`LICENSE`](./LICENSE). You are free to use, modify, distribute, and sublicense the Python code for any purpose, including commercial. Attribution appreciated but not required.\n\n### Data license\n\n**Not redistributed.** The raw time-series data used as input is subject to the respective providers' licensing terms:\n\n- **MSCI indices** — licensed; may not be redistributed in raw form\n- **CBOE indices** (PUT, BXM, VIX historical) — licensed\n- **S\u0026P / FTSE indices** — licensed\n- **SG CTA Index** — licensed\n- **LBMA Gold prices** — usable for personal / commentary; redistribution restricted\n- **Yahoo Finance data** — ToS prohibits systematic bulk redistribution\n- **FRED data** — public domain (US Federal Reserve)\n\nUsers must download data from original providers. See [`data/README.md`](./data/README.md). Output charts and metrics **derived** from this data are the user's intellectual property, subject to whatever conclusions you draw.\n\n### Disclaimer\n\nThe outputs of this software — charts, tables, numerical results, simulations, including Monte Carlo — are **for informational and educational purposes only**. They are **NOT**:\n\n- investment advice\n- a recommendation to buy, sell, or hold any security\n- an offer or solicitation\n- a personalized financial plan\n- a guarantee of any future performance\n\nThe author is **not a registered investment advisor, financial analyst, portfolio manager, broker, fiduciary, or regulated professional** in any jurisdiction.\n\nPast performance and hypothetical simulations do **not** guarantee future results. All backtests are subject to survivorship bias, selection bias, look-ahead bias, and other methodological limitations that bias results upward. Real-world execution will produce different results.\n\n**You are solely responsible for your own financial decisions.** If you are considering actual investment actions based on this code or its outputs, consult a properly licensed professional in your jurisdiction. The author, contributors, and distributors of this software accept no liability whatsoever for any loss or damage arising from its use.\n\nSee the full disclaimer in the [accompanying Medium article](https://medium.com/@padosoft).\n\n---\n\n*Questions, feedback, corrections welcome. Open an issue or reach out on GitHub.*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flopadova%2Fportfolio-backtest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flopadova%2Fportfolio-backtest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flopadova%2Fportfolio-backtest/lists"}