{# avoid-ai-design-ignore-file: K7 (reason follows) :focus-visible is styled for the whole site in pmlab.css #} {#- The home page. Every number on it comes from docs/assets/data/sample-run.json (see scripts/make_site_data.py), read by scripts/mkdocs_hooks.py; the quickstart is the code block of docs/index.md, which is the README's, which CI runs. Nothing here is typed in by hand. -#} {% extends "main.html" %} {% set s = config.extra.sample %} {% set h = config.extra.home %} {% set gate = s.gate %} {% block site_nav %}{% endblock %} {% block container %}
pmlab {{ config.extra.pmlab_version }}stable API · MIT · Python 3.12+
{# avoid-ai-design-ignore: SD5 (reason follows) two whole lines, solid then outlined, the hiddenterps.si headline pattern; no single word is accented #}pmlab is a plugin-based ML framework for Polymarket. Write four methods for a market family; pmlab does the rest: edge and cost arithmetic, walk-forward backtests, a hard GO / NO_GO gate before anything is promoted, paper trading, and settlement that waits until the truth is final.
pip install pmlab
traintest daynot seen
brier by fold, lower is better ({{ '%.3f' | format(h.spark_range[0]) }} to {{ '%.3f' | format(h.spark_range[1]) }})
Synthetic sample: {{ s.sample.markets }} markets, {{ s.sample.cities }} cities, {{ s.sample.days }} days, no network. It says nothing about real markets.
Enforced in code and checked by the test suite, not left to convention. Each claim below links to the page that shows how.
rolling_origin_eval trains only on rows whose label was already known when the fold decided. build_panel refuses a price seen at or after the decision it informs, and check_no_leakage raises when a feature gives the outcome away.
fold 1 train {{ s.backtest.train_start }} → {{ h.first_fold.train_end }} test {{ h.first_fold.test }} fold {{ s.backtest.folds }} train {{ s.backtest.train_start }} → {{ h.last_fold.train_end }} test {{ h.last_fold.test }} ({{ h.last_fold.train_rows }} rows)How the checks work
ChampionManifest.publish raises unless the gate says GO, and that is not configurable. On the sample the gate reads every city, one of them loses money, and publishing is refused. Scope the champion to the cities that pass and it publishes, with that scope recorded in champion.json.
ValueError: {{ gate.publish_refused }}
The champion registry
Every backtest records its seeds, library versions, resolved config and a fingerprint of the data. pmlab reproduce re-runs it and verifies that the metrics come back.
reproduce → {{ s.reproduce.status }} data match: {{ 'yes' if s.reproduce.data_match else 'no' }} metrics compared: {{ s.reproduce.metrics_compared }}Reproducibility
From 1.0 pmlab follows semantic versioning. What is public is compared with a checked-in snapshot on every change, and nothing public is removed before 2.0 without a deprecation warning that names the replacement.
{{ s.api.names }} public names in {{ s.api.modules }} modules · {{ s.api.commands }} CLI commands · tests/api/public_api.jsonWhat is public
A champion is never published with a NO_GO gate. Canonical training data is never overwritten without an explicit flag. Workspaces are never mixed: live forward evidence and training data stay apart. The champion's segment gate is always read from champion.json, the record made at promotion. A trade is never settled before MarketPlugin.is_truth_final says the truth is final.
Offline, on a small synthetic sample that ships in the package: no network, no account, no order placed. The code is the README's, and CI runs it.
pmlab command and a first offline session.pmlab monitor.
Source and issues on GitHub · PyPI · Changelog · Roadmap · Deprecation policy.
pmlab is research software and not financial advice: trading prediction markets can lose all the money you put in, and no pmlab command places an order.