Predicting the move is the easy part. Beating the price is not.
Every 15 minutes Kalshi opens a market — will Bitcoin be above a strike when the cycle closes? Five minutes in, our engine commits a direction. Over thousands of forward cycles it has been right about — of the time. That is a real signal.
But a hit rate is not money. At the moment the engine commits, the market is already charging a price that implies about —. Buy the side it favours at that price, pay the settlement fee, and the edge is — — currently negative. The market has, so far, priced the move at least as well as we predict it.
So this is the open question, and it is the whole point of the project: can the accuracy be made to exceed the price by enough to clear the fee — under some condition, some regime, some subset of cycles — often enough to matter? We test candidate answers, register each one before we see the result, and publish what happens. Most fail. We show those too.
The model reads a panel of signals every cycle and weighs them into a single directional read. The signals are standard and named below; what we keep private is how they are weighted and combined, and the thresholds that turn them into a committed call. Everything that can be verified from public data — the market price, every prediction, every outcome — is published in full.
Momentum & trend
EMA stack · MACD impulse · range breakout · ADX regime
Mean reversion
RSI extreme · Bollinger-band tag · VWAP stretch
Order flow
taker buy/sell delta · cumulative volume delta slope
Liquidity events
order-book sweeps · sweep reversals
Derivatives
perp funding bias · liquidation cascades
Context
time-of-day session clock · dead-market gate
What the engine predicts. Every 15 minutes, Kalshi opens one BTC market — will Bitcoin be above a set strike when the cycle closes? Five minutes in, our engine (M6E) reads order flow and momentum and commits a direction: UP, DOWN, or ABSTAIN. We record that call before the cycle settles, and score it against the price Kalshi was charging at that instant.
Write the pass mark down first.
Every engine is registered with a numeric gate it must clear and the sample size required, recorded before the results exist. Once a gate is published in advance, a failed result cannot quietly become a success. Everything downstream on this page depends on that one discipline.
What we score against. A prediction is only worth something if it beats the price you had to pay to act on it. So every call is measured against the market's own asking price at the exact moment the engine committed — accuracy alone is not a result.
We write down what would prove us wrong — before we have the data.
Every engine is registered here first with a numeric gate it must clear and the sample size required. The register is timestamped and append-only. Once a gate is published in advance, a failed result cannot be quietly reinterpreted as a success.
| Hypothesis | Registered | Gate to promote | n | Result | Status |
|---|---|---|---|---|---|
| M6E-COMMITTED-v1all committed tiers · minute 5 | 2026-07-03 | accuracy ≥ 0.7178 at n ≥ 100 | 597 | 0.683 | FAILED |
| M6E-E3-v1highest-conviction tier only | 2026-07-06 | edge lower bound > 0 | 152 | −0.036 | RUNNING |
The first hypothesis failed its own gate. We publish that, because the alternative — moving the goalposts after seeing the data — is how most trading claims on the internet are manufactured.
Every call, and how each one settled.
The full table, the favourite-echo check and the hash-chain verification live on the Ledger, with the complete history downloadable as CSV.
No. Accuracy minus price minus fees was negative in every tier. The market had already priced in what the model knew.
No — worse in every configuration tested. On a high-probability binary the losing side drifts then gaps, so a stop triggers on noise or arrives far too late.
No — the dip is a loss detector, not a discount. Dips ≥4c appear in 51–55% of winners but 78–95% of losers, three to five times deeper.
Not this one. It converges at the same speed as the order book, not ahead of it. Tested commitment at minutes 1–5; no horizon cleared the price with confidence.
No. Look-ahead bias — it priced a late, matured signal at an early, cheap price. We found it ourselves. It is the reason this site exists.
Strikingly often. Across every probability band, win rate matched price. A contract at 70c wins about 70% of the time — the bar everything else must clear.
EXCHANGES INGEST + RESEARCH BOX PUBLIC BOX BROWSER
───────── ───────────────────── ────────── ───────
Binance ws ──aggTrade──▶ ┌──────────────────────┐ ┌────────────┐
Kraken ws ──trades────▶ │ ingestor → TimescaleDB│ │ candles │
│ ticks · candles │ │ footprint │
│ fut_state · liq │ │ tape │
└──────────┬───────────┘ │ depth │
│ SELECT-only, statement_timeout 20s └─────▲──────┘
▼ │
┌──────────────────────┐ rrsync -wo ┌───────────┐ fetch │
│ feature engine F1-F14│ ──────────────▶│ Caddy │────────┘
│ model → predictions │ one-way push │ static │
│ (DRY-RUN, no orders) │ 185 ──▶ 169 │ + /data │
└──────────────────────┘ └───────────┘
▲
Binance public ws ────────────────────────────────────────────────────────┘
(browser connects DIRECT — the live layer never touches the research box)
The load-bearing decision: the live layer streams from the exchange to the browser, not through our servers. The research box also runs a live trading daemon, so every public request that could reach it is one that could degrade it. Public traffic and trading are physically separate paths.
Read-only by construction
The exporter opens its database session with readonly=True and
statement_timeout='20s'. It can only issue SELECTs, and the database kills anything slow
before it can compete with the trading daemon.
One-way credentials
The push runs research → public, never the reverse, over a key pinned to
rrsync -wo on a single directory. The public box holds no credential that reaches the
trading system.
22× from reading the plan
The volume-delta aggregation took 7,007 ms with a symbol predicate and
date_trunc. Rewritten on Timescale's time_bucket it takes 319 ms.
Same result, same window.
Sign conventions get verified twice
Buy/sell classification was confirmed in two independent code paths before publication. A flipped delta sign would invalidate every order-flow number on this site while still looking plausible.
Freshness is shown, never implied
Every panel carries its own age. If a snapshot is stale or a fetch fails, the page says so and blanks the panel rather than presenting old numbers as current.
Reproducible indicators
EMA, Bollinger, VWAP and RSI are computed in your browser from the same bars we publish at
/data/candles.json. Every line on the chart can be recomputed from public data.
The model commits at a fixed point in each cycle, guarded by a time window. Interpreter startup pushed it to the very edge of that window, so 49.2% of cycles were dropped — and the loss grew with machine load, biasing the sample. Fixed by firing early and sleeping to the exact commitment moment, so every prediction lands on the operating point the model was validated at.
A profile query scanned ~1.4M rows per symbol and was scheduled every minute against the box running the trading daemon. Caught and killed within two minutes, no trading impact. The window was cut and a hard statement timeout added, so the class cannot recur.
Two sibling scripts each declared the same top-level constant. That is a syntax error which silently kills the second file — the chart simply never ran, with an empty console. Now each script is scoped, and a build gate concatenates them to reproduce the shared scope and fail on any collision.
The first version re-rendered once a minute from a file. It was accurate and completely wrong as a product — a market view that does not move is a screenshot. Rebuilt on the same charting engine our internal desk uses, streaming per tick.
Ingest & storage: Python websocket consumers, PostgreSQL + TimescaleDB hypertables.
Research: NumPy/scikit-learn feature engine (F1–F14), logistic model refit per horizon,
walk-forward validation with pre-registered promotion gates.
Publishing: read-only Python exporter, rrsync over SSH, cron with flock.
Serving: Caddy with automatic TLS behind Cloudflare, strict CSP, static assets.
Front end: vanilla JavaScript, no framework, no trackers, no cookies —
TradingView Lightweight Charts
(Apache-2.0) is the only third-party dependency,
vendored locally.
Weight: the whole front end is a few hundred KB and renders without a build step.
Vamsi Venkatesh — Magdeburg, Germany. I build and run the whole stack: ingest, research, modelling, infrastructure and front end.
The most useful thing here is the record, because it shows an engine I built failing its own pre-registered test — measured, published, and not quietly retired. Anyone can claim a hit rate; the interesting question is what someone does with a result they did not want.