Back to the work

Case notes · Web app

ElliotFund

A private investment-fund ledger: daily NAV, unit transactions, and fund accounting for money my family trusts me with.

Next.js · Neon Postgres · Vercel · Live and in daily use · Screenshots show sample data

Why it exists

ElliotFund started with my son's savings. I always knew how much he had, because I wrote it down in a spreadsheet. But the money itself had no account of its own: it sat inside my investments, spread across stocks, bonds, and money market funds. His savings and mine were one pot with two owners, and only a sheet remembered which part was his.

That is exactly the problem a mutual fund solves, so the fair model is the mutual-fund one: a contribution buys units at today's price, and the price, NAV per unit, moves only when the market moves. His money grows at the same rate as mine because it is the same portfolio; the units decide how much of it is his. In a spreadsheet, keeping that honest meant recomputing stakes by hand, and one wrong cell silently changed who owned what.

So I built the fund a real ledger and named it after him. The database is the only place fund math happens, every number on screen is derived from it, and a small circle of family has since joined the same pot.

The fund, at one price

NAV per unit is never stored. It is a live view: total asset value divided by total units, computed by the database on every read. There is no cached figure to go stale and no nightly job to forget to run.

The chart is built from snapshots written at the moment a valuation changes, inside the same transaction, so the history and the live number can never disagree.

ElliotFund fund overview with NAV per unit chart and asset allocation, sample data
Fund overview: AUM, NAV per unit, and how the fund is invested.

What a member sees

Each member sees their own stake: units held, what those units are worth at today's NAV, and how that stake moved over time. The value is just units times NAV, recomputed on every visit.

No member balance is ever stored as money. Money is always derived, so there is nothing to drift out of sync.

ElliotFund member dashboard with holding value and stake chart, sample data
A member's holdings: units, value, and stake over time.

An append-only ledger

Every operation is a typed, numbered row: buys, sells, transfers, cash deployments, revaluations. Each row records the NAV at the moment it happened, and rows are never edited or deleted. A correction is a new entry.

When someone asks why a number is what it is, the answer is always in this table.

ElliotFund transactions ledger with typed entries and references, sample data
The transaction ledger: the fund's permanent record.

Only the market moves the price

Revaluing an asset is the single operation that can change NAV. It updates the asset, appends the ledger row, and records a NAV snapshot in one transaction.

Deposits and withdrawals cannot move the price, and that is enforced rather than hoped for: a verification script buys units inside a database transaction, asserts that NAV did not budge, then rolls the whole thing back.

ElliotFund assets screen with holdings and valuations, sample data
Assets: adjust a valuation and the NAV recalculates from the total.

Ownership that cannot lie

The members screen is the fund's cap table: units held and the share of the fund they represent. Transfers move units between members without touching assets, AUM, or NAV.

The shares always sum to one hundred percent because every figure is computed from the same units column.

ElliotFund members screen with ownership split, sample data
Members: the ownership split, derived from units.

The fund updates itself

Valuations do not wait for me to type them in. Every trading day, after the market closes, a Vercel cron job calls an API route that reads the current value of each position from the sheet where I manage my portfolio and applies the changes to the fund.

The decision that matters: the job has no special powers. It goes through the exact same revalue function an admin uses, so an automated update produces the same ledger row and the same NAV snapshot as a manual one, attributed and auditable. A swing guard skips any value that moves implausibly far in one day, because a bad cell in a sheet should never become a bad NAV.

The hard parts

A deposit is three writes or none

One contribution mints units for the member at the current NAV, adds the cash to the fund's pool, and appends the ledger row, all inside a single database transaction. Either the books fully update or nothing happens. There is no state where the money arrived but the units did not.

The return figure that would have lied

The headline number, growth since inception, must mean investment performance. In a family fund, money moves in and out all the time, so assets under management jump with every contribution. Computed from AUM, every deposit would look like a great month. So return is defined strictly as growth in NAV per unit, Rp 10,000 at inception versus today. Contributions mint units at the current price and leave it unchanged, which means only actual market movement can move the number.

One implementation of the arithmetic

Units carry four decimals and money two, fixed in the schema. All fund math lives in database functions, not application code, so there is exactly one place the arithmetic can be right or wrong, and every client, present or future, inherits the same rules.

Notes on the build

Next.js on Vercel, Neon Postgres underneath. Sign-in is Google against a member allowlist; there is no public signup, because a family fund does not need one. The fund functions ship with a verification script that proves the NAV invariants hold before any schema change lands.

If you want to talk about this build, or something you want built, say hello on WhatsApp or write to philip.kamdani@gmail.com.

Back to the work