REFERENCE/THE NINE FACTORS

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.

All pages