Skip to main content

A backtester for binary prediction markets that grades against the price you could actually transact at — realizable fills, fees, and an adverse-selection diagnostic — not the optimistic paper fill.

Project description

honest-backtest

Your prediction-market backtest is lying to you. This one tells the truth.

Most backtests paper-fill at the displayed ask and report a win rate. On an efficient venue the transactable price already contains the signal — so WR ≈ ask by construction, and the "edge" you see is an artifact of filling at prices you could never actually get. Deploy on that and you lose money the backtest swore you'd make.

honest-backtest grades every decision against the price you could actually transact at:

  • edge_real = mean(won − fill_px) — the only honest headline. Win rate alone is meaningless; if you pay the ask and the ask equals your win rate, your edge is zero.
  • Realizable fills, not the paper ask — two lenses:
    • book-persistence: did the ask still rest at your limit ~1s later, or did it pull above it (the classic "asks vanish on the winners")?
    • tape-corroboration: did a real trade print at your price? Proves the ask was genuine, not a thin phantom ladder.
  • Real fees — PM taker fee is fee_rate·p·(1−p) per share; makers pay 0. No hand-wavy min(p,1−p).
  • Adverse-selection diagnostic — a Fokker-Planck fair-value model flags trades where the book baited you (cheap ask) but the physics disagree: the fills you get are the losers; the winners reject you.
  • ghost_gap = paper edge − honest edge = the adverse-selection drag, in one number.

This is the harness that retrodicted six consecutive live strategy deaths on Polymarket up/down markets — every one looked great on paper and lost real money.

Install

pip install honest-backtest      # numpy is the only dependency

Quick start

from honest_backtest import Signal, Decision, evaluate

class BuyTheCheapNo(Signal):
    name, family, mode = "cheap_no", "demo", "taker"
    def decide(self, ctx, i):
        # read ONLY indices <= i (no lookahead). Return a Decision or None.
        if ctx.book_ok(i) and 0 < ctx.na[i] < 0.40 and ctx.yes_mid(i) >= 0.56:
            return Decision(i=i, ts_ms=int(ctx.ts[i]), token_yes=False,
                            action="taker", target_px=float(ctx.na[i]), size=20.0)
        return None

row = evaluate(BuyTheCheapNo(), ctxs)   # ctxs: iterable of SlotCtx
print(row["headline_edge_real"], row["ghost_gap"])

Bring your own SlotCtx stream (build them from your data with SlotCtx.from_rows), or load the open Polymarket up/down dataset straight from the Hub parquet (no 2 GB download):

pip install "honest-backtest[parquet]"
huggingface-cli download kinzikdza/polymarket-updown-microstructure \
    --repo-type dataset --local-dir pm_data
from honest_backtest.adapters.parquet_pm import load_corpus
ctxs = load_corpus("pm_data/parquet", coins=("btc",), durations=("5m",))

A SQLite adapter (adapters.sqlite_pm.load_corpus, stdlib-only) is also there if you have the data as a .sqlite file.

The calibration anchor

The shipped example no_overpriced is a calibration gate, not a strategy to trade. Live it ran 325 fills at WR 0.382 vs avg ask 0.386 → edge_real = −0.004. A naive paper backtest of the same rule showed +0.07. The honest harness must reproduce ~0/negative here:

python -m honest_backtest.examples.no_overpriced pm_data/parquet   # or open_dataset.sqlite

If it prints a strongly positive edge_real, the fill model has drifted optimistic — that's a bug in fills.py, not an edge.

Why edge_real, not WR

WR > 50% with ask > WR is not an edge — the market priced higher than you win. Only WR > ask is a real edge.

On an efficient venue, corr(signal, outcome) is usually positive (momentum, spot-vs-strike do predict) — but that's not the same as beating the transactable price. The gap between the two is adverse selection, and it's exactly what ghost_gap measures.

API

function what it does
evaluate(signal, ctxs) run a signal over a corpus → one leaderboard row
run_signal(signal, ctxs) → raw per-decision grade dicts
grade_taker(ctx, decision) book-persistence + tape-corroboration fill grade
grade_maker(ctx, decision) queue-aware resting-bid fill grade
leaderboard_row(name, family, mode, recs) aggregate grades → edge_real, CIs, $ROI, breakdowns
physics_features(ctx, i, buy_yes) / adverse_score(feat, buy_yes) adverse-selection diagnostic
won_buy, fee_per_share, fp_prob_yes, bootstrap_ci building blocks

Signal, SlotCtx, SlotMeta, Decision are the data model. A Signal implements decide(ctx, i) reading only indices <= i.

Dataset

The companion open dataset (6 weeks of real Polymarket BTC/ETH/SOL/XRP up/down microstructure — book snapshots, trade tape, and gamma-oracle resolutions) ships in the schema this library's SQLite adapter reads. See the dataset card for provenance and caveats.

License

Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

honest_backtest-0.2.0.tar.gz (35.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

honest_backtest-0.2.0-py3-none-any.whl (25.9 kB view details)

Uploaded Python 3

File details

Details for the file honest_backtest-0.2.0.tar.gz.

File metadata

  • Download URL: honest_backtest-0.2.0.tar.gz
  • Upload date:
  • Size: 35.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for honest_backtest-0.2.0.tar.gz
Algorithm Hash digest
SHA256 4bc0a754202d4f0feb0b00d195e73da54cfaa1129f33f088d2de274349ac14d4
MD5 7e5d664766bcd9d74ade8b023d602f72
BLAKE2b-256 2ca1bfae2dafff19682274e7ca6b9b1cee06454473a962c1fc8e9bd06c386449

See more details on using hashes here.

File details

Details for the file honest_backtest-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for honest_backtest-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5cc3c6dc9917692268824cf88f211bd250143145ad199032fa2280033db779be
MD5 810692960d011e8cfc0992c122787758
BLAKE2b-256 ea32a4786aeb41c6721958fc59e7c46b540785472ae8f5761c7d76570942f30c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page