ProjectsDaneel
Back to projects
Daneel

Daneel

Scheduled paper trading with kill rules the code enforces, reviewed by AI after the close.

Next.jsTypeScriptFirebasePythonAnthropicCloud FunctionsFirestoreIBKR APIVercel

Context

Daneel is a scheduled paper-trading system for US equities that runs across three runtimes, inside a written operating envelope, with kill rules the code enforces rather than the operator remembers. Strategies tick every minute against live quotes, place orders into an IBKR paper account through a queue that settles exactly once, and get reviewed in writing after the close.

The trading question is the one that made it worth building: which of these strategies would actually have made money this month? Every idea sounds good in a spreadsheet, and the only way to know is real prices, real order timing and no money on the line. Most of the system, though, exists to make sure the answer is not a lie.

A trading day, on a schedule

07:00 ET
researchRun
Claude, notes only
09:00 ET
preMarketScan
the day's picks
Market hours
strategyTick every minute
quotes, evaluate, order
brokerSync every minute
flush intents, reconcile fills
16:00 to 19:00 ET
afterHoursTick every 2 minutes
AI types, when enabled
16:20 ET
dailyBarRefresh
adjusted closes, before anything reads them
16:40 ET
dailyPostMortem
insights, evolution every third day

Every one of those is a Cloud Functions schedule pinned to America/New_York, and the ordering at the end of the day is deliberate. Yahoo publishes a dividend adjustment with the ex-date, so a post-mortem that ran before the daily bar refresh compared today's price against a prior close that still had the dividend in it, and every holder printed a phantom loss on every ex-date. Twenty minutes of gap is the fix. The minute tick is guarded by a market-session check, not by cron alone, and a Finnhub webhook can fire the same tick out of band behind a shared-secret header.

Three runtimes, one source of truth

Vercel
Next.js dashboard and API
Firestore
Cloud Functions
scheduled ticks and agents
Firestore
strategies, portfolios, order intents
IBKR paper
headless OAuth 1.0a
Feeds
Massive or Polygon quotes
Finnhub fallback, plus a webhook tick
Yahoo adjusted daily bars
signal engine
Flask and pandas, its own container

State is Firestore, scoped per strategy: a portfolio document, tick history per symbol, observations. Each strategy carries its own positions, P&L and ledger, so a good idea and a bad one never share a balance. IBKR is reached with headless OAuth 1.0a straight from Functions; a gateway process on a Mac or a VPS was rejected because it needs a weekly 2FA re-login and breaks the Functions-only executor.

The operating envelope

$250
standard ledger, every strategy at the same stake
$8,750
across the sixteen daily-rule ledgers
10%
drawdown from initial cash pauses a strategy
3%
daily loss across the book halts new entries
8
ledgers may hold one symbol in paper, 3 before live
1%
broker fill off the sim price triggers a review

Those are not guidelines. They are constants in the functions project and the rules that read them, written down in a risk policy that says who may change each line. The account is IBKR paper only: no live credential set has been created, the live flag stays unset, and a live account submits nothing without it. Every ledger gets the same $250 so results are comparable, and cash never moves between ledgers without an order behind it.

The same policy fixes where AI may sit: Claude writes research notes and nothing else, never in the decision path of an order, and the AI-driven strategy types stay paused. Reporting is fixed too, as a push on every broker fill and every kill rule trip plus a post-close daily summary of P&L against benchmark. The summary is the record and the board is a view of it, not the other way round. The judgment horizon is six weeks from the first paper fill, which puts the date at 2026-10-20.

Where the ledger cannot drift

Every mutation goes through one mutateState() path, serialized so two writers cannot interleave. The interesting problem is the other one: a strategy tick reads a portfolio, spends seconds in the engine, then writes the whole document back, and a broker settlement can commit inside that window. An unconditional write would erase settled cash while the order intent was already terminal.

So settlement is one Firestore transaction that reads the portfolio and the intent together, no-ops unless the intent is still in the status the caller expected, and writes both documents atomically. That makes a retry free: a second pass finds the intent already moved and writes nothing. The tick's own save is a compare-and-set on updatedAt, skipping that strategy's minute rather than clobbering the settlement. Broker sync runs at one instance as the outer belt, and every pending-to-submitted write is itself a compare-and-set. An ambiguous broker status never reverses the ledger on its own, because IBKR reports the same code for a rejected order and for one merely queued outside regular hours.

Reviewed by agents after the close

The pre-market scan saves the day's candidates. The post-mortem reads the session, writes insights and proposes changes to the strategies that misbehaved, and every third day a separate pass proposes parameter evolution. The research run is earlier and separate, producing a display-only feed. The loop is deliberately slow: one review per trading day, in writing, against numbers that cannot be argued with.

What got deleted

Polymarket and Bitcoin ran alongside the equities strategies through the summer. The July 2026 audit showed all fifteen or so binary-market approaches lost money, so the whole side was removed: the app routes, the functions, the live-trader scripts and the dependencies. Keeping it dormant was rejected as dead weight rather than an option. Every equities strategy was kept, including ones that had never fired.

Alpaca went the same way. The broker-neutral execution layer had been built against it, then IBKR was opened, and Alpaca was dropped rather than parked behind a flag: the layer was renamed once and IBKR lives behind the same interface. The Portfolio Agent was the third deletion and the sharpest. It displayed a hypothetical equity compounded from ledgers that had since been reset, which is AI in the display path producing numbers nobody could stand behind. The rule that replaced it: every displayed figure traces to a measured source or renders as a placeholder.

What it found

The board once showed one scalper up 3,860% while its own daily summaries added to minus $258 over 46 trades. Losses were being dropped three ways. Cash was rebalanced between scalper ledgers by streak score with no order behind it. Positions were valued at their last fill price, so an open loss stayed invisible until a sell. And a bare sell with no position credited the notional as cash, which is how a short breakout paid for itself.

All three are fixed: the rebalance is gone, every position is marked every tick in both engines, and a close larger than the holding is rejected. The post-mortem used to derive opening equity as closing equity minus realized P&L, so a summary could never contradict the board; opening equity is now stamped on the first tick of the ET day and measured, realized and board drift are reported as three separate numbers. The simulator charges an IBKR-shaped commission on both legs, refunded exactly on a reversal.

Under the hood

Next.js and React on Vercel, TypeScript throughout, Tailwind CSS v4 and shadcn/ui, Firebase Auth, Firestore and Cloud Functions v2, Recharts, and a Python signal engine on Flask and pandas. Market data is Massive or Polygon with Finnhub as fallback, Yahoo for adjusted daily bars; the agents use the Anthropic and OpenAI SDKs.

The landing is public; the dashboard is behind sign-in.