The build
What I built
Six scheduled collectors running every 15 to 60 minutes, plus real-time ingest over WebSockets, feeding an archive of ~1.05 TB as of August 2026 — that is the self-reported total on cloud storage; the server itself holds a 250 GB working set that fills and is periodically offloaded, which is its own small operational discipline. Every gap in the feed is logged and masked rather than interpolated, and continuity is accounted for rather than assumed — which is what makes the absence of activity evidence instead of missing data. Out of that corpus I built a licence-clean, hash-verified panel of 400 resolved markets and 188,856 trades.
On top sits the machine learning: gradient-boosted models with isotonic calibration running live inference and order execution, a walk-forward backtester with realistic fill simulation, and a behavioural clustering pipeline — UMAP, HDBSCAN and Gaussian mixtures — over 3,413 wallets.
$ systemctl --user list-timers 'poly-*' UNIT ACTIVATES EVERY poly-books.timer poly-books.service 15min poly-trades.timer poly-trades.service 15min poly-markets.timer poly-markets.service 30min poly-resolved.timer poly-resolved.service 60min poly-wallets.timer poly-wallets.service 60min poly-crypto5m.timer poly-crypto5m.service 15min 6 timers listed. 1 dormant unit not shown. $ poly-status --audit gaps 19 >1s over an 18-day window, 74.6 min total (~4.1 min/day) replay median touch-match 0.9992 across 19 markets structural audit: 219 markets, 0 defects
What was hard
Reliability turned out to be an auditing problem rather than an uptime problem. A collector that misses an hour is not a disaster; a collector that misses an hour silently destroys the analysis, because in this work the absence of a price move is itself the signal. So the pipeline records what it failed to see and masks those windows, and one lost collection day is carried as a declared 45-hour gap rather than quietly interpolated away.
The second hard choice was in the backtester. One strategy needed the queue of resting buy and sell offers, which the historical data does not contain. Substituting a rough stand-in would have produced a number — and that number would have been fiction. It declines to run instead, and reports why.
What I found
Trader behaviour predicts profit. The behavioural clustering produced types that hold up against held-out profit and loss, which mattered beyond the result itself: it broke a circular definition — "informed traders are the ones who make money" — that had blocked two earlier research directions.
None of the four candidate strategies beat a random baseline. That is the answer the platform was built to produce, and it is worth as much as a positive would have been: it is a measurement, not a disappointment. The fourth strategy is the one I would point at in an interview — it abstained on every market rather than trade on a signal the data could not supply.
Chart scrolls sideways on a narrow screen — or open “Show the numbers” below.
Show the numbers
| strategy | n markets | trades taken | p value vs random | verdict |
|---|---|---|---|---|
| mean_reversion_v1 | 382 | 67 | 0.4978 | NO_EDGE |
| longshot_fade_v1 | 382 | 157 | 0.6007 | NO_EDGE |
| depth_imbalance_v1 | 382 | 0 | NOT_APPLICABLE | NOT_APPLICABLE |
| regime_conditioned_v1 | 382 | 4 | 0.6127 | NO_EDGE |
strategies/depth_imbalance.py — the abstention path
snap = market_state.get("latest_snapshot")
if snap is None:
return _abstain("latest_snapshot missing from state")
dimb = getattr(snap, "depth_imbalance", None)
if dimb is None:
return _abstain("L2 depth_imbalance not available (L1-only snapshot)")
mid = float(getattr(snap, "mid", 0.0))
if not (self.extreme_lo < mid < self.extreme_hi):
return _abstain(
f"mid {mid:.3f} is at extreme; depth imbalance there is "
f"artifactual not informational")
Here L2 means the full list of resting buy and sell offers, and L1 only the single best price on each side. Three separate reasons to decline, and no branch that guesses. _abstain returns
"no trade" with the reason attached, so the refusal is recorded rather than silent.
The technical version
A complete research stack for a prediction-market venue: continuous collection, a modelling layer, and a fee-aware backtester. Six scheduled collectors feed an audited panel of 400 resolved markets and 188,856 trades, licence-clean and hash-verified. A predecessor execution line on BTC used gradient boosting with isotonic calibration across 35 tested hypotheses; its recorded limitation is that win rate is flat across edge magnitude, so the model captures direction but not conviction.
Headline result — negative
All four candidate strategies scored NO_EDGE against a random
baseline under a market-clustered
bootstrap:
mean_reversion_v1 p=0.498,
longshot_fade_v1 p=0.601,
regime_conditioned_v1 p=0.613. Verdict bands were
fixed in advance: below 0.05 beats random, 0.05–0.20 weak, at or above 0.20 no edge.
depth_imbalance_v1 returned NOT_APPLICABLE: the
historical dataset carries no
L2 depth — bid and ask are
null on every snapshot per the data audit — so its only signal is missing everywhere and
it abstains universally. Reported as not applicable rather than faked with a
tape-derived proxy.
One positive, with its caveat
A behavioural trader typology reached BEHAVIOR_PREDICTS_PNL on held-out profit and loss — Kruskal–Wallis p=7.7e-07, with 6 of 45 pairs surviving Bonferroni correction. Cluster stability was only MIXED: mean bootstrap adjusted Rand index 0.41. Reported with that caveat attached rather than on the strength of the p-value alone.
Real-time WebSocket pipelines, XGBoost with isotonic calibration, live inference and order execution, walk-forward backtesting with realistic fill simulation, Linux, systemd.