openportfolio

Apache-2.0 · self-hosted · no provider API key

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.

Get started See the live demo

git clone · pnpm install · npx convex dev

The openportfolio net worth view: a single total in pounds, then breakdowns by venue and by asset class, then the full position table

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
The track record view showing a mean Brier score of 0.187 over 74 scored calls, with a reliability table comparing forecast probability against observed rate in five buckets

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
The flows view showing net buying by investor type over thirty sessions and a table of individual sessions with net and turnover columns

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
The decisions view listing open deferred decisions with trigger conditions and due dates, one row highlighted as overdue, and a table of upcoming catalysts

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 client
const 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.

Full quick start · Writing a venue adapter · Multi-tenancy

Questions

Is this a trading bot?

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.

Star on GitHub Read the docs