A calm, browser-first portfolio control room for your XTB portfolio (and IBKR later). It turns raw broker statements into a plain-English monthly review: what you own, how much you put in, how much your investments actually gained, whether you are following your core/satellite plan, and what to buy with your next contribution.
Everything runs in your browser. Your portfolio data never leaves your machine — only public market symbols and currency pairs are sent to a small bundled PHP API that fetches and caches quotes, FX and historical bars.
Firefolio is free software under the GNU Affero General Public License,
version 3. See LICENSE. If you run a modified version over a network, the
AGPL requires that users be offered the corresponding source code.
- Dashboard — value, money contributed, investment gain, core share, FIRE gauge, top actions, and your full recommendation list.
- Review — a beginner-friendly monthly review with a copyable plain-English summary.
- Holdings — every open position with value, cost, unrealized P/L, role and class.
- Allocation — actual-vs-target by ticker / class / role, currency & region exposure, concentration, tiny positions, and a new-money rebalancing planner (167 / 250 / 500 / 1000 / custom).
- Analytics — daily timeline, time-weighted and money-weighted return, Modified Dietz, realized vs unrealized, drawdown, monthly returns, rolling returns and a return-decomposition waterfall. Estimates are clearly labelled.
- Benchmarks — growth-of-100 against VWCE, FWIA, V80A, SPY, QQQ, plus learning-oriented counterfactuals ("what if tactical buys had gone into VWCE?").
- Doctor — separates real issues from harmless noise, plus a cash reconciliation breakdown by account.
- Settings & data — import, prices/FX, the ticker table editor, transactions, copyable reports, FIRE export, settings, backup/reset.
- Any host that can serve static files and run PHP 7.4+ (the finance API is a single PHP file). A plain Apache/Nginx + PHP-FPM box, or
php -Sfor local use, is enough. - A modern browser (Chrome, Edge, Firefox, Safari). Imports use the browser's built-in zip/decompression APIs.
- No build step, no database, no Docker, no broker login.
Locally (quickest):
cd firefolio
php -S localhost:8000
# open http://localhost:8000On a web host: copy the whole firefolio/ folder into a PHP-capable web root. Make sure data/finance-cache/ is writable by the web server (that is the only writable directory; it stores cached public quotes).
If you open
index.htmldirectly withfile://, the app loads but price/FX/history updates fail because there is no PHP. Serve it over HTTP.
Go to Settings & data → Import and drop your XTB export. Firefolio accepts the XTB ZIP (containing the per-account .xlsx files) or the individual .xlsx files. It reads both the Cash Operations sheet (deposits, withdrawals, buys, sells, dividends, fees, taxes, transfers) and the Closed Positions sheet (for broker-exact realized P/L). Multiple accounts and currencies are handled and kept separate.
Re-importing is safe — rows are de-duplicated, so you can drop a fresh export each month.
Go to Settings & data → Prices & FX → Update prices. This sends only your tickers (as public symbols) to api/finance.php, which fetches quotes, applies XTB→Yahoo symbol mapping and GBp/pence scale fixes, and caches results for 6 hours. Then Update FX rates for any non-base currencies.
Go to Settings & data → Ticker table. This is where classification lives. For each ticker you can edit name, asset class, theme, region, quote currency, price symbol, scale, role and target weight, all inline.
- Roles: core, satellite, speculative, cash-like, legacy.
- Core ETFs are detected explicitly (VWCE.DE, FWIA.DE, AVWS.DE, V80A.DE) — never guessed from a fund name.
- Target weights accept either form:
0.48or48both mean 48%. A live indicator shows whether your targets sum to 100%. - Apply recommended classifications fills classes/roles/themes for known tickers; Apply recommended targets previews a starter target set before you commit.
- Search, sort and bulk-edit are supported. Edits are held until you press Save ticker table (an unsaved-changes banner reminds you). You can import/export the table as JSON or CSV.
Go to Analytics → Build analytics. Firefolio fetches daily history for your holdings and FX pairs and reconstructs a daily portfolio timeline. This unlocks the charts and return figures across Dashboard, Review, Analytics and Benchmarks. Rebuild whenever you import new data or change classifications.
Allocation shows where you have drifted from your target structure and includes the new-money planner: pick a contribution amount and Firefolio suggests what to buy, why, how much gap each buy closes, and what to avoid topping up — preferring new money over selling.
Benchmarks → Build comparison plots your portfolio's growth-of-100 against simple index ETFs and shows counterfactuals for behaviour review (not predictions).
The Dashboard lists a small, calm set of recommendations. Each shows severity, confidence, why it triggered, the supporting data, a suggested action, what happens if ignored, and a link to the relevant tab. Dismiss or Snooze (30 days) anything that does not apply.
Settings & data → Reports gives copyable plain-English narratives (monthly review, allocation, trading behaviour, FIRE summary) you can paste into a journal.
Settings & data → Backup & reset → Download backup exports a full JSON snapshot (transactions, prices, aliases, FX, settings) you can re-import later or move to another browser. Reset erases all local data.
Settings & data → FIRE export writes a clean, unencrypted JSON bridge (portfolio value, holdings/cash split, net contributed, investment gain, monthly contribution assumption, allocation summary, optional daily series and benchmark summary). Firefolio deliberately does not do full retirement scenario planning — that belongs in your separate FIRE app.
- Portfolio data (transactions, prices, aliases, settings) is stored locally in IndexedDB. A couple of small preferences (theme, dismissed/snoozed recommendations) live in
localStorage. - The only data sent to the server is public market symbols and currency pairs. No holdings, amounts, account numbers or personal data are transmitted.
- The PHP API caches public responses in
data/finance-cache/. No database is used.
- Returns, realized/unrealized P/L and counterfactuals are estimates for review, not tax or accounting outputs. Realized P/L is broker-exact when XTB's Closed Positions sheet is imported, and an average-cost estimate otherwise (clearly labelled).
- Charts, the daily timeline and benchmarks require the PHP finance API to fetch history; without network access those sections stay in their empty state, but everything else (holdings, allocation, classification, cash reconciliation) works offline from your import.
- IBKR Activity Flex CSV import is scaffolded for the future; XTB is the supported path today.
firefolio/
index.html # small static shell (nav + empty panels, rendered by JS)
assets/app.js # single build-free module: pure engine + browser UI
assets/styles.css # calm light theme (default) + optional dark theme
api/finance.php # public quotes / FX / history, with 6h file cache
data/default-aliases.json # reference classification library (also embedded in app)
data/finance-cache/ # cached public market data (must be writable)
If the XTB importer gets stuck at Reading <file>.zip…, use this build. It includes a built-in pure JavaScript ZIP/DEFLATE inflater so imports no longer depend on browser-specific DecompressionStream('deflate-raw') behaviour.
- Fixed an IndexedDB save stall that could stop around
600/710 row(s)during large XTB ZIP imports. - Broker rows are now saved with idempotent
put()operations in smaller 50-row chunks. - Re-importing the same export is still safe because rows are keyed by content hash.
- Import progress now reports saved rows instead of trying to classify new vs duplicate rows during the heavy save step.
This release fixes the remaining portfolio-reading issues found during review:
- Benchmarks now include
V60A.DE. - Learning counterfactuals prefer
FWIA.DEandV60A.DEinstead of onlyVWCE.DE. - Likely XTB subaccount cleanup / immediate reinvestment is de-emphasised in churn-style insights.
- ERNA/cash-like instruments are tracked in allocation but excluded from automatic new-money buy suggestions.
- Fixed quote scaling for London instruments such as
WISE.UK: the PHP API already normalises pence/GBp, so the browser no longer multiplies by the API scale again. - Analytics metrics have plainer explanations for time-weighted return, money-weighted return, Modified Dietz, rolling returns and drawdown.
- Actual-vs-target bars are clearer and no longer label-collide around target markers.
After installing, run Prices → Update holding prices once to correct any old WISE/GBp cached browser price row.
The Behaviour lessons section now has a clearer contribution-planner button and a more informative tiny-position simplification check. Tiny positions are compared against FWIA using still-open FIFO lots, so the result is meant as a practical behaviour lesson rather than a precise backtest.
Run the dependency-free engine tests with:
npm testdemo/firefolio-demo.json contains synthetic transactions only and can be imported through Firefolio's backup/import workflow. Generated market-data cache files are deliberately excluded from source control.
The canonical deployment is diogo.site/apps/firefolio/. Firefolio requires PHP for quotes, FX and historical prices, so GitHub Pages can host only the static shell, not the complete application.