Calculations
All analysis is rule-based (no LLM), pure and covered by tests.
Net worthβ
portfolio (investments) + off-portfolio assets (real estate, cash) β debt (loans)
Assets and positions held in a foreign currency are converted to PLN at the current NBP rate (table A); the conversion refreshes when the app starts, and further calculations then operate on amounts already in PLN.
Cryptocurrencies. A crypto position with a quantity and a recognised symbol is
valued at the spot rate from CoinGecko (public API, no key), just like
currencies against the NBP: value = quantity Γ rate, with the rate cached
(and refreshed) and a fallback to the last known price when there is no network.
The average purchase price = capital Γ· quantity; in advanced mode the quantity,
the weighted average (WAC) and the realised result are derived from the transaction
history. Privacy: the request for a public rate contains no data about you or
how much you hold β it is purely a market-price lookup.
Gold and metals. A metal position holds grams of pure metal
(weight Γ fineness/1000). Gold is valued at the NBP price (cenyzlota β
the official price of 1 g of 1000-fineness gold in PLN, published once per
business day; the same free API the currency rates come from):
value = pure grams Γ price per gram. The valuation refreshes on app start; a
very old cache (offline) never overwrites anything. Silver, platinum and
palladium are valued from the Stooq spot (XAG/XPT/XPD, USD per troy ounce)
converted at the NBP rate into zΕoty per gram of pure metal. Gain/loss is
measured against the purchase price entered when adding. A monthly
contribution to gold settles in grams (amount Γ· the month's NBP price;
past months come from the NBP archive, the positional median of daily quotes)
and lands in the position's purchase history β just like crypto contributions;
the position's initial holding is protected by a seed entry in the history.
Retail treasury bonds (EDO/COI/TOS/OTS/ROR/ROSβ¦). A position with series terms is valued by the bond engine: year 1 at the issue rate, years 2+ = CPI (GUS, y/y) + margin (deflation β margin only), with annual capitalization (EDO/TOS/ROS/ROD) or interest payouts (COI yearly, ROR/DOR monthly; payouts don't raise the position value β they're cash flow). Within a period interest accrues linearly by days. CPI comes from a bundled GUS table; months outside it use the position's editable inflation assumption. The early-redemption fee (zΕ/unit, per series) is capped at accrued interest. Simplification: ROR/DOR use the purchase rate (the NBP reference rate has no free CORS feed). Monthly contributions skip bond positions β buying more in another month is a different series, i.e. a new position from the catalog.
Quantity computed from contributions (automatically). In the monthly entry you don't enter how many units you bought β you provide the contribution amount in zΕoty, and the app converts it into units at the coin's rate for that month:
units = contribution amount (PLN) Γ· coin's rate for the given month (PLN)
The sum across months = the entire holding. We take the rate live for the
current month, and for past months β from the CoinGecko archive
(/coins/{id}/history, also public, no key). Each such contribution is saved as a
transaction in the history (a computed purchase), so the quantity, the average
price (WAC) and the value are calculated by the existing code. Saving the same
month again overwrites the entry (no double counting), while manually entered
transactions remain untouched. When the rate for a month cannot be fetched
(offline / unmapped coin), the contribution falls back to a plain zΕoty top-up β
we never block the save and never invent a quantity.
Savings rateβ
The average over recent months with income: contributions / income.
Financial health score (0β100)β
A weighted composition of components:
| Component | What it measures | Weight |
|---|---|---|
| Buffer | months of expenses in the reserve (target 6) | 25% |
| Savings rate | against the 20% target | 20% |
| Debt | the lower the debt share, the better | 15% |
| Diversification | 1 β the portfolio's Herfindahl index | 15% |
| Goals | average goal progress | 10% |
The score is the sum of value Γ weight normalised to the available components.
FIREβ
The FIRE portfolio = the investment lens (accounts + cash + rentals), not total wealth (your home/car are left out). We treat the rates as real (after inflation).
- Freedom amount (FIRE number) = (annual expenses β fixed post-FIRE income) Γ· SWR. Multiplier = 1/SWR (4% β Γ25; 3.5% β Γ28.6; 3% β Γ33).
- % FIRE = investment portfolio / freedom amount.
- SWR configurable (default 3.5%); monthly /12.
- Coast FIRE = the freedom amount discounted to today at the real return over the
years to retirement:
target Γ· (1+r)^years. "You already have enough that, without adding more, it will grow to the target." - Years to freedom β iteratively: capital Γ (1+r) + annual contribution, until β₯ target.
- Levels Lean / Full / Fat = 0.7Γ / 1Γ / 1.5Γ annual expenses.
- Monte Carlo β N draws of annual returns ~N(mean, volatility); "chance of success" = % of runs that reach the target (deterministic with a seed).
- Effective after-tax SWR β SWR Γ (1 β weighted average account tax: IKE 0, IKZE 10%, standard 19%). A directional simplification, not advice.
Return on investmentβ
We show three numbers (Analysis β Health):
- Total gain = current value β invested capital (absolute, in zΕ).
- Market return (TWR) β time-weighted: a chain of sub-period rates between snapshots with contributions taken out of each period. It answers "how did the market/strategy perform", independently of contribution timing. Showing TWR and MWR side by side is the industry standard: TWR judges the strategy, MWR β your actual outcome.
- Annual return (MWR) = money-weighted (XIRR) β the internal rate of return computed from cash flows: the oldest portfolio snapshot is the starting capital, subsequent contributions are added in their respective months, and the current value is the end state. This way the number accounts for how much and when you contributed β which the absolute gain alone doesn't capture (e.g. a large contribution just before a rise inflates the % gain but not the annual return). Computed monthly (Newton + bisection); requires at least 2 months of history, otherwise we show "β".
Value projectionβ
Future value = target contributions + compounding at the annual rate assumed for the position (e.g. ETF and IKE 8%, EDO 5.5%, ROS and COI 5.25%, PPK 6%). Debt is reduced by planned overpayments (conservatively, ignoring interest).
Crypto is an exception: we don't compound a fixed rate (it's not a deposit). The crypto projection is flat β current value + any contributions, with no assumed growth. In the projection table a crypto position shows "current value Β· no forecast" with a variable badge, and the editor has no "target" or "rate of return" fields (crypto is valued from the current rate).
Employer match (US 401(k) employer match). For a 401(k) account, in the editor you can set how much the employer adds to your contribution: a percentage of your monthly contribution (e.g. 50%) and an optional monthly match cap. Then the effective monthly contribution = your own + the match β and that is what compounds in the projection (the employer's capital grows too). The match counts as a contribution (capital), not as gain, so it doesn't inflate the result. With no match set, the projection stays unchanged (zero impact on the other accounts).
Tax on withdrawal β by account typeβ
We compute net worth depending on the account type (tax wrapper), not on a fixed basis. We keep the rules as data (limits and multipliers change every year):
- IKE, OIPE, PPK, PPE β a qualified withdrawal is tax-free (net = gross).
- IKZE β 10% of the entire withdrawal (not just the gain). The contribution lowers PIT in the year it is made β we show that relief separately (we don't add it to net worth), and only when you provide your PIT rate (we don't guess it).
- Standard account / bank β Belka 19% on the gain (gain = value β contributions β capital).
Positions with no assigned account type behave as before (the "taxable" flag = Belka), so the projection of existing plans doesn't change.
Other countries (per-country engine)β
The same engine computes the tax by country (country) β you choose it in Settings:
- π©πͺ Germany β Depot: Abgeltungsteuer 26.375% on the gain after Teilfreistellung (a portion exempt depending on the instrument) and the Sparerpauschbetrag allowance, plus an optional Kirchensteuer; RΓΌrup/Riester/bAV β a deduction on the way in and a taxed portion of the annuity (Besteuerungsanteil).
- π¬π§ United Kingdom β GIA: CGT 18/24% on the gain after the annual Β£3,000 allowance (a separate Β£500 dividend allowance); ISA/SIPP tax-free; the tax year 6 Aprilβ5 April (not the calendar year) affects how the limits are counted.
- πΊπΈ USA β brokerage: LTCG 0/15/20% depending on the holding period; Roth vs Traditional as a toggle (tax-free on the way out vs deductible on the way in); 529 maps to a child's education goal.
- π«π· France β CTO: PFU 31.4% split into the components PS 18.6% + IR 12.8%; PEA (0% IR after 5 years) and assurance-vie (after 8 years) computed by account tenure (opening date); PER β a deduction on the way in.
Projections don't fake a precision they don't have (e.g. the Vorabpauschale is explicitly not accounted for) β simplifying assumptions are flagged.
Annual contribution limitβ
For accounts with a limit (IKE/IKZE/OIPE, DE RΓΌrup/Riester/bAV, UK ISA/SIPP, US 401(k)/IRA/HSA, FR PEA/PER) we track the use of the annual limit β the sum of a given person's contributions to accounts of that type in the year against the limit. IKZE has a higher limit for people running a business (JDG). In the UK the "year" is the tax year (6 April). Accounts with no tracked limit (PPK/PPE, GIA, brokerage, CTOβ¦) don't show the bar.
Horizon and groupingβ
In Portfolio we show the projection for a single chosen horizon (2/5/10/20 years) instead of four columns β no sideways scrolling. Rows can optionally be grouped by person or account type.
Insightsβ
The rules generate tips with a severity of good / info / warn: loan payoff,
savings-rate trend, buffer, debt share, portfolio concentration (and crypto risk),
% FIRE. The same rules power your notifications.