Trading engine
2026 · open source · paper only
A research engine that runs itself on one Linux box. Real US daily bars into DuckDB, a nightly screen, 21 paper portfolios with rules frozen before they trade, a fill model that cannot see the future, and forward tests that can kill a strategy but never promote one. Two months in, the honest result is that nothing beats its control yet. Code on GitHub.
Figure 01 · The nightly loop
Signal at the close, fill at the next open, judge later
Bars in, screen, orders, next-open fill, ledger. The monitors on the right read the equity paths and apply a rule frozen before the first signal. Nothing on this canvas can promote a strategy.
- Bars
- Ranked names
- Next-open fill
- Verdict
The fill model is the project
Every backtest I had read that looked good was a look-ahead bug wearing a strategy. So the one rule the engine enforces in exactly one place is that an order signalled from the close of day t fills at the open of day t+1, and attempt_fill raises if you ask for anything else. It raises under python -O too, because a guard that disappears under optimisation is not a guard.
The fill price is the open moved against you by a half-spread estimated from the sixty-day median dollar volume, plus five basis points a side. An order over one percent of that median volume is rejected outright rather than partially filled, because a partial fill I invented is still invented. A missing bar leaves the order pending for three sessions and then rejects it. A bar is never fabricated. Dividends are credited on the ex-date from the same corporate-actions table the screen reads.
same-bar fill
$ python -c 'from sim.fills import attempt_fill; ...'ValueError: look-ahead violation: fill_date 2026-09-17 !> signal_date 2026-09-17 # sim/fills.py — the only guard, verbatimif fill_date <= signal_date: raise ValueError(f"look-ahead violation: fill_date {fill_date} !> signal_date {signal_date}")
Pre-registered or it did not happen
A strategy enters the league as a charter: the mechanism, the control it has to beat, one primary statistic, a kill criterion, and the total number of trials. All of that is written down before the first signal. The rule does not change after the outcome, and a losing test is recorded, not rescued.
Ten charters so far. Seven are closed as rejected or inconclusive: a VIX term-structure timer, turn-of-month, sell-in-May, a drawdown throttle, a vol target, a sector cap and a quarterly ETF rebalance. Each failed the gate it declared up front. The three calendar timers lost to a static exposure-matched control, which is the control most of the literature forgets. Three are still accruing: a sector-momentum book that needs two hundred shared sessions before its kill rule can fire, a 12-1 cross-sectional momentum book measured against an unscreened control, and a forty-Monday test of SPY's open-to-close drift.
a report that will not peek
$ cat data/reports/experiments/e1-spy-monday-forward.mdNO RESULT YET — 8 of 40 out-of-sample Mondays.Kill criterion: after 40 Mondays, KILL if mean <= 0 or t < 0.5, net of 20 bp.Current standing: mean -0.29%, t -1.37 — would KILL if applied today,which it is not. 32 Mondays to go.
Survivorship is the number everyone hides
The price store holds only names listed today. Measured against listed-company counts, that is about eleven percent of the companies that existed in 1996, a quarter of 2003 and forty percent of 2014. No 2008 casualty is in it, so a fold that spans 2008 is one in which those names cannot lose money. The bias is not a constant; it grows the further back a window reaches, and every fold table carries its universe size so a reader can weight it.
The walk-forward therefore never reports absolute return as evidence. A stock-picking book is compared with an equal-weight basket of the same screened names, fold by fold, so the bias sits on both sides of the difference. On that comparison, no screen-driven book beat equal weight on any window of three years or more, and the two books that led the live table in September had drawn down eighteen percent inside two months. That result is in the repo. The engine remains paper-only, and the next research gates are calendar-bound: the point-in-time tables are not deep enough for a fair stock-selection test until 2029 unless I buy a dataset with the delisted names in it.
Running unattended
Cron fires the nightly at 22:30 UTC: universe, collect, screen, corporate actions, league step, the three forward monitors, sync, then the heavier jobs through a queue with per-job timeouts and a resource cap. DuckDB has one writer; the collector releases it between batches so the API and the UI are never locked out for a multi-hour pull. Saturdays a verifier re-checks the full universe against a second source and reports disagreements instead of quietly patching them. Sundays the walk-forward replays every book through the live league.py day-step with the config frozen in its database row, so the report can never describe a rule the league is not trading.
Coding agents built the engine from a written spec, and what it taught me most recently was about agents. Once the research answer was “nothing works yet, wait for evidence”, the agents kept building anyway: forty-six thousand lines of governance for a broker that does not exist. The repo now carries an operating contract, a scope ledger with size ceilings the test suite enforces, and a drift snapshot every session must publish. The plan is the appliance: collect, judge, wait.
- paper books
- 21 active · 18 replayable from their frozen config
- strategy modules
- 30 · one file each, pre-registered
- charters with a kill rule
- 10 · 7 closed as rejected or inconclusive
- liquid universe
- 4,097 US names, refreshed weekly
- fill model
- next open · spread tier + 5 bp · ≤ 1 % of 60-day volume
- walk-forward
- 10 folds · train 24 mo · validate 12 mo
- store
- DuckDB · 50 tables · one writer
- api
- 30 loopback routes, read models only
- tests
- 3,156 collected · warnings are failures
- python
- ~81k lines · 201 commits since 2026-07-16
- status
- paper only · MIT · github.com/ong6/trading-engine
What it is not
It holds no credentials, connects to no broker and moves no money. The two services bind to loopback and the repo ships no market data. I built it to find out whether I could make a research loop whose numbers I would trust, and to be able to say, with the evidence in the open, that nothing has passed yet.