Every account as one book. Every call on the record.
openportfolio is an open-source, self-hosted portfolio tracker. It pulls every brokerage, pension, wallet
and bank account into a single net worth, stores the investor flows behind the price, and Brier-scores the
forecasts you registered before the fact.
Screenshots show the demo book. Every figure in them is invented.
Two problems that look unrelated
A portfolio is scattered by construction, and market commentary is unaccountable. Both are bookkeeping
failures, so openportfolio treats them as bookkeeping.
01
One net worth
A broker here, a pension there, an ISA, an exchange account, a holding no API will ever return. Every
app shows you a number and none of them shows you yours, so the figures that decide things get
estimated, generously, in the direction that avoids a decision.
02
Flows, not just prices
Price is the output of who was buying and who was made to sell. Net buying by investor type, turnover
and a calendar of dated forward events are stored as first-class series, not derived when somebody
remembers to ask. A forced seller is on a schedule.
03
A scored track record
Being wrong costs nothing and leaves no trace, so a forecaster worth reading and one who is merely
fluent are indistinguishable. Register the call before the fact with a probability and a resolution
criterion, and the machine settles it and scores it.
Track record
The reliability diagram is the product
A mean Brier score alone hides the direction you are wrong in. The diagram puts what you said next to
what happened, bucket by bucket, and the gap between the two bars is the entire point.
Calls with a parseable criterion resolve themselves when the horizon passes. Nothing waits on you
remembering that you made a prediction three weeks ago.
Probability, horizon and resolution criterion required at registration
Automatic settlement on horizon expiry, scored on resolve
Calibration error and per-bucket observed rate
Flows
Who was buying, and who was made to sell
Net buying by investor type and session turnover, per market or per symbol, kept next to the tape rather
than under it. Retail absorbing a foreign dump on collapsing volume is a fact about supply, and it is
visible before it is a story.
Per-session net buying by investor type
Turnover alongside every flow row, never without it
Dated catalyst calendar for forced-seller events
Decisions
A deferred decision is still a decision
"Wait for the print, then decide" evaporates the moment it is said out loud. The queue holds each one
with a trigger condition and an outcome, and marks the ones that quietly went past their date.
Trigger condition and due date on every entry
Overdue rows sorted first and flagged
Closed only with a written outcome
No provider API key. Anywhere.
Watching a book is only useful if something is actually watching. Metered inference is the wrong shape for
that.
When each run bills per token, every autonomous check becomes a purchase, and a product that spends your
money unprompted has to ask first, or batch, or ration. All three turn a portfolio that watches itself
into a portfolio that asks permission to look.
So every model call is dispatched to an agent CLI you are already signed in to, with a per-task fallback
order. There is no key in this repo and no field to put one in. Agent work is bounded by quota and wall
clock rather than by spend, so it never has to be justified one invocation at a time.
codex, antigravity and claude supported out of the box
Per-task provider order with automatic fallback
MCP server with 25 tools, so the agent reads and writes the book directly
// actor.mts — the provider chain, not an API clientconst ORDERS = {
sync: ["codex", "claude"],
review: ["claude", "codex"],
resolve: ["codex", "antigravity", "claude"],
}
// Each entry is a CLI you already signed in to.// There is no apiKey field to fill in, in any of them.
Where it sits
Between a spreadsheet that never updates and a hosted tracker that wants your broker password.
openportfolio compared with a spreadsheet and a hosted portfolio tracker
openportfolio
Spreadsheet
Hosted tracker
Data stays on your infrastructure
Yes
Yes
No
Updates without you
Yes
No
Yes
Holdings no API returns
Manual adapter
Yes
Rarely
Investor flow series
First class
No
No
Scored forecast record
Brier + reliability
No
No
Readable by an agent
MCP, 25 tools
No
No
Places orders
Never
No
Sometimes
Running in five commands
Node 22, pnpm, and a free Convex deployment. Nothing else is required to see a real number.
git clone https://github.com/seonglae/openportfolio.git
cd openportfolio
pnpm install
cp .env.example .env.local
npx convex dev --once
# create the first book
npx convex env set OPENPORTFOLIO_DEV_TENANT home
npx convex run tenants:create '{"slug":"home","name":"Home","baseCurrency":"GBP"}'# the UI, then the sync loop
pnpm --filter openportfolio-browser dev
npx tsx sync-worker.mts --once
With nothing linked it registers the venues it can serve and records a net worth of zero, which is
correct. Point OPENPORTFOLIO_MANUAL_HOLDINGS at a JSON file and the same command gives you
a real total, with crypto re-quoted through the keyless CoinGecko adapter and every row converted into
one base currency.
No. The backend has no function that places an order, the adapters that ship declare
canPlaceOrders: false, and PlaceOrderRequest requires an
OrderConfirmation naming who confirmed it and when. That field has no default on purpose. It
aggregates, watches and keeps score.
Where does my financial data live?
In your own Convex deployment. Broker credentials stay in the worker process on your machine; the backend
never receives them and this repository has nowhere to put them.
Which accounts can it read?
Two adapters ship: a keyless CoinGecko quote source, and a manual adapter that reads a JSON file you
maintain, which is how a pension or an unlisted holding gets into the total instead of being left out of
it. A keyed broker adapter is about forty lines against a documented contract.
Can several people share one deployment?
Yes. Every table carries a tenant id and every index leads with it. A caller never says which tenant it
is: the id comes from membership rows or from the service key's own row. A document belonging to another
tenant reads as missing rather than forbidden, because "forbidden" confirms the row exists.
What does it cost to run?
A Convex free tier, a machine you already own, and the agent CLI subscriptions you already pay for. There
is no inference bill because there is no inference key.
Start keeping score
Apache-2.0. Clone it, point it at one account, and register one call with a date on it.