MSc thesis · UCL · in write-up, submission Sep 2026

Latent Dynamics of Prediction-Market Price Paths

The work

Supervised by Prof. Philip Treleaven, University College London

The raw material is the repricing event — a moment when a market's price actually moves. Not what the price is, but when it changes.

What I built

A hierarchical point-process model of market activity, and the inference machinery to fit it. The likelihood runs through a forward algorithm that had no automatic-differentiation gradient in this codebase, which put the usual gradient-based samplers out of reach — so I wrote a Metropolis-within-Gibbs sampler from scratch, with a conjugate Normal-Inverse-Wishart block for the population level and a Laplace-EM fit as an independent cross-check. Four chains, 3,500 sweeps each. Alongside it, self-exciting and moving-average baselines to compare against, and a market-clustered bootstrap for the uncertainty.

The two-state market A market switches between a quiet state, moving its price about once every 25 minutes, and a busy state, moving about once every 53 seconds. The state itself is hidden and has to be inferred from the price moves. lambda_cold 0.041 moves per minute; dwell about 37 minutesQUIET stateone move ≈ every 25 min lambda_hot 1.141 moves per minute; dwell about 8 minutesBUSY stateone move ≈ every 53 s switches switches back stays ~37 min stays ~8 min 28× faster in the busy state you never observe the state — only the moves it produces
What you're looking at: the structure the model is built to find. A market sits in a quiet state, moving its price about once every twenty-five minutes, then switches into a busy one moving roughly every fifty-three seconds. The dashed box is the whole difficulty: the state is never observed directly, only inferred from the moves it produces. Population estimates, n = 34 markets · data 2026-06-17 · two-state.csv

What I found

Markets go quiet as their deadline approaches, and busy spells that survive their first minutes tend to keep running.

I expected activity to intensify as a market ran out of time, the way a deadline usually concentrates attention. The opposite happens: price movement slows down. That result held as I went from 9 markets to 318, and got stronger when I measured time against the official settlement instead of the last observed trade.

Then I looked at how long busy spells last. A standard model said the longer a burst had run, the sooner it would end. That was wrong, and wrong for an instructive reason: I had only counted bursts above a minimum length, so the short ones were invisible to me. It is the same mistake as surveying people who have already been at a party for twenty minutes and concluding that nobody ever leaves early. Once I removed that floor the answer reversed — the longer a burst has already lasted, the more likely it is to keep going.

Hot-episode hazard against elapsed time The truncation-robust discrete hazard falls from 0.325 to 0.167 per minute over 3 to 40 minutes elapsed. The rejected Weibull fit rises instead. 0 0.2 0.5 0.8 3 10 20 30 40 minutes the burst has already lasted chance it ends this minute 0.3247/min, 113 episodes ended 0.2394/min, 45 episodes ended 0.1939/min, 19 episodes ended 0.1026/min, 8 episodes ended 0.1081/min, 4 episodes ended 0.1667/min, 1 episodes ended observed — falls rejected Weibull (k=1.43) — rises

Chart scrolls sideways on a narrow screen — or open “Show the numbers” below.

Show the numbers
bin start minbin end minhazard per min observedrejected weibull shape from k normalised at first binn episodes ending in bin
3.06.00.32470.3247113
6.010.00.23940.416845
10.015.00.19390.505919
15.025.00.10260.62038
25.040.00.10810.76594
40.0200.00.16670.86041
What you're looking at: the solid line is the chance a busy spell ends in the next minute, given it has lasted this long already. It falls, so bursts that survive their first few minutes tend to keep running. The dashed line is what the rejected model claimed — that the chance rises — which would have meant the opposite. n = 191 busy spells · data 2026-06-17 · pre-registered · truncation-robust re-analysis, not held-out · hazard.csv
The technical version

Reversal — the conjecture was wrong

Repricing intensity freezes rather than sharpens as a market approaches the end of its activity: the time-trend coefficient β_τ (beta-tau — how the move rate changes as the deadline nears; negative means slowing) = −0.131, credible interval [−0.188, −0.074], with 8 of 9 markets frozen at the first cohort. Re-anchoring at the settlement record makes it stronger. The direction held from n=9 through n=318 under frozen specifications.

Evidence — pre-registered, with recovery and identifiability gates that had to pass before any empirical number was read; both directions and the null admissible in advance. Caveat — on the trade-tape re-run the freeze survives as a median-market property but the pooled-rate gate fails; that robustness slice is graded partial.

Reversal — a rejected parametric fit

A pre-registered Weibull gave shape k = 1.434, CI (credible interval — the range the true value plausibly sits in) [1.305, 1.575] — a rising hazard. It is flagged in the committed result file as weibull_hot_ARTIFACT, left-truncation-biased by the three-minute minimum-episode floor, and is not the verdict basis. The truncation-robust model-free discrete hazard falls from 0.325 to 0.167 per minute over 3→40 minutes elapsed, log-hazard slope −0.603, CI [−0.933, −0.205], and survives a frailty control. Note the last bin rests on a single episode.

The thesis asked whether a market's closeness to a settled consensus predicts its activity, and answered the question with evidence.

I had a specific hypothesis, written down and committed to version control before I looked at any result — a practice called pre-registration, which exists precisely so you cannot quietly change what you were testing after seeing how it came out. The hypothesis was that knowing how close a market's price had come to a settled consensus would help predict its activity, beyond simply knowing how much time was left. It did not. The measured effect was almost exactly zero, with a range wide enough to include a decent effect in either direction. That is not a near miss; it is a genuine no-answer, and the thesis reports it as one.

The same study produced a cleaner lesson about measurement. A pre-registered way of scoring how far ahead the model detected activity gave an encouraging ten minutes. Measured instead against the first actual price move — the very data the model reads — the model was two minutes late, and was early in none of the 48 cases. Both numbers are correct. They measure different things, and the flattering one is not the one I report.

Five definitions of detection lead The pre-registered metric reports a +10 minute lead. Measured against the first raw repricing the same model is 2 minutes late, and never early. 0 = simultaneous pre-registered metric (n=68) median 10 min, CI [9, 10], n=68, frac>0 = 0.941 +10 min · frac>0 0.94 vs centred label (n=48) median 6 min, CI [2, 8], n=48, frac>0 = 0.812 +6 min · frac>0 0.81 vs causal detector (n=48) median 3 min, CI [3, 7], n=48, frac>0 = 0.833 +3 min · frac>0 0.83 episode end (n=41) median 6 min, CI [-1, 9], n=41, frac>0 = 0.585 +6 min · frac>0 0.58 vs first raw repricing (n=48) median -2 min, CI [-3.5, 0], n=48, frac>0 = 0 -2 min · frac>0 0

Chart scrolls sideways on a narrow screen — or open “Show the numbers” below.

Show the numbers
metriclabelmedian minci loci hin episodesfrac positive
lead_preReg_minpre-registered metric10.09.010.0680.941
lead_upcross_vs_centered_minvs centred label6.02.08.0480.812
lead_vs_causal_detector_minvs causal detector3.03.07.0480.833
end_lead_minepisode end6.0-1.09.0410.585
lead_vs_first_raw_count_minvs first raw repricing-2.0-3.50.0480.0
What you're looking at: five ways of scoring the same question — how far ahead did the model see activity coming? Each dot is the typical answer under one definition, and the line through it is the range. Dots to the right of the dashed line mean the model was early. The bottom row is the one that compares against the first real price move, and it sits on the wrong side of the line. n = 48–68 episodes depending on metric · data 2026-06-17 · pre-registered · held-out, walk-forward · lead-metrics.csv
The technical version

Null — the primary pre-registered test failed

The thesis's own primary hypothesis was that a consensus term would improve prediction beyond a calendar term. It did not: −1.1 nats, CI [−394.6, +409.1] at n=318, with 46% of markets positive. The thesis calls this "a genuine equipoise, not an underpowered near-miss", and states the consequence against its own interest — the licensed claim becomes mechanism-existence, not mechanism-primacy.

Evidence — pre-registered, leave-one-market-out, market-clustered bootstrap, carried from n=105 to n=318 with zero re-specification.

The five lead definitions

lead_preReg_min median +10.0, CI [9.0, 10.0], n=68, frac>0 = 0.941. Against the first raw repricing, lead_vs_first_raw_count_min median −2.0, CI [−3.5, 0.0], n=48, frac>0 = 0.000. Detection rate 0.68, CI [0.412, 0.891], rising to 1.00 pooled. The pre-registered metric's apparent lead tracks the length of its own lookback window, which is what makes it an artifact rather than a finding.

Letting each market learn from all the others improved prediction on every market tested.

Most of these markets are individually data-poor. The fix is partial pooling: rather than estimating each market alone, let a thin market borrow the shape of the typical market. It worked on all 34 markets tested, with none going the other way, and it pulled the data-poorest markets about forty times harder than the data-rich ones — which is exactly the behaviour you want from it.

Then the awkward part. My pre-registered comparison showed that a self-exciting model — one where each price move simply makes the next more likely, with nothing hidden driving it — beat the hidden-mood model by a wide margin. After that comparison was closed, I tested a variant I had flagged in advance but not registered: giving the hidden-mood model a third mode instead of two. It reversed the result. So I narrowed the claim rather than defending it. What I can say is that a self-exciting account beat the specific two-mode model I tested, not that it beats hidden-mood models in general.

Per-market change in held-out log-evidence Partial pooling improves held-out predictive log-evidence on all 34 of 34 markets, leave-one-market-out. Log scale; every bar is above zero. 0.1 1 10 100 1,000 10,000 market 1: +0.6 nats, 176 events, other market 2: +1.5 nats, 365 events, geopolitics market 3: +1.9 nats, 366 events, geopolitics market 4: +2.2 nats, 690 events, other market 5: +2.3 nats, 243 events, other market 6: +2.6 nats, 981 events, other market 7: +3.2 nats, 187 events, crypto market 8: +4 nats, 704 events, politics market 9: +4.2 nats, 3763 events, other market 10: +4.7 nats, 1712 events, politics market 11: +5 nats, 609 events, politics market 12: +5.6 nats, 1056 events, geopolitics market 13: +5.9 nats, 278 events, other market 14: +6.9 nats, 1116 events, politics market 15: +7.3 nats, 1014 events, sports market 16: +7.4 nats, 653 events, geopolitics market 17: +7.5 nats, 114 events, other market 18: +7.6 nats, 1031 events, sports market 19: +7.6 nats, 1183 events, geopolitics market 20: +7.7 nats, 947 events, geopolitics market 21: +8.7 nats, 842 events, crypto market 22: +9.7 nats, 521 events, other market 23: +12.2 nats, 135 events, other market 24: +47 nats, 69 events, other market 25: +130.5 nats, 148 events, sports market 26: +132.1 nats, 133 events, other market 27: +212.5 nats, 136 events, other market 28: +323.8 nats, 118 events, sports market 29: +594.2 nats, 254 events, sports market 30: +771.1 nats, 380 events, sports market 31: +834.3 nats, 402 events, other market 32: +1494.7 nats, 577 events, sports market 33: +1962.9 nats, 816 events, other market 34: +4770.5 nats, 1449 events, sports 34 markets, sorted — log scale, or one market flattens the other 33 better prediction → all 34 positive

Chart scrolls sideways on a narrow screen — or open “Show the numbers” below.

Show the numbers
rankdelta natsn eventscategoryregime
10.63176otherreveal_before_wire
21.549365geopoliticsreveal_before_wire
31.88366geopoliticsreveal_before_wire
42.207690otherreveal_before_wire
52.311243otherreveal_before_wire
62.57981otherreveal_before_wire
73.248187cryptoreveal_before_wire
84.031704politicsreveal_before_wire
94.1683763otherreveal_before_wire
104.6711712politicsreveal_before_wire
115.048609politicsreveal_before_wire
125.6251056geopoliticsreveal_before_wire

First 12 of 34 rows. Download all 34.

What you're looking at: each bar is one prediction market. A bar's height is how much better the pooled model predicted that market's unseen data than a model fitted to that market alone. Every one of the 34 is above zero, which is the result — a single bar below the line would have counted against it. The scale is logarithmic because one market gains about 4,770 and the typical market gains about 8; on a straight scale two thirds of the bars would be invisible. n = 34 markets · data 2026-06-17 · pre-registered (SPEC ab05bc1 committed before results 5293b7a) · held-out, leave-one-market-out · pooling-per-market.csv
The technical version

Positive — partial pooling

Partial pooling improved held-out predictive log-evidence on 34/34 markets leave-one-market-out: total +11,400 nats, CI [3,256, 23,292], mean +335/market, CI [+32, +639]. Shrinkage regularizes sparse markets 40.5× harder than data-rich ones on λ_cold. Σ's top-3 eigen-axes carry 87.6% of variance.

Reversal — a post-hoc competitor beat my own headline

A pre-registered comparison found a self-exciting model predicting repricing far better than the two-state latent-state model: +48,970 nats, CI [+35,238, +67,016], zero drop-one flips. After the pre-registration closed I tested the flank I had disclosed — and a three-state competitor reverses that ordering by 37,868 nats, CI [+30,156, +46,662]. A simulation control rules out a parameter-count artifact.

So the claim was narrowed rather than defended: the result is scoped to the tested two-state model, not to the model class. One-step-ahead count likelihood does not separate the two accounts. The licensed wording is "consistent with self-excitation", never identified endogeneity, because a self-exciting kernel and serially-correlated exogenous information generate the same counts.

Evidence — the winning arm pre-registered and recovery-gated; the competitor exploratory, post-freeze and not pre-registered. Both numbers are published together or neither.

I benchmarked the interpretable models against neural networks to find out how much simplicity was costing.

The models above are deliberately simple: a handful of numbers each, chosen so a human can read what they mean. The obvious objection is that a large neural network would do better, so I ran that comparison, decided the rules in advance, and evaluated the test set exactly once. The neural network won, ahead on every one of the 51 markets held back for testing.

The simple models still capture about ninety per cent of the total gap between doing nothing and the neural network's performance, using three to six numbers instead of millions. The honest reading of that is the unflattering one: the neural model's learning curve was still improving when the data ran out, so its advantage is a floor rather than a ceiling, and the ninety per cent is the most generous figure the simple models can claim rather than the least.

The technical version

Negative — my own model class lost its benchmark

A pre-registered neural benchmark put the interpretable models on trial, and they lost: an LSTM (long short-term memory network, a standard neural network for sequences) beat the champion by +15,600 nats, CI [+10,675, +21,640], ahead on 51/51 held-out test markets, zero drop-one flips, Benjamini–Hochberg-corrected p < 0.001 (a correction applied because testing many things at once makes a lucky-looking result more likely). The interpretable ladder still covers 90.8% of the Poisson-to-LSTM gap, CI [88.2, 92.7], using 3 to 6 parameters.

The direction of the bound is the unflattering one, and is reported that way: the learning curve is still rising at n=206, so +15,600 is a lower bound on the neural advantage and 90.8% is an upper bound on the interpretable share. The share is defined relative to this baseline and this champion on this split, not as an intrinsic fraction of learnable structure.

Evidence — pre-registered, falsification-tested in both directions, test set evaluated exactly once. Disclosed deviation — a single frozen split at n=206, not the leave-one-market-out standard used elsewhere. Model-selection validation only: no costs, no fills, no trading claim.

The hidden quiet/busy split is real, well identified, and about twenty-eight-fold.

The model does recover two genuinely distinct modes. In the quiet mode a market moves its price about once every twenty-five minutes; in the busy mode, about once every fifty-three seconds — roughly twenty-eight times faster. Markets stay quiet for around thirty-seven minutes at a time and busy for around eight. None of that is in doubt.

The problem is what it is worth. Knowing which mode a market is in does not tell you what happens next any better than simply noting that it has been active recently. A real structure that carries no forecasting value is still a finding — it is just not the finding I wanted.

Two-state repricing intensity The hot state reprices about 27 times faster than the cold state. Cold dwells last about 37 minutes, hot about 8. cold state cold: 0.041/min, CI [0.025, 0.063] 0.041 /min ~37 min dwell hot state hot: 1.141/min, CI [0.946, 1.383] 1.141 /min ~8 min dwell 0 0.5 1 1.5 price moves per minute (further right = busier)

Chart scrolls sideways on a narrow screen — or open “Show the numbers” below.

Show the numbers
statelambda per min medianlambda lolambda hidwell min mediandwell lodwell hi
cold0.04070.0250.062537.140126.194255.2032
hot1.14090.94581.38348.09896.543210.2869
What you're looking at: how fast prices move in each of the two modes, in moves per minute. The quiet bar is so short next to the busy one because the gap is about twenty-eight-fold — that gap is the point. The thin line through each bar is the range the true value plausibly sits in. n = 34 markets · data 2026-06-17 · pre-registered · in-sample population fit, bars show 95% credible intervals · two-state.csv
The technical version

Population parameters

λ_cold (lambda-cold — the quiet-mode move rate) median 0.0407/min, CI [0.0250, 0.0625]; λ_hot median 1.1409/min, CI [0.9458, 1.3834]. Dwell times 37.1 min cold, 8.1 min hot. The published medians give a ratio of 28.05×; the committed report states 27×, computed on the posterior means (27.58×). Both are correct on their own basis and the prose above uses the medians, because those are the numbers published in the CSV.

Carried limitation — the hazard/trigger block is weakly identified at this n (R-hat 1.23–1.37, ESS 26–41). Only the sign and ordering are claimed robust, not the magnitudes; sharpening is expected to need n ≈ 50.

Limitation — the winner fails its own absolute fit test

A time-rescaling check rejects the best-performing model on most markets. It is the best-performing tested approximation, not a generatively adequate description — and the residual clustering it misses is the same headroom the neural benchmark exploits. The two strands agree on where the ladder ends.

The full write-up, with the method and every caveat →