Financial systems

Fund-accounting terminal

A web terminal for small-fund accounting, now open source. When the market data it needs is missing it refuses to write a fund value rather than record one it cannot stand behind — and around that sit NAV unit accounting, positions derived from an append-only trade ledger, and seven investor documents generated from the book.

Open source, MIT · started July 2025 · Python and Flask, React front end

  • MITpublic repository, readable in full
  • exactdecimal money, written as text
  • 7investor documents from the book
  • 0real financial figures, anywhere

The build

When several people put money into one pot, you need to track who owns what share as money goes in and out at different times. The standard answer is unitization: the fund's value is divided into units, like shares in a fund. Put money in and you are issued units at today's price; take money out and units are redeemed at today's price. This is the terminal that does that arithmetic, prices the holdings against live market data, and produces the paperwork an investor actually expects to receive.

It declines to record a number it cannot stand behind

Working out what the fund is worth means asking the market what every holding is worth at that moment. The interesting path is the one where the answer arrives incomplete. If a quote is missing — the price feed is down, the ticker has gone away, the market is shut — the recomputation stops and writes nothing at all, and the last figure the book could stand behind stays as the figure. Mark to market, the industry's name for that recomputation, means restating a holding at what the market says it is worth today rather than what was paid for it.

The alternative is worse than it looks. Filling the gap with yesterday's price, or a zero, or by quietly skipping the holding, produces a fund value that is indistinguishable from a real one and is wrong — and because every unit price, every member's ownership share and every return is computed from that one number, the error propagates into all of them at once and leaves no trace of where it came from. A refusal is loud, dated and obviously a refusal. It is the same discipline as the agent-orchestration measurement on this site, whose generated report is reproduced with the sections where it declines to answer left in.

Three other decisions follow from the same rule. Money is never held as an ordinary decimal number, because those cannot represent amounts like 0.10 exactly and the error compounds silently over thousands of operations; it is held in an exact form and written to disk as text, so nothing is lost in translation. Positions are not stored at all — they are derived from an append-only log of trades each time they are needed, so the holdings and the trade history cannot come to disagree, because there is only one of them. And a trade in a foreign currency stamps the exchange rate of its own trade date into the record, rather than being re-translated later at whatever rate happens to be in force when someone looks.

Marking the book, and the refusal Recomputing what the fund is worth means pricing every holding against the market. If any one of them has no quote, the write is refused and the last value the book could stand behind is left in place. the only moment a new fund value is recordedmark to marketoperator asks for it each position is priced against live market dataquote every holdinglive market data the gate: a partial answer is not an answerevery one priced? yes no the invariant the book is kept tofund value writtencash + Σ (position market value) a missing quote produces a refusal, never a filled-in numberwrite refusedno value recorded, none guessed positions are derived from an append-only trade ledger, not stored each trade stamps the FX rate of its own trade date, so it cannot drift
What you're looking at: what happens when the book is marked to market. Every holding is priced against the market, and the new fund value is written only if all of them came back with a price. Had the design gone the other way, the missing holding would have been filled in from a stale price and the box on the left would not exist. System design · no fund values, holdings or members are shown

Read it, and look at it

The source is public under the MIT licence: the fund-terminal repository. There is also a browsable demo, and it is important to be clear about what it is not. It is a static page built against a fund that does not exist. The members, the amounts, the NAV history and the holdings are all generated; the companies in it are invented; there is no backend behind the page and everything that would write is disabled. Nothing in it describes a real book, and none of the figures mean anything.

It does demonstrate the refusal, though, and not by contrivance. Because the instruments in the seed fund are invented, no market quote exists for any of them — so the positions report themselves as offline and marking to market declines, which is exactly what a real ticker would produce while the price feed was down. Adding a real security to the book is what turns live pricing on.

No real financial figures appear anywhere: not on this site, and not in the repository, which ships a synthetic seed and excludes real book files by design.

Unitisation, and the ledger invariant Money paid in is converted to units at today's price per unit. The fund's value always equals its cash plus the market value of its positions. a member depositsmoney in€ deposit net asset value per unit, computed at the moment of the depositprice per unitvalue ÷ units the member is issued units at that priceunits issueddeposit ÷ price fund value = cash + Σ (position market value) checked on every write — the ledger cannot drift money is held as exact decimals and written as text, so nothing rounds withdrawals redeem units at the price of the day, by the same arithmetic
What you're looking at: what happens when someone puts money in. The deposit is divided by the current price per unit to decide how many units they receive, and the invariant along the bottom is checked on every write, so the ledger cannot drift. System design · no fund values, holdings or members are shown
The technical version

A Flask API over two plain files — a JSON book and a CSV transaction log — with a React front end on top, covering fund overview, positions, trades, members, transactions, analytics, reports and documents. Four generations of a command-line tool preceded it; what is published is the web terminal.

Correctness is the story

Money is handled as Decimal end to end and serialised to JSON as strings, so no float drift can enter the ledger. Trades are the source of truth and positions are derived from them by average cost, with the euro price stamped at the FX rate of the trade date. The core invariant, that fund value equals cash plus the market value of positions, is enforced rather than assumed — and the mark-to-market path refuses to write a value at all when a holding has no quote to mark against, instead of recording an interpolated one.

What it is not

The README states the limits and this page will not soften them. There is no authentication and no authorisation: the API is open on localhost and anyone who reaches it can read and write the book, so it is never exposed to a network. State is a JSON file rewritten in place, with no database and no transactions, so concurrent writers lose data. It is not a regulated system — no defensible audit trail, no double-entry bookkeeping, no custodian reconciliation, no tax reporting. There is no test suite, and the accounting logic is the part that would most deserve one. Prices come from an unofficial scraper of a public endpoint, with no fallback vendor. The base currency is euros, hard-coded.

Flask, pandas and yfinance on the backend, all money as Decimal persisted as strings; React, Vite and Recharts on the front end. MIT licensed.