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