The nine factors
Every tick's change in mispricing breaks into nine components that sum to it. These are the names the truth table uses, and the closed set an agent's explain must answer from.
The set, in order
The first three are the model's own dynamics, the next four are shocks, the last two are discrete events booked after the tick chain.
| reversion | The pull back toward fair value. Negative above fair value, positive below, decaying at a rate set by its half-life. |
| momentum | Herding: the fraction of yesterday's re-rating that continues today. Controlled by momentum_theta, and the reason returns trend here. |
| crowd_lean | The crowd's net flow: net-buys what trades below fair value, net-sells what trades above, and chases yesterday's move. Bounded. |
| company_news | News priced in: a name's own events at full weight, its sector's at a fraction, market-wide at a smaller fraction. |
| order_flow_impact | The permanent, information-bearing part of order imbalance. Your own orders land here. |
| short_squeeze_effect | Forced flow: squeezes on high short interest into a rising price, and stop cascades in either direction. |
| random_noise | The idiosyncratic draw, scaled by the name's beta, its sector's volatility, its size, and the GARCH variance state. |
| circuit_breaker | The session breaker's own correction, when the model price leaves the allowed band. |
| jump | The daily jump, applied after the tick loop, recorded on the tick where it is first observed. |
They sum, and that is the point
Difference mispricing_s across two ticks, add the nine columns, and the residual sits near 1e-16. That is what makes this a dataset rather than a commentary: a label can be checked against the outcome instead of trusted.
FACTORS = ["reversion", "momentum", "crowd_lean", "company_news",
"order_flow_impact", "short_squeeze_effect", "random_noise",
"circuit_breaker", "jump"] # Engine.FACTORS
one = truth.filter(pl.col("instrument_id") == 0).sort("day", "tick")
delta = one["mispricing_s"].diff()
summed = one.select(sum(pl.col(c) for c in FACTORS)).to_series()
print((delta[1:] - summed[1:]).abs().max()) # ~1e-16
Before 2026-08-26 this did not always hold
On any day a jump landed, from pt-v4 onward, or any day the session circuit breaker bound, the seven-column identity fell short: 0.128 off on one measured seed over 120 days. Both mechanisms are columns now, and the identity holds through a crisis. The columns were never wrong, only incomplete.
Scope
It decomposes the mispricing, the gap between price and fair value, not the price itself. Everything that moves fair value, such as a rate change repricing the discount, lives in fundamental_value instead. And it is per tick, deliberately: a day-level attribution can say order flow moved a price today, never when, and a label that cannot align to a bar is not a label.