VVDEXOPS
Research · pre-registered · scored in public
Research

Is a 15-minute move predictable?

We built an engine to find out, wrote down what would count as success before we had the data, ran it forward, and published everything it did. This page is the whole story in order: the method, the record, what we found, and how it is built.

model accuracy
edge vs market
hash-chained calls
CONTENTS The model, live The problem What it watches Method Register Record Findings How it is built Mistakes Who built it
The model, live
LIVE CALLwill BTC be up in 15 minutes? the engine's answer right now
waiting for the next cycle…
THE MARKET WE PREDICT · Kalshi BTC 15mthe price we are trying to beat
loading market…
loading…
The problem we are actually working on

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.

What the engine watches

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

A call is only committed when at least two independent families agree — otherwise the engine abstains. Conviction tiers E0 (no call) through E3 (strongest agreement) reflect how many families line up and how strongly.

I · The method

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.

II · The register

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.

HypothesisRegisteredGate to promotenResultStatus
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.

III · The record

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.

IV · Findings — tested, then abandoned
Refuted
Does a 68% accurate predictor make money?

No. Accuracy minus price minus fees was negative in every tier. The market had already priced in what the model knew.

Refuted
Do stop-losses and take-profits help?

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.

Refuted
Is a dip inside a winning cycle a discount?

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.

Refuted
Can the model see the move before the book?

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.

Refuted
Our backtest showed +38%. Real?

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.

Confirmed
How often is the market's own price right?

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.

V · How it is built
  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.

Measured numbers
tape rate
prints per second ingested
tape depth
days of millisecond history
ingest lag
newest tick age
export
~15s
full snapshot build + push
book stream
100ms
depth update interval
Decisions worth defending

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.

VI · Mistakes, and what they cost
Fixed
Half the predictions were being silently discarded

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.

Fixed
A 24-hour query aimed at a live trading database

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.

Fixed
A one-word collision that erased a whole feature

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.

Fixed
A chart that looked live but was a snapshot

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.

VII · Stack

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.

VIII · Who built it

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.

vamsivenkatesh.com · UnboundPDF · Conxto