Skip to content

R6 — simulate the paper's model to test whether the tick is the mechanism - #24

Merged
brianmmaina merged 1 commit into
mainfrom
research-r6-simulate
Aug 16, 2026
Merged

R6 — simulate the paper's model to test whether the tick is the mechanism#24
brianmmaina merged 1 commit into
mainfrom
research-r6-simulate

Conversation

@brianmmaina

Copy link
Copy Markdown
Owner

The problem with R5

The dp/p_c finding was a correlation on five points, and dp/p_c is large for exactly INTC and MSFT — also the two cheapest, highest-volume, most heavily quoted names in the sample. A dozen things that separate a $30 stock from a $500 stock would produce the same ordering. R5 could not distinguish "the tick causes the failure" from "dp/p_c is a proxy for being cheap."

Simulation holds everything else fixed

tools/zi_paper.cpp implements the paper's model — the austere one: single order size, uniform deposition, equal rates, constant cancellation — on this project's matching engine. Run at each stock's measured (α, μ, δ, σ) and its real tick size, nothing about a cheap stock is present except four flow parameters and dp.

Prediction stated before running: small dp/p_c → simulated ratio near 1; large dp/p_c → ratio near that stock's empirical ratio. The second half is risky — a coarse grid inflating the spread is easy, matching the magnitude of the real inflation is not.

dp/p_c sim ratio real ratio sim/real
GOOG 0.21 0.66 ± 0.01 4.36 0.15
AAPL 0.35 0.72 ± 0.01 3.70 0.19
AMZN 0.76 0.83 ± 0.01 5.66 0.15
INTC 17.30 32.23 39.75 0.81
MSFT 22.03 40.87 50.35 0.81

The tick alone reproduces 81% of the observed inflation — the same fraction for both constrained stocks, which nothing here was tuned to produce.

Simulated inflation is perfectly rank-ordered by dp/p_c (ρ = 1.000, exact p = 0.017). Inside a simulation that is far stronger than the same number on real data: dp/p_c and the four flow parameters are the only things that vary, so there is no confounder left to appeal to.

What this does not show, kept in the writeup

  • The remaining 19% is real. The tick is the dominant term, not the whole story. What's left is presumably what the model deletes — strategic quoting, hidden liquidity, heterogeneous sizes.
  • The small-tick ratio is 0.66–0.83, not 1.0. The simulation runs ~25% below the mean-field prediction, consistently. f(ε) is itself an approximation so a gap this size is unremarkable, but it is a gap and it is not rounded to "agreement."

The width scan caught a real bug

The paper's deposition intervals are semi-infinite; a simulation must truncate them. If the answer moves when the truncation moves, the boundary is setting the spread and the whole result is an artifact.

The first implementation truncated to a fixed price box centred at zero. The scan returned:

32.23, 32.23, 32.23, 0.00, 32.23

Non-monotonic in the width is a bug, not a boundary effect. The book could pin its best bid against the top of the box, at which point the sell interval [b+1, box_top] was empty and no sell order could ever arrive again — an absorbing one-sided state that silently reported a spread of zero. The same box also made buy and sell arrival rates depend on where the price sat inside it, quietly breaking the model's equal-rates assumption.

Anchoring each interval to the opposing best quote — buys on [a(t)−W, a(t)−1], sells on [b(t)+1, b(t)+W] — gives constant equal widths and lets prices wander. After the fix: GOOG 0.66/0.68/0.65/0.69/0.70 across a 16× width range, INTC 32.23 at every width.

Without the scan the fixed-box version would have produced a plausible-looking table.

Design notes

  • Separate tool, not a flag on zi_sim. zi_sim uses empirical sizes and an empirical placement histogram — a better imitation of a market and a worse test of this paper. Mixing them would let a difference in the model masquerade as a difference in the law.
  • Parameters come from farmer2005.measure_all, not re-measured, so a difference in measurement cannot masquerade as a difference in the model. Same reason compare.py imports stylized_facts.
  • By-product: an independent exercise of the matching engine — a CDA driven for millions of events against an analytically known answer, agreeing to within a constant wherever that answer applies.

191 tests pass; -Werror clean.

The dp/p_c result was a correlation on five points, and dp/p_c is large for
exactly the two cheapest stocks in the sample. Plenty of things that separate a
$30 stock from a $500 one would give the same ordering.

Simulation holds everything else fixed. zi_paper implements the paper's model,
the austere one, on our matching engine, and runs it at each stock's measured
alpha, mu, delta, sigma and its real tick. Nothing about a cheap stock is
present except four flow parameters and dp.

The tick alone reproduces 81% of the observed inflation, the same fraction for
both constrained stocks. Simulated inflation is perfectly rank-ordered by
dp/p_c, rho = 1.000, p = 0.017, and inside a simulation that is much stronger
than it was on real data because there is no confounder left.

The remaining 19% is real and stays in the writeup. So does the small-tick
ratio being 0.66 to 0.83 rather than 1: the simulation runs consistently below
the mean field prediction and that is not rounded to agreement.

Separate tool rather than a flag on zi_sim. zi_sim uses empirical sizes and an
empirical placement histogram, so it is a better imitation of a market and a
worse test of this paper. Mixing them would let a difference in the model look
like a difference in the law.

The width scan earned its place. Deposition intervals are semi-infinite and
have to be truncated somewhere; if the answer moves with the truncation the
boundary is setting the spread. The first version truncated to a fixed price
box and scanned 32.23, 32.23, 32.23, 0.00, 32.23. Non-monotonic in the width is
a bug, not a boundary effect: the book could pin its best bid against the top
of the box, leaving the sell interval empty so no sell could ever arrive again.
An absorbing one-sided state reporting a spread of zero. The box also made buy
and sell rates depend on where the price sat inside it, breaking the model's
equal-rates assumption. Anchoring each interval to the opposing quote fixes
both. Without the scan the fixed-box version would have produced a
plausible-looking table.

Parameters come from farmer2005.measure_all rather than being measured again,
so a difference in measurement cannot masquerade as a difference in the model.
@brianmmaina
brianmmaina merged commit 0f6cb6f into main Aug 16, 2026
11 checks passed
@brianmmaina brianmmaina mentioned this pull request Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant