A backtest that cannot lie to the strategy

Algo OMS

python · production path · simulated replay

What
A Python algorithmic trading stack — AlgoServer → AlgoEngine → SecurityManager → AlgorithmManager → OrderManager → Broker — where the backtester runs historical bars through that same production graph. This page replays one full session of it, event by event.
Why it was hard
Most backtests are a second implementation of the trading system, so every result carries the question "would the real path have done that?" — and lookahead bias creeps in anywhere a fill can see a price the strategy already saw.
What I decided
One event path, three injections. The backtest injects a simulated clock, no socket, and no db into the production graph; everything else is the real code. Strategy code cannot tell backtest from live, and fills are next-bar-open, enforced twice.
What happened
The engine is deterministic — same config and data give identical equity curves and order ids on any machine. The replay above the fold ran one SPY session through it — 390 bars, 14 orders, 13 fills, every fill at the next bar's open — including one order that can never fill, because it was created on the session's last bar.

Watch one session

Below is one full trading session — SPY, 27 September 2023, 390 one-minute regular-hours bars — played back through the engine. Simulated execution against a paper account; the bars are historical, and the code path that processed them is the production one. What to watch for: a moving-average cross fires a signal, the OrderManager stamps an order at that bar’s timestamp, the broker acks it to WORKING — and the fill only lands on the next bar, at that bar’s open, a price that did not exist when the order was created. Pause on any fill (click a triangle, or step with the event buttons) and the inspector shows the two bars magnified, so you can see the gap the strategy is never allowed to cross.

◐ SIMULATED REPLAY · loading the session timeline…

    every figure above is derived in your browser from the committed replay file — one session, simulated execution, paper account

    The session ends on a detail worth scrubbing to: the strategy signals on the final bar of the day, and that order never fills. There is no next bar, so there is no next-bar open. A backtester that quietly filled it at the close would be flattering itself; this one leaves it WORKING, because that is what the real path would do.

    The one decision that matters

    The backtester is not a separate simulator. It is the production AlgoServer → AlgoEngine → OrderManager → Broker event path with exactly three things swapped at the seam: a SimulatedClock instead of the wall clock, no socket, no db. A test enforces the discipline — no production module may touch the wall clock at all; “now” is whatever the event stream says it is. The same strategy class runs live unchanged once a live broker and feed are attached.

    Fills that cannot peek

    An order created on bar N fills at bar N+1’s open — never at a price the strategy has already seen. This is enforced twice, in two different components: the engine applies fills before invoking algorithms on a new bar, and the simulated broker independently refuses any order stamped at or after the current market event’s timestamp. Either check alone closes the loophole; both existing means neither can be quietly lost in a refactor. Slippage is always adverse — buys pay up, sells receive less. Commissions are charged to the portfolio, never hidden inside the fill price, so cost sensitivity stays analyzable. And the session filter drops thin extended-hours prints by default, because a “fill” against an 858-share 19:45 bar is fiction.

    Determinism as a feature

    Same config + same data ⇒ identical equity curve and identical order ids, on any machine. An end-to-end test pins a golden final equity to the cent — if it moves, fill or signal behavior changed, and that has to be a conscious decision, not drift. The replay file on this page is itself a product of that property: regenerate it and every byte of the timeline comes back the same.

    What you are looking at, exactly

    The timeline above is a JSON export of a real run: an additive script attaches observers to the engine’s public seams (a bar listener, the order-manager callbacks), plays the session, and serialises every bar close, signal, order state transition, fill and equity mark in the order they happened. Nothing in the engine was modified to produce it — the same instinct as the backtest itself: observe the seam, don’t fork the path. The strategy shown is the reference SMA(10)/SMA(30) cross sizing 100 shares — it exists to prove the plumbing (bar → signal → order → fill → P&L), not to find alpha, which is why the honest result of this session is a fistful of small whipsaw losses and one good trend. The export is built to be regenerated on a schedule — a nightly job replaying the previous session into this page is the designed next step — but every number shown today comes from the one committed run, and is labelled with its date.