research: intraday error bars and charts - #27
Conversation
The real side had one observation per stock and no way to say whether a number was stable or a fluke. LOBSTER's free sample is one trading day, so the paper's 434-day average is not reachable; intraday blocks are the substitute that is. --blocks 13 cuts the session into 30 minute blocks and re-measures everything inside each, in one pass. scan() now accumulates per block rather than needing one file read per block. The two regimes do not overlap in any of the 65 block measurements. Small tick tops out at 9.49, tick-constrained bottoms out at 25.32. The ordering is positive in 13 of 13 blocks, and the persistent rho of 0.9 rather than 1.0 is the same GOOG/AAPL swap seen over the full day, so that swap is a feature of the sample and not noise. Blocks measure sampling variability, not day to day variability. Overnight gaps, news and regime shifts are invisible inside one session, so these bars are a lower bound on the true ones, and blocks from one day are correlated so 13 of 13 is a consistency check rather than thirteen replications. Both stated where the numbers are. MSFT's block mean sits well below its full day ratio because per block delta is more censored: an order outliving its block is dropped rather than credited to a clock it never ran on. The full session row stays the headline estimate and the doc says why. plot_farmer2005.py emits hand built SVG in a self contained page, no javascript and no external assets, matching the dependency rule and the book_replay.html precedent. Three panels: the ratio against dp/p_c with error bars, the simulated ratio overlaid, and the width scan as the negative control. The dp/p_c > 1 region is shaded so the scope condition is on the chart instead of in a caption, and the caption says a correct law gives a horizontal line at any height, since the prediction only holds up to a constant. Refactored measure_all and measure_blocks onto one row builder so the two cannot drift. Full session output is unchanged.
Found auditing my own strongest result before it went in a writeup. The paper measures diffusion on a different clock from the one its parameters live on. A3 defines event time as order placements and cancellations, and mu, alpha and delta are all per event on that clock, so D_hat is per event. A4 then says an event is anything that changes the midpoint, and measures V(tau) over that sequence, so D_real is per midpoint change. The ratio carries a hidden factor of events per midpoint change. Harmless if that factor is roughly constant across the sample, which is presumably true of the paper's 11 LSE stocks. It runs 6 to 186 here. A spread pinned at one tick is a spread whose midpoint rarely moves, so the mismatch inflates exactly the stocks the tick already inflates and a raw comparison double counts. Two claims in this repo were wrong as a result: Diffusion did not fail by four orders of magnitude. Matched, the spread of ratios is 66x rather than 1777x, so closer to two. The negative slope was substantially an artifact. Matched, A goes from -0.612 to +0.171. The narrative still holds, there was a missing control and it was dp/p_c, but the statistic was inflated. The rank result does not move. rho = 1.000 at p = 0.0167 on both clocks. That is the reason for building the argument on a rank test: it survived a factor of 27 error in its own input. Both clocks are now reported side by side rather than one being silently replaced, since the raw column is the literal A4 quantity.
Added: a correction to my own strongest resultAuditing the diffusion finding before it went in a writeup turned up a units problem. The paper measures diffusion on a different clock from the one its parameters live on. §A3 defines event time as order placements and cancellations, and μ, α, δ are all per event — so The ratio carries a hidden factor of events-per-midpoint-change. Harmless if that factor is roughly constant across the sample, which is presumably true of the paper's 11 LSE stocks. It runs 6 to 186 here:
A spread pinned at one tick is a spread whose midpoint rarely moves — so the mismatch inflates exactly the stocks the tick already inflates, and a raw comparison double counts the same effect. Two claims in this repo were wrong
The rank result does not moveρ = 1.000, p = 0.0167 on both clocks. That is the whole argument for having built on a rank test rather than the regression: it survived a factor-of-27 error in its own input. The regression did not. Both clocks are now reported side by side rather than one silently replacing the other, since the raw column is the literal §A4 quantity and someone comparing against the paper needs to see it. |
Two things: error bars on the real side, and charts.
LOBSTER's free sample is one trading day
The paper averages parameters over 434 days per stock, which is what gives its real side an error bar. LOBSTER's free samples are a single day (2012-06-21) per ticker, so that design is not reachable without a paid or institutional subscription.
Intraday blocks are the substitute that is reachable.
--blocks 13cuts the session into 30-minute blocks and re-measures everything inside each —scan()now accumulates per block in one pass rather than needing a file read per block.The two regimes never overlap
Small-tick tops out at 9.49; tick-constrained bottoms out at 25.32. No overlap across any of the 65 block measurements — a stronger statement than the full-day ratios alone, because it survives being re-measured 13 times.
The ordering is positive in 13/13 blocks. The persistent ρ = 0.9 rather than 1.0 is the same GOOG/AAPL swap seen over the full day, which makes that swap a real feature of the sample rather than noise.
What blocks do not buy, stated where the numbers are
Charts
analysis/plot_farmer2005.py→docs/farmer2005.html. Hand-built SVG in a self-contained page: no JavaScript, no external assets, matching the dependency rule and thebook_replay.htmlprecedent. Theme-aware, and the wide charts scroll inside their own container.Three panels:
dp/p_c > 1region is shaded so the scope condition is on the chart rather than in a caption, and the caption states that a correct law gives a horizontal line at any height — the prediction only holds up to a constant, so flatness is the claim, not proximity to 1.Verification
The refactor merged
measure_allandmeasure_blocksonto one row builder so the two cannot drift. I diffed full-session output before and after: byte-identical, so the refactor is behaviour-preserving and the block work is purely additive.Also verified the generated page has zero external resource references and every plotted point lands inside its viewBox.