pmlab
A plugin-based ML framework for Polymarket prediction markets. Build, backtest and paper-trade models for any market family (weather, sports, politics, crypto) with a backtest that cannot look ahead and a gate that no failing model gets past.
You write a small plugin that says how to find a family's markets, build its features and resolve its outcomes. pmlab does the rest: edge and cost arithmetic, walk-forward backtesting, a hard GO / NO_GO gate before a model is promoted, paper trading, settlement, drift monitoring and a cron-friendly watchdog.
Not financial advice. pmlab is research software; trading prediction markets can lose all the money you put in. No
pmlabcommand places an order. Only aLiveBrokeryou build yourself, with your own Polymarket credentials, does, anddry_run=Trueis how you try it first.
Why it is safe to build on
These are enforced in code and checked by the test suite, not left to convention.
- No lookahead.
rolling_origin_evaltrains only on rows whose label was already known when the fold decided: a decision for tomorrow's temperature is not a label until tomorrow is over.build_panelrefuses a price observed at or after the decision it informs, andcheck_no_leakageraises when a feature gives the outcome away. How the checks work - A failed gate cannot ship.
ChampionManifest.publishraises unless the gate says GO, and that is not configurable. Paper trading reads its allowed segments fromchampion.json, the record made at promotion, and a trade is never settled before the plugin says the truth is final. - Reproducible. Every backtest records its seeds, library versions, resolved config and a
fingerprint of the data;
pmlab reproducere-runs it and verifies the metrics come back. Reproducibility - Stable. From 1.0 pmlab follows semantic versioning. The public API (the
names, the
pmlabcommand and its options, the file formats) is frozen and compared with a checked-in snapshot on every change; nothing public is removed before 2.0 without a deprecation warning first. What is public
Install
pip install pmlab # Python 3.12+; or: uv add pmlab
pip install "pmlab[tune]" # + Optuna, for TunedForecaster
pip install "pmlab[track]" # + MLflow, for MLflowTracker
Quickstart
This runs offline on a small synthetic sample bundled in the package: 750 weather-temperature markets, no network and no account. The numbers say nothing about real markets.
from pmlab import (ChampionManifest, HoldoutGateResult, LGBMForecaster, TradePolicy,
build_panel, rolling_origin_eval)
from pmlab.datasets import load_sample
sample = load_sample() # 750 synthetic weather markets, checked against a checksum
resolver = sample.plugin() # the real WeatherTmaxPlugin, wired to the bundled file
found = [(day, spec) for day in sample.days for spec in sample.plugin(day).discover_markets()]
# One row per (market, bin), each at its own price. build_panel refuses a price seen at or after
# the decision, and check_leakage=True raises if a feature gives the outcome away.
panel = build_panel(
[resolver.build_training_rows(s, "previous_evening", decision_time=f"{d}T12:00:00+00:00")
for d, s in found],
check_leakage=True,
)
# The forecast alone cannot tell a market's bins apart: add how far each bin sits from it.
bounds = {(s.market_id, b.label): [x for x in (b.lower, b.upper) if x is not None]
for _, s in found for b in s.outcome_bins}
centre = [sum(bounds[k]) / len(bounds[k]) for k in zip(panel["market_id"], panel["outcome_label"])]
panel["feature_bin_dist"] = centre - panel["feature_fcst_tmax"]
# Walk-forward backtest: every fit sees only rows from before the day it predicts.
model = LGBMForecaster(random_state=7, n_jobs=1)
result = rolling_origin_eval(
panel, model, stride=10, min_train_rows=2700,
policy=TradePolicy.live(min_edge=0.05), # what `pmlab record-trades` trades: best bin and side
)
gate = HoldoutGateResult.evaluate(result.trades)
print(len(result.trades), "trades | PnL", round(result.trades["realized_pnl"].sum(), 2), "|", gate.decision)
panel.to_parquet("panel.parquet") # the command line below reads this file
# A gate that fails cannot publish a champion.
failed = HoldoutGateResult.evaluate(result.trades, required_segments=["Atlantis"])
try:
ChampionManifest.publish(model, failed, "champion", plugin_family="weather_tmax")
except ValueError as err:
print("refused:", err)
It prints the number of trades, the total PnL and the gate's decision, then the refusal. The same panel runs from the command line:
pmlab backtest --plugin weather_tmax --panel panel.parquet \
--stride 10 --min-train-rows 2700 --min-edge 0.05 --output metrics.json
pmlab reproduce metrics.json # re-runs the backtest and verifies the same metrics come back
The tutorial continues from here: tuning, a per-city gate, a published champion, paper trading, settlement and an HTML report, in one notebook that CI executes.
The command line
| Command | What it does |
|---|---|
pmlab backtest |
Walk-forward backtest of a panel, recorded so it can be reproduced |
pmlab reproduce |
Re-run a recorded backtest and verify the metrics |
pmlab promote-champion |
Publish a model as champion, only if its gate is GO |
pmlab scan-markets |
List a family's open markets from the Gamma API |
pmlab record-trades |
Paper-trade the scan's signals that clear --min-edge |
pmlab status · report · resettle |
Inspect paper trades, render an HTML report, repair old settlements |
pmlab monitor |
The watchdog: drift, live calibration and live PnL; silent when healthy |
pmlab settle-trades |
A placeholder that exits 1: it settles nothing and prints the SettlementEngine call that does |
pmlab version |
Print the version |
pmlab --help and pmlab <command> --help list every option. Command line reference
Bring your own market family
A plugin is one class with four methods. Everything else is shared.
from pmlab import MarketPlugin
class MyPlugin(MarketPlugin):
family = "my_markets"
def discover_markets(self, **kwargs): ...
def fetch_features(self, spec, horizon, **kwargs): ...
def fetch_truth(self, spec, **kwargs): ...
def build_training_row(self, spec, horizon, **kwargs): ...
Documentation
Everything else lives on the documentation site.
| Start here | Tutorial · Architecture · Command line · Plugin authoring |
| Build models | Data layer · Features · Modeling · Tuning · Costs · Position sizing |
| Run it | Live trading · Drift monitoring · Watchdog · Champion registry |
| Reference | API reference · Public API and stability · Deprecation policy |
| Project | Changelog · Roadmap · Contributing · Security |
Development
git clone https://github.com/ArtBreguez/polymarket-lab && cd polymarket-lab
uv sync --extra dev
uv run pytest # the suite; the coverage gate is in pyproject.toml
CONTRIBUTING.md has the rest: the test-first rule, the end-to-end, performance and notebook suites, and how the public API is changed.
License
MIT. See LICENSE.
Metadata
Release files for pmlab 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pmlab-1.1.1.tar.gz | 1.1 MB | With provenance | Details for pmlab-1.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pmlab-1.1.1-py3-none-any.whl | Python 3 | none | any | Details for pmlab-1.1.1-py3-none-any.whl |
Total release size: 1.4 MB
Release files / pmlab-1.1.1.tar.gz
| Download URL | pmlab-1.1.1.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
25fabb93f430d5571ed3ae7a2bd984b3be0365e2188dc151bc71da97d7d06430
|
|
BLAKE2b-256 checksum How to use checksums |
8b077071e4b48a8616f9c08575155d665627c31a23c89488aaf3c7699f98c5ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logRelease files / pmlab-1.1.1-py3-none-any.whl
| Download URL | pmlab-1.1.1-py3-none-any.whl |
|---|---|
| Size | 262.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cc5ac258607b76eedcf48c7d06e2425ea231738dad3eb573aff8be8a5cf43a66
|
|
BLAKE2b-256 checksum How to use checksums |
e5582f72de9f735e801c84498d4f3b3c3d60c4e0f5022f088837556306821f17
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logSecurity for pmlab 1.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for pmlab-1.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.