⚙️ Portfolio Engine — Mathematical Model
⬅️ Back to Performance Metrics Overview
💡 Overview
This page formally defines the mathematical model underlying LibreFolio's portfolio calculation engine. All other metric pages (NAV, Book Value, Period P&L, WAC, Deposited Capital) reference this page for their precise computation rules.
📐 1. Notation and Sets
| Symbol | Meaning |
|---|---|
| \(V(u)\) | All brokers visible to user \(u\) |
| \(S \subseteq V(u)\) | Selected (filtered) broker scope |
| \(A\) | Set of assets with positions |
| \(C^*\) | Target currency |
| \([t_0, t_1]\) | Requested evaluation frame |
| \(q(a,b,t)\) | Quantity of asset \(a\) at broker \(b\) on date \(t\) |
| \(p(a,t)\) | Valuation price of asset \(a\) on date \(t\) |
| \(\mathrm{fx}(c_1, c_2, t)\) | Exchange rate from currency \(c_1\) to \(c_2\) on date \(t\) |
📐 2. Valuation Price
- \(p_{\text{mkt}}\) = backward-fill from PriceHistory (latest close with date \(\leq t\))
- \(p_{\text{buy}}\) = unit price of most recent BUY of \(a\) across all brokers in \(V(u)\), with date \(\leq t\)
- WAC is never used as valuation price
📐 3. Position State
For each position \((a, b)\) with \(q(a,b,t) > 0\):
Where \(w(a,b,t)\) is the Weighted Average Cost for position \((a,b)\) at date \(t\).
📐 4. WAC Iterative Update
Maintained per-position \((a,b)\) with pool state \((\hat{q}, \hat{c})\):
Acquisition (qty \(> 0\), unit cost \(u\)):
Reduction (qty \(< 0\)):
Ordering
Within the same date: additions processed before reductions. Ensures SELL reads the correct WAC including same-day BUYs.
📐 5. Portfolio Aggregation
📐 6. Three-Pool Cash Model — Per-Broker \((K_b, R_b, W)\)
Three accumulator pools track cash provenance. \(K\) and \(R\) are maintained per-broker \(b\); \(W\) is global (exits the system entirely).
| Pool | Scope | Meaning |
|---|---|---|
| \(K_b\) | Per-broker | External capital still in broker \(b\) as cash |
| \(R_b\) | Per-broker | Generated returns still in broker \(b\) as cash |
| \(W\) | Global | Returns that left the system (hidden, restorable on re-deposit) |
Key property
A BUY on broker \(b_1\) can only consume \(R_{b_1}\), never \(R_{b_2}\). Cash does not teleport between brokers — only explicit transfers move pool balances.
Update rules (per-transaction on broker \(b\), chronological)
| Icon & Type | Update Formulas | Logic & Description |
|---|---|---|
DEPOSIT \(D > 0\) |
\(r = \min(D,\, W)\) \(R_b \mathrel{+}= r\) \(W \mathrel{-}= r\) \(K_b \mathrel{+}= D - r\) |
Restores previously withdrawn returns from the global tracker \(W\) first, then adds the remainder to capital \(K_b\). |
WITHDRAWAL \(X > 0\) |
\(k = \min(X,\, K_b)\) \(K_b \mathrel{-}= k\) \(\rho = \min(X - k,\, R_b)\) \(R_b \mathrel{-}= \rho\) \(W \mathrel{+}= \rho\) |
Consumes capital \(K_b\) first, then moves remaining returns \(\rho\) to the global tracker \(W\). |
DIVIDEND / INTEREST \(I > 0\) |
\(R_b \mathrel{+}= I\) | Yields directly increase the returns pool \(R_b\). |
FEE / TAX \(F > 0\) |
\(R_b \mathrel{-}= F\) \(\text{if } R_b < 0\text{: } K_b \mathrel{+}= R_b,\; R_b = 0\) |
Consumes returns \(R_b\) first; if \(R_b\) becomes negative, it drains from capital \(K_b\). |
BUY \(B > 0\) |
\(\rho = \min(B,\, R_b)\) \(R_b \mathrel{-}= \rho\) \(K_b \mathrel{-}= (B - \rho)\) |
Consumes returns \(R_b\) first, then drains the rest from capital \(K_b\). |
SELL |
\(G = P - C\) \(K_b \mathrel{+}= C\) \(R_b \mathrel{+}= G\) \(\text{if } R_b < 0\text{: } K_b \mathrel{+}= R_b, \quad R_b = 0\) |
Cost basis $C = |
CASH TRANSFER (Internal, \(s \to d\), \(X > 0\)) |
Departure Leg (\(s\)): \(\rho = \min(X,\, R_s)\) \(R_s \mathrel{-}= \rho\) \(\kappa = X - \rho\) \(K_s \mathrel{-}= \kappa\) Arrival Leg (\(d\)): \(K_d \mathrel{+}= \kappa\) \(R_d \mathrel{+}= \rho\) |
Internal cash transfers move pool allocations (\(R_s \to R_d\), \(K_s \to K_d\)) proportional to the departure balance. The global tracker \(W\) is never touched (capital remains inside the system). |
If departure and arrival dates differ, the transfer is in-transit: subtracted from \(s\) at departure day, added to \(d\) at arrival day. Between those dates, \(\sum K_b + \sum R_b < \mathrm{Cash}_{\text{like}}\) by the in-transit amount — handled by proportional reconciliation.
Aggregation for output
Reconciliation invariant
Proportional per-broker scaling applied if drift \(> 0.01\) (from FX rounding or in-transit timing).
📐 7. Period Contribution
For period \([t_0, t_1]\), per-position \((a,b)\):
Contribution position set:
Unallocated (fees/income without asset_id) grouped per broker.
📐 8. Realized Gain/Loss
On SELL of \(|q_s|\) units from position \((a,b)\):
Where \(w_{\text{pre}}\) is the WAC before the pool reduction (same value used by 3-pool SELL rule above).
📐 9. Pre-Frame / Frame Architecture
| Phase | Date range | Computes |
|---|---|---|
| Pre-frame | \([t_{\mathrm{first}},\ t_0)\) | Cash, qty, WAC, pools — no market evaluation |
| Frame | \([t_0,\ t_1]\) | Full daily: prices, FX, position states, portfolio states |
Pre-frame transactions update accumulators (cash ledger, WAC pools, 3-pool K/R/W) without consuming price or FX data. This enables efficient range-based caching.
📐 10. Performance Metrics (Layer 2)
Computed after daily states, as a separate pass:
| Metric | Formula | Reference |
|---|---|---|
| Total PnL | \(\mathrm{NAV}(t) - \text{DepositedCapital}(t)\) | Deposited Capital |
| Period PnL | \(\mathrm{NAV}(t_1) - \mathrm{NAV}(t_0) - \text{ECF}_{[t_0,t_1]}\) | Period P&L |
| TWRR | \(\prod_i (1 + r_i) - 1\) (sub-period chain) | TWRR |
| MWRR | XIRR solving \(\sum \frac{CF_i}{(1+r)^{d_i/365}} = 0\) | MWRR |
| Simple ROI | \((\mathrm{NAV} - \text{NetInvested}) / \text{NetInvested}\) | ROI |
| Timing Effect | \(\text{MWRR}_{\text{cum}} - \text{TWRR}_{\text{cum}}\) | Timing Effect |
🔗 Related
- 💼 NAV — snapshot valuation
- 📖 Book Value — cost basis aggregate
- 📊 Period P&L — windowed gain/loss with contribution
- 💸 Deposited Capital — 3-pool details and worked examples
- 📈 WAC — iterative cost method