The Core Model
The schema has 124 tables. About twenty-five of them are the engine. This is those twenty-five, grouped by the question each one answers, and then one morning walked through them in the order they are touched.
Why a model and not a spec
Fifty-eight of the tables are vocabulary. A dozen are control plane or side journals. What is left is the engine, and those twenty-five tables are hard to see for the rest. The specs describe them one subsystem at a time; a model needs them together. If you can hold the seven roles and the one morning in your head, you can read any spec on this site and know where its tables sit.
Every table named here exists in the schema that was loaded into MySQL 8 on 2026-09-02. Every step in the morning walk is specified in detail elsewhere; this page tells it as a story.

The seven roles
Hold the questions, not the table names. The names follow.
| # | Role | The question | Tables |
|---|---|---|---|
| 1 | Configuration | Who trades, running what, with how much? | TradingAccounts, StrategyModules, StrategyConfigs, TradingAccountStrategyModuleMappings, Instruments, Markets |
| 2 | Registries | What is capable of producing an opinion? | SignalSources, Resolvers, ProbabilitySignalDefs |
| 3 | Inputs | What does the world say? | DailyBars, MacroSeriesObservations, NewsItems, CorporateActions, plus blob |
| 4 | Interpretation | What does it mean? | ResolverStances, ProbabilityEmissions, MarketRatingDays, frozen into the MarketState blob |
| 5 | Cause | Why did anything happen? | Stimuli → PipelineRuns → Decisions |
| 6 | Action | What did we do? | TradeOrders → Positions + PositionExits → ClosedTrades |
| 7 | Grading | Were we right? | DecisionOutcomes, ResolverStanceOutcomes, AccountEquityDays, ProbabilityEmissions.Resolution |
| 8 | Promotion | What has earned trust? | BacktestRuns, Promotions, LearningHypotheses |
Nothing enters row 1 without passing through rows 7 and 8. Strategies, resolvers and probability signals all climb the same ladder, Draft → Shadow → Active → Retired, and the ladder is a vocabulary table, not a convention.
The tables, role by role
Configuration is read fresh every cycle. A TradingAccounts row is one account: mode, role, starting capital, and typed risk caps. The caps are columns because the gauntlet compares against them on every entry. A strategy is code plus config: StrategyConfigs holds a versioned, hashed document that is immutable per hash, so a backtest can name exactly which config it ran.
Registries name things; they hold no values. The rule the design standards learned the hard way: a registry is not storage. The SignalSources row for the dollar index does not contain the dollar index; MacroSeriesObservations does, with the reference period, the release time and the vintage number, which is what stops a backtest from trading on a final-revision GDP nobody had at the time.
Interpretation is frozen once. A ResolverStances row is one opinion on the −100 to +100 scale with a confidence, an effective N and a FlagBits bitmask. A ProbabilityEmissions row is one estimate; later, its Resolution records whether the event happened. All of it is serialized to one blob per cycle, and everything downstream reads that blob, not the tables.
Cause is the reason chain. A Stimuli row is one thing that crossed a line, with the values that crossed and a pointer to the world it saw. A PipelineRuns row is one pass through a pipeline and, when refused, which stage killed it. A Decisions row is a verdict that survived, carrying the typed expectation it will be graded against.
Action is the book. TradeOrders records expected price against fill price. Positions keeps the stimulus that opened it. PositionExits are rows rather than a JSON list because every hourly window asks which armed stops are below the current price, and that is a query. ClosedTrades keeps the exit that ended it and the return versus SPY.
Grading keeps three scorecards, direction, calibration and outcome, and they are never merged. A resolver is not graded on P&L; a probability is not graded on direction. Promotion closes the loop: BacktestRuns carries a manifest hash so two runs with the same manifest reproduce bit-identically, and the origin window a hypothesis was born in, so the backtester can refuse to count it as evidence. Promotions now has foreign keys to what it promoted and the run that justified it. Until 2026-09-02 it had none, because its evidence lived in a JSON column, which is not an audit trail.

One morning, table by table
06:45, the daemon wakes. The first thing it does is write to DaemonRunProgressSteps, one row per stage as each completes. That is the resume ledger: crash at stage 4, restart at stage 4. Then it reads configuration from the database, every cycle, never cached across cycles, so an admin edit lands next cycle without a redeploy.
Pull, one fetch per source. For every SignalSources row whose freshness contract says it is due, the pull phase fetches. Bars land in DailyBars and in blob. Macro observations land with their vintage. News lands in the blob archive first, then a row per scored item pointing back at it; the order is fixed, because a row promising a document that does not exist is corruption. A source that fails its contract is marked stale and does not halt the cycle; every downstream consumer gets an absence with a reason rather than a stale value pretending to be fresh.
Compute the world, once. Every active resolver writes its stances. Every estimand writes an emission. Then everything is serialized to one blob, hundreds of kilobytes, written once. This is the last database read most of the cycle will do. From here down the decide phase touches no database.
Something crosses a line. A momentum rank passes 0.80. The regime resolver reports a transition. A position has drifted 3.4 percent past its 2 percent band. Each becomes a Stimuli row. Nothing has been decided; a stimulus is a question, not an answer.
The gauntlet. Each stimulus enters the entry gauntlet as one PipelineRuns row and meets its stages in order: predicate, confirmation, veto, probability, caps, sizing, construction. Risk always outranks. Most stimuli die at one of these, and the row says which. The design treats that as the product: a system that refuses well is a system that can be trusted to enter. The gauntlet in detail.
Survivors become decisions, decisions become orders. A Decisions row, then a TradeOrders row with its collar and expected price. The broker lane is a single-consumer channel, so the broker never sees two things at once. On fill, Positions is updated and the strategy's exits are armed as PositionExits rows. In paper mode the broker is the same fill simulator the backtester uses.
The hourly windows react; they do not re-plan. Regime, probability and macro stance stay frozen at the morning's values; only prices, news and the stress gauge refresh. The hourly work is mostly the exit-evaluation pipeline. Risk exits can fire in any window regardless of what any strategy thinks. When an exit fires, a PositionExits row goes Armed to Triggered, an exit order carries the E-code, and the position closes into ClosedTrades.
Weeks later, grading. DecisionOutcomes compares expected to actual and to the counterfactual of doing nothing. ResolverStanceOutcomes asks whether the stance was right and by how much. From those, LearningHypotheses proposes, BacktestRuns tests, Promotions decides, and the promoted version is what tomorrow's 06:45 reads as configuration.
The reason chain, read backward
The test of the model: pick any closed trade and walk backward.
No gaps. "Why did it do that on June 16th" has an answer at every level, and the answer at the bottom is the complete frozen state, which is also exactly what the backtester replays. The live journal and the backtest journal share a schema for this reason.
The three ideas that hold it together
Every row points backward to its cause
FkStimuliKey appears on PipelineRuns, Decisions, TradeOrders and Positions. The chain is foreign keys, not log correlation.
The world is computed once and frozen
Strategies never fetch. They receive a MarketState and return target weights, synchronously and purely. That is why a backtest is evidence about live behavior rather than about a lookalike.
Opinions are graded on their own scorecards
A stance says which way; a probability says how likely; a decision says what to do. Three claims, three outcome tables, three ways of being wrong.
What is not core
| Group | Count | What they are |
|---|---|---|
Vocabularies (*Types) | 58 | Every closed list as data: the lifecycle ladder, E1 to E8, S1 to S8, the seven stance bands. Seeded once, retired with IsActive = 0, never deleted |
| Control plane | 3 | DaemonCommands, DaemonRunProgressSteps, SystemParameters |
| Side journals | ~12 | Claude calls and budgets, notifications and routing, ops events, integrations and probes, the two sender queues |
| Reference data | ~10 | Fundamentals, earnings events, insider transactions, universe memberships, exposure routing, style scores |
| Desk and news intelligence | ~6 | User signals, news-to-market mappings, hold-review digests |
| Child tables added 2026-09-02 | 6 | The rows that used to be JSON: exits, rebalance weights, style scores, secrets, probes, signal subjects |
Every one of these matters somewhere. None of them is on the path from a stimulus to a trade. How the schema was reviewed.
Where the model breaks today
Honest gaps, so the model is not mistaken for the system.
- The registries are nearly empty. The vocabularies are seeded; the things that produce opinions are not. A registry seed exists for the Phase 2 set, three strategy modules and 25 instruments, and every module is Draft until its class passes the contract test. No resolver or estimand has a row.
- The entity layer covers six tables of 124. The C# side of this model is Phase 3.
- Only the strategy configs have C# records. The other document shapes are written down and not yet typed. The eleven shapes.
- The daemon has not run a cycle. The database was built from the schema file on 2026-09-02 and the market calendar is seeded, but no engine phase has written a journal row. This page describes what the tables mean; Phase 4 is what makes them move.
- 124 tables in the schema
- 58 are vocabulary
- ~25 are on the path from stimulus to trade
- 7 questions, answered in order
- 7 gauntlet stages, each able to kill
- 1 frozen blob per cycle that everything reads
Strategies, resolvers and probability signals all take the same ladder. The rungs are rows in LifecycleStatusTypes.