MORPHIQLABS
Anvil4 min read

Determinism as a Correctness Contract

Why Anvil treats bit-for-bit reproducibility as a testable property — and how an event-sourced core makes a backtest and a production run literally the same program.

By MorphIQ Labs Research Notes

Most trading systems are tested the way most software is tested: feed an input, assert an output, hope the parts you did not assert on behaved. That works until the part you did not assert on is wall-clock time, or iteration order over a hash map, or a thread that won the race yesterday and lost it today. In a trading system those non-determinisms are not cosmetic. They are the difference between a backtest you can trust and one you cannot.

Anvil takes a stricter line. Determinism is not an emergent nicety — it is a contract, declared at the type level and checked on every commit.

The contract

Anvil's core is a state machine. Given a strategy state sts_t and an input event ete_t, the transition function produces the next state and a list of emitted commands:

(st+1,  ct)  =  f(st,  et)(s_{t+1},\; c_t) \;=\; f(s_t,\; e_t)

The contract is that ff is a pure function. No clock reads, no RNG without a seeded source, no filesystem, no allocation-order-dependent iteration. Given the same (st,et)(s_t, e_t), it must produce the same (st+1,ct)(s_{t+1}, c_t) — on any machine, in any year, under any compiler optimization level.

That single constraint buys an enormous amount. If ff is pure, then the entire run is a fold over the event log:

sn  =  (fen1fen2fe0)(s0)s_n \;=\; \bigl(f_{e_{n-1}} \circ f_{e_{n-2}} \circ \cdots \circ f_{e_0}\bigr)(s_0)

and the final state sns_n is a deterministic function of the seed state and the event sequence alone. Replay is not a feature bolted on afterwards. It is the definition of the system.

Why event sourcing falls out for free

Because state is reconstructed by folding events, the event log is the database. There is no separate mutable store to drift out of sync. This has a consequence that matters more than it first appears:

The class of bug where a strategy behaves differently in simulation than in production simply cannot be expressed. The two are the same object code reading two sources of the same event type.

Making it testable

A contract that is not checked is a comment. Anvil enforces determinism three ways.

Replay equality

Every recorded session can be replayed. The check is exact:

replay(log)  =!  snrecorded\text{replay}(\text{log}) \;\overset{!}{=}\; s_n^{\text{recorded}}

State is content-addressed — we hash the serialized state sns_n — so the assertion is a single 32-byte comparison, not a structural diff. A mismatch fails the build.

Cross-run divergence

The same log is folded twice in the same process and the intermediate states are compared step by step. The first index jj where

sj(1)sj(2)s_j^{(1)} \neq s_j^{(2)}

is reported directly. Because the two runs share a process, a divergence here points squarely at hidden global state or iteration-order dependence, not at a serialization round-trip.

Property-based fuzzing

A generator produces random-but-valid event sequences. For each, Anvil asserts that folding the sequence, serializing, deserializing, and folding again yields identical state. This catches the subtle cases — a HashMap iteration leaking into a command's ordering, a f64 sum whose result depends on accumulation order.

CheckCatchesCost
Replay equalityDrift between record and replay pathsO(events)
Cross-runGlobal state, iteration-order dependenceO(events)
Property fuzzingSerialization + accumulation-order bugsO(cases)

The cost, honestly

Determinism is not free. You give up the easy reach for SystemTime::now() — time must arrive as an event. You give up unordered collections in any code path that influences output. Floating-point reductions must fix an accumulation order, which occasionally costs a few percent of throughput versus a vectorized free-for-all.

We think the trade is obvious. A few percent of throughput is cheap. A backtest result you cannot reproduce six months later — after the bug it would have caught has already traded — is not.

Where this goes next

The current contract is enforced per-strategy. The next step is enforcing it across strategies sharing an event bus — proving that the interleaving of two strategies' command streams is itself a deterministic function of the merged log. That turns multi-strategy portfolios into the same replayable object the single strategy already is. More on that when the proof obligations are nailed down.

More from Anvil

July 6, 2026 · 2 min

Deterministic Replay for Trading Systems

The most expensive production bugs in trading systems are often not the ones that crash. They are the ones that cannot be reproduced. A signal changed, an order decision differed, a timeout fired in a different order,…