Adam Burich Open to work

← Portfolio

2026 · Solo · Python library + local web console

paper-broker

August 2026 · ~1,300 lines of Python · standard library only · Python 3.11+

A paper brokerage I own: accounts, cash, market orders, FIFO cost basis, realized P&L, statements, and a local web console — over an append-only event ledger designed to be committed to git. I built it as the execution layer for my two market experiments, because a hosted paper-trading account couldn't give me the one thing they both need: a journal nobody, including me, can quietly rewrite.

Why build a brokerage

Silly Prices Holdings and the second-order news experiment are both attempts to find out whether a decision method is any good. That question lives or dies on the record: what was bought, when, at what price, and with what reasoning at the time. A broker's paper account holds balances, not reasoning, and its history lives on someone else's server. The news experiment started on one and moved here in August.

The design goal was that the ledger be the evidence. If it lives in the consuming project's git repository, every entry is timestamped by a commit that can't be moved after the fact — the journal is lookahead-proof by construction, and every trade can carry the context it was made with.

01The ledger is the source of truth

One JSON line per event. There's no database and no stored balance: cash, positions, lots, and P&L are all derived by replaying the file from the top every time they're needed.

{"ts":"2026-08-12T04:04:21+00:00","account":"AUTO","type":"buy",
 "ticker":"FDS","shares":0.353419,"price":282.95,"tranche":1,
 "note":"ladder lot 1/3 (paper-routine)",
 "context":{"rating":"BUY","basis":"P/B","regime":{…},"ladder":{…}}}

Seven event types: deposit, buy, sell, dividend, mark, split, and rename. Replay is strict — an unknown event type, a sell of shares that aren't there, or a buy the cash can't cover raises rather than being skipped, so a corrupted ledger fails loudly instead of producing a plausible wrong statement. The context field is free-form: Silly Prices writes the rating, valuation lens, and market regime that justified each lot, so the reasoning travels with the trade.

02Deliberately small semantics

03Valuation is injected

The engine never decides where prices come from. It takes a price_source — any function from a symbol to a price — and symbols are opaque strings. Stocks priced from a delayed quote are the common case, but a prediction-market claim priced by probability works identically. Workspaces that set pricing to manual value positions from mark events entered by hand, and refuse to fill without one. Fills and statements can use different sources: fresh quotes to execute, cheaper cached quotes to report.

Pricing a whole book is concurrent — every held ticker is quoted in parallel, behind a 60-second cache — and a failed quote shows the position as unmarked rather than failing the statement.

04Separating selection from timing

The question both experiments ask is whether the picks are good, not whether the market went up. The library supports that two ways. Any buy can automatically place an equal-dollar benchmark order at the same instant, so the difference between the pair isolates selection from timing and market beta. And because accounts are cheap, an experiment can instead run an untouched index account alongside its traded one — which is what the news experiment settled on.

05One console across every experiment

Each project keeps its own ledger in its own repository; a small registry maps workspace names to ledger paths, a benchmark, and a pricing mode. The web console presents all of them as one system: a single account picker across every workspace, an overview of cards with each account's spread against its control, and for any account its positions, closed positions with realized P&L, full fill history with each trade's context expandable, cash events, and marks. Orders, deposits, marks, and sells can be placed from it too.

It's a local tool, and treated like one: it binds to 127.0.0.1 only, and a fresh random token is generated at every launch and required on every state-changing request, so another page in the browser can't place orders through it. Errors come back as structured responses and a toast; the server keeps running.

06Library, CLI, or both

from paperbroker import Broker

b = Broker(ledger_path="ledger.jsonl", price_source=my_price_fn, benchmark="VOO")
b.deposit("MAIN", 10_000)
b.buy("MAIN", "META", dollars=500)
b.sell("MAIN", "META", sell_all=True)
print(b.statement())
b.serve_gui()                      # http://127.0.0.1:8321

Silly Prices uses it as a git submodule behind a thin wrapper, with its weekly buying routine writing lots straight to the ledger; the news experiment drives it from the command line in its evening and morning routines. The consumers carry the tests — including a pre-commit guard that replays the ledger before anything is committed. That guard exists for a known edge case: a buy that uses nearly all of an account's cash can pass at order time and then fail on replay once the stored price is rounded.

Stack

Python 3.11+ (standard library only: http.server, urllib, concurrent.futures) · JSONL · git · vanilla-JS single-page console

Paper money only — a validation instrument, not a trading platform and not investment advice.