{"id":47637595,"url":"https://github.com/flash286/portfolio-tracker","last_synced_at":"2026-04-02T00:25:43.568Z","repository":{"id":340547189,"uuid":"1166011402","full_name":"flash286/portfolio-tracker","owner":"flash286","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-25T13:38:54.000Z","size":685,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-02-25T14:59:58.451Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flash286.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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-24T19:34:12.000Z","updated_at":"2026-02-25T13:38:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/flash286/portfolio-tracker","commit_stats":null,"previous_names":["flash286/portfolio-tracker"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/flash286/portfolio-tracker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flash286%2Fportfolio-tracker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flash286%2Fportfolio-tracker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flash286%2Fportfolio-tracker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flash286%2Fportfolio-tracker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flash286","download_url":"https://codeload.github.com/flash286/portfolio-tracker/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flash286%2Fportfolio-tracker/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31293376,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: 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":[],"created_at":"2026-04-02T00:25:40.239Z","updated_at":"2026-04-02T00:25:43.555Z","avatar_url":"https://github.com/flash286.png","language":"Python","funding_links":["https://buymeacoffee.com/flash286"],"categories":[],"sub_categories":[],"readme":"# Portfolio Tracker\n\n[![CI](https://github.com/flash286/portfolio-tracker/actions/workflows/ci.yml/badge.svg)](https://github.com/flash286/portfolio-tracker/actions/workflows/ci.yml)\n[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)\n[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-flash286-FFDD00?style=flat\u0026logo=buy-me-a-coffee\u0026logoColor=black)](https://buymeacoffee.com/flash286)\n\nTrack your ETF portfolio with full **German tax compliance** — Abgeltungssteuer,\nTeilfreistellung, Vorabpauschale, FIFO lots — all from the command line.\nBuilt for European ETF investors living in Germany.\n\n\u003e **Designed for:** DE tax residents · EUR-denominated ETF portfolios · buy-and-hold investors\n\u003e\n\u003e **Not designed for:** US investors, multi-currency portfolios, real-time trading\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screen%20-2.png\" alt=\"Web dashboard — allocation charts and tax summary\" width=\"900\"\u003e\n  \u003cbr\u003e\u003cem\u003eWeb dashboard — allocation, holdings, tax summary\u003c/em\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screen%20-1.png\" alt=\"CLI — holdings list and stats\" width=\"900\"\u003e\n  \u003cbr\u003e\u003cem\u003eCLI — pt holdings list and pt stats summary\u003c/em\u003e\n\u003c/p\u003e\n\n---\n\n## Features\n\n- **Portfolio \u0026 holdings** — multiple portfolios, buys/sells/dividends, FIFO lots\n- **German tax engine** — Abgeltungssteuer (25%) + Soli (5.5%), Teilfreistellung, Freistellungsauftrag\n- **Vorabpauschale** — annual prepayment tax for accumulating ETFs (§18 InvStG)\n- **Rebalancing** — target allocations by ISIN/type, deviation check, trade suggestions\n- **Price fetching** — automatic yfinance lookups for European ETFs (`.DE` `.L` `.AS` …)\n- **Cash tracking** — full ledger: top-ups, buys, sells, dividends, fees\n- **Performance history** — Time-Weighted Return over 1m/3m/6m/1y/2y/all, auto-fetches historical prices\n- **Revolut import** — idempotent one-command CSV import\n- **Web dashboard** — tabbed SPA: allocation charts, sortable holdings table, P\u0026L bars, FSA progress, tax summary, rebalancing, AI Analysis\n- **AI Analysis** — one-click portfolio review by Claude / GPT / Gemini in the dashboard\n- **AI import** — Claude Code skill imports any broker CSV without writing code\n\n---\n\n## Setup\n\n**Requirements:** Python 3.10+ and [uv](https://docs.astral.sh/uv/)\n\n```bash\ngit clone https://github.com/flash286/portfolio-tracker.git\ncd portfolio-tracker\n\nmake setup     # installs Python deps + dashboard (Node.js optional)\nuv run pt --help\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eDon't have \u003ccode\u003euv\u003c/code\u003e yet?\u003c/summary\u003e\n\n```bash\n# macOS / Linux\ncurl -LsSf https://astral.sh/uv/install.sh | sh\n\n# Windows\npowershell -ExecutionPolicy ByPass -c \"irm https://astral.sh/uv/install.ps1 | iex\"\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ePrefer plain pip?\u003c/summary\u003e\n\n```bash\npython -m venv .venv\nsource .venv/bin/activate      # Windows: .venv\\Scripts\\activate\npip install -e \".[dev]\"\npt --help\n```\n\n\u003c/details\u003e\n\n### First-run configuration\n\n```bash\npt setup run\n```\n\nThe wizard configures your tax profile (country, FSA amount, Zusammenveranlagung,\nchurch tax, exchange suffix) and optionally sets up an AI provider for dashboard\nanalysis. Settings are saved to `config.json` (gitignored).\n\n### Make targets\n\n```bash\nmake setup    # install all deps (Python + dashboard)\nmake check    # lint + tests\nmake dev ID=1 # Vite HMR dev server\nmake build    # build dashboard to dist/\nmake help     # show all targets\n```\n\n### Shell completion\n\nEnable tab completion for all `pt` commands and subcommands:\n\n```bash\n# zsh (add to ~/.zshrc)\neval \"$(_PT_COMPLETE=source_zsh pt)\"\n\n# bash (add to ~/.bashrc)\neval \"$(_PT_COMPLETE=source_bash pt)\"\n```\n\nRestart your shell, then `pt \u003cTab\u003e` and `pt stats \u003cTab\u003e` will autocomplete.\n\n---\n\n## Quick start\n\n```bash\npt portfolio create \"My Portfolio\"        # → ID 1\npt holdings add 1 IE00BK5BQT80 etf \\\n    --name \"Vanguard FTSE All-World\" \\\n    --ticker VWCE --tfs-rate 0.3\npt tx buy 1 10 115.42 --date 2025-01-15\npt prices fetch 1\npt stats summary 1\n```\n\n```\nPortfolio: My Portfolio\n\n  Total Cost Basis      €1,154.20\n  Holdings Value        €1,282.78\n  Cash Balance              €0.00\n  Portfolio Value       €1,282.78\n  Unrealized P\u0026L     +€128.58 (+11.1%)\n```\n\n---\n\n## Revolut import\n\n### Export from Revolut\n\n1. **Revolut app** → Investments → portfolio → **···** → **Export transactions**\n2. Download:\n   - **Transactions CSV** — buys, dividends, cash flows\n   - **P\u0026L CSV** — realized gains with ISIN details (recommended)\n\n### Run the import\n\n```bash\npt import revolut data/revolut_transactions.csv --pnl data/revolut_pnl.csv\n```\n\n```\n╭──────────── Import result ─────────────╮\n│  Portfolio             Revolut (ID 1)  │\n│  Holdings created                   8  │\n│  Buys imported                     56  │\n│  Dividends imported                42  │\n│  Cash rows imported               146  │\n╰────────────────────────────────────────╯\nDone. Run pt holdings list to verify.\n```\n\nThe import is **idempotent** — re-running on the same file skips all duplicates (SHA-256 deduplication). Use `--dry-run` to validate without writing.\n\n### Options\n\n| Flag | Description |\n|------|-------------|\n| `--pnl \u003cfile\u003e` | P\u0026L CSV for ISIN enrichment (recommended) |\n| `--portfolio-name / -n` | Portfolio name to create or reuse |\n| `--portfolio-id / -p` | Use an existing portfolio by ID |\n| `--dry-run / -d` | Validate without writing to DB |\n| `--no-interactive` | Skip prompts for unknown tickers (CI/script mode) |\n\n---\n\n## Manual portfolio setup\n\n```bash\npt portfolio create \"Trade Republic\"\n\npt holdings add 1 IE00BK5BQT80 etf --name \"VWCE\" --ticker VWCE --tfs-rate 0.3\npt holdings add 1 IE00BMC38736 etf --name \"VVSM\" --ticker VVSM --tfs-rate 0.3\n\npt tx buy 1 100 115.42 --date 2025-01-15\npt cash add 1 1000 --type top_up --desc \"January deposit\"\n```\n\n---\n\n## Prices\n\n```bash\npt prices fetch 1              # fetch current prices for all holdings\npt prices history \u003cholding_id\u003e\n```\n\nyfinance is used for lookups. European ETFs require exchange suffixes — the tracker handles this automatically (tries `.DE` → `.L` → `.AS` → `.PA` → `.MI`).\n\nAdd overrides in `src/portfolio_tracker/external/price_fetcher.py`:\n```python\nTICKER_OVERRIDES = {\n    \"VWCE\": \"VWCE.DE\",\n    ...\n}\n```\n\n---\n\n## Statistics\n\n```bash\npt stats summary 1               # P\u0026L, allocation, estimated tax\npt stats allocation 1            # breakdown by asset type\npt stats performance 1           # value history + Time-Weighted Return\npt stats performance 1 --period 2y   # longer range: 1m|3m|6m|1y|2y|all\n```\n\nThe tax estimate in `summary` applies Teilfreistellung → Freistellungsauftrag → Abgeltungssteuer + Soli. This is a hypothetical estimate (as if you sold everything today).\n\n`pt stats performance` automatically backfills missing history from yfinance the first time you run it for a given period — no manual setup needed.\n\n---\n\n## Performance snapshots\n\n```bash\npt snapshot take 1               # record today's portfolio value\npt snapshot backfill 1           # fill history from yfinance (all time)\npt snapshot backfill 1 --since 2024-01-01 --interval 1d   # daily, from a date\n```\n\nSnapshots power the performance chart in `pt stats performance` and the dashboard. `pt dashboard open` records a snapshot automatically on every open.\n\n---\n\n## Tax commands\n\n### Realized gains\n\n```bash\npt tax realized 1 --year 2025\n```\n\nShows all sells with FIFO-matched cost basis, Teilfreistellung, FSA deduction, and estimated Abgeltungssteuer + Soli.\n\n### FIFO tax lots\n\n```bash\npt tax lots \u003cholding_id\u003e\n```\n\nShows individual cost lots — acquired date, quantity, cost/unit, remaining.\n\n### Vorabpauschale\n\n```bash\npt tax vorabpauschale 1 --year 2024\n```\n\nCalculates the annual prepayment tax for accumulating ETFs (§ 18 InvStG):\n\n```\nBasisertrag/share = Kurs(Jan 1) × Basiszins × 0.7\nVorabpauschale    = min(Basisertrag, Fondszuwachs) × Anteile\n```\n\n**Basiszins** (BMF-published):\n\n| Year | Basiszins |\n|------|-----------|\n| 2023 | 2.55% |\n| 2024 | 2.29% |\n| 2025 | 2.53% |\n\nRun once in January after the previous year's prices are final. Results are cached and shown in the dashboard.\n\n---\n\n## Rebalancing\n\n```bash\npt rebalance target 1        # set target allocations interactively\npt rebalance check 1         # show current deviation from targets\npt rebalance suggest 1       # suggest trades with cash impact\npt rebalance execute 1       # execute trades and record transactions\n```\n\n---\n\n## Web dashboard\n\n```bash\npt dashboard open 1\npt dashboard open 1 --output /tmp/portfolio.html   # save to file\n```\n\nOpens a local HTTP server at `http://127.0.0.1:\u003cport\u003e` and launches the browser. Press **Ctrl+C** in the terminal to stop. No CDN, no external requests (except the optional AI Analysis call).\n\n### Development mode\n\n```bash\npt dashboard dev 1                  # legacy live-reload (watches dashboard.html)\npt dashboard dev 1 --vite           # Preact + Vite HMR (requires Node.js)\npt dashboard dev 1 --data-refresh   # re-collect portfolio data on each reload\n```\n\nThe `--vite` mode starts a Vite dev server with hot module replacement and a Python API backend for live data. Requires Node.js; dependencies are installed automatically on first run.\n\nThe dashboard is organised into **5 tabs**:\n\n| Tab | Content |\n|-----|---------|\n| **Overview** | KPI cards · allocation donut charts · P\u0026L bar chart per holding |\n| **Holdings** | Sortable, filterable table (click any column header to sort) |\n| **Tax** | Freistellungsauftrag progress bar · tax summary · realized gains |\n| **Rebalancing** | Target vs. actual deviation bars |\n| **AI Analysis** | One-click deep analysis by your configured AI provider |\n\nOther features:\n- **Copy as Markdown** — exports a full portfolio snapshot for AI review\n- Auto-records a snapshot on every open (used by the performance chart)\n\n---\n\n## AI Analysis\n\nThe dashboard includes a built-in **AI Analysis** panel powered by your choice of LLM. Click **Generate Analysis** to get a structured financial advisor review of your portfolio.\n\n![AI Analysis](docs/screen%20-3.png)\n\n### What the AI produces\n\n| Section | Content |\n|---------|---------|\n| **Overall** | Rating (strong / good / fair / weak) + 2–3 sentence summary |\n| **Performance Highlights** | Top winners and underperformers with commentary |\n| **Risk \u0026 Diversification** | Concentration risk, geographic exposure, ETF overlap warnings |\n| **Tax Optimization** | Freistellungsauftrag usage, Vorabpauschale notes, actions needed |\n| **Recommendations** | Numbered, prioritised action items |\n\n### Supported providers\n\n| Provider | Default model |\n|----------|--------------|\n| **Anthropic** | `claude-sonnet-4-6` |\n| **OpenAI** | `o3` |\n| **Google Gemini** | `gemini-2.5-pro` |\n\n### Setup\n\n```bash\npt setup run\n# → answer the \"AI Analysis\" step at the end\n# → choose provider, paste API key, optionally override model\n```\n\nOr edit `config.json` directly:\n\n```json\n{\n  \"ai_provider\": \"anthropic\",\n  \"ai_api_key\": \"sk-ant-...\",\n  \"ai_model\": \"\"\n}\n```\n\nThen reopen the dashboard:\n\n```bash\npt dashboard open 1\n```\n\n\u003e The API key is stored only in `config.json` (gitignored) and embedded in the locally-generated\n\u003e temp HTML file. It is never sent anywhere except the chosen provider's API endpoint.\n\n---\n\n## Cash management\n\n```bash\npt cash balance 1           # current balance\npt cash history 1           # full ledger\npt cash add 1 500 --type top_up\n```\n\nTypes: `top_up`, `withdrawal`, `buy`, `sell`, `dividend`, `fee`. Buy/sell transactions automatically create matching cash entries.\n\n---\n\n## AI import (Claude Code skill)\n\nThis project includes [Claude Code Skills](https://docs.anthropic.com/en/docs/claude-code/skills) — reusable AI workflows that drive the CLI. No separate server or API key needed beyond your existing Claude Code session.\n\n### Skills\n\n| Command | Description |\n|---------|-------------|\n| `/portfolio import \u003cfile\u003e` | Import from any broker CSV |\n| `/portfolio summary` | Full portfolio overview + tax snapshot |\n| `/portfolio` | Show menu |\n\nThe skill lives in `.claude/skills/portfolio/SKILL.md` and is committed to the repo —\navailable to anyone who clones it.\n\n### Universal broker import\n\n```\n/portfolio import data/trading212_export.csv\n```\n\nClaude reads the columns, maps them to `pt` commands, and imports:\n1. Shows a dry-run summary (counts by type, date range, tickers)\n2. Asks for confirmation before writing anything\n3. Creates missing holdings and records all transactions chronologically\n\n\u003e Unlike `pt import revolut`, the AI import is **not idempotent**. Running it twice creates\n\u003e duplicates — the skill always shows a preview and asks before proceeding.\n\n### Portfolio overview\n\n```\n/portfolio summary\n```\n\nRuns `pt stats`, `pt holdings list`, `pt rebalance check`, and optional tax commands,\nthen gives 1–3 actionable recommendations based on the data.\n\n---\n\n## CLI reference\n\n```\npt portfolio  create | list | show | delete\npt holdings   add | list | remove\npt tx         buy | sell | list\npt prices     fetch | history\npt stats      summary | allocation | performance\npt snapshot   take | backfill\npt rebalance  target | check | suggest | execute\npt cash       balance | history | add\npt tax        realized | lots | vorabpauschale\npt import     revolut\npt dashboard  open | dev\npt setup      run\n```\n\n---\n\n## Stack\n\n| | |\n|---|---|\n| Language | Python 3.10+ |\n| Package manager | [uv](https://docs.astral.sh/uv/) |\n| CLI | [Typer](https://typer.tiangolo.com) + [Rich](https://github.com/Textualize/rich) |\n| Database | SQLite (`portfolio.db`) |\n| Prices | [yfinance](https://github.com/ranaroussi/yfinance) |\n| Dashboard | [Preact](https://preactjs.com) + [Vite](https://vite.dev) — builds to a single HTML file |\n| AI Providers | Anthropic Claude, OpenAI, Google Gemini (optional) |\n\n---\n\n## Privacy\n\nAll data stays **local**:\n- `portfolio.db` — SQLite on your machine\n- `config.json` — local config, gitignored\n- No accounts, no cloud sync, no telemetry\n- AI Analysis calls your configured provider's API directly with your own key\n\n---\n\n## Disclaimer\n\nThis tool provides estimates for informational purposes only.\nIt is **not financial advice** and **not a substitute for professional tax counsel**.\nAlways verify Vorabpauschale and Abgeltungssteuer calculations with a Steuerberater\nor your Finanzamt before filing.\n\n---\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflash286%2Fportfolio-tracker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflash286%2Fportfolio-tracker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflash286%2Fportfolio-tracker/lists"}