Metadata-Version: 2.5
Name: pmlab
Version: 1.0.1
Summary: A plugin-based ML framework for Polymarket prediction markets: no-lookahead backtests, a hard GO / NO_GO gate, paper trading.
Project-URL: Homepage, https://github.com/ArtBreguez/polymarket-lab
Project-URL: Documentation, https://artbreguez.github.io/polymarket-lab/
Project-URL: Repository, https://github.com/ArtBreguez/polymarket-lab
Project-URL: Changelog, https://github.com/ArtBreguez/polymarket-lab/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/ArtBreguez/polymarket-lab/issues
Project-URL: Security, https://github.com/ArtBreguez/polymarket-lab/blob/main/SECURITY.md
Author-email: Arthur Breguez <arthurbreguez@gmail.com>
License: MIT
License-File: LICENSE
Keywords: backtesting,calibration,forecasting,lightgbm,machine-learning,paper-trading,polymarket,prediction-markets
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: duckdb>=1.2.1
Requires-Dist: httpx>=0.28.1
Requires-Dist: lightgbm>=4.6.0
Requires-Dist: numpy>=2.2
Requires-Dist: orjson>=3.10
Requires-Dist: pandas>=2.2
Requires-Dist: py-clob-client>=0.18.0
Requires-Dist: pyarrow>=19.0
Requires-Dist: pydantic-settings>=2.8
Requires-Dist: pydantic>=2.10
Requires-Dist: pytz>=2025.1
Requires-Dist: rich>=13.9
Requires-Dist: scikit-learn>=1.6
Requires-Dist: tenacity>=9.0
Requires-Dist: typer>=0.15
Provides-Extra: dev
Requires-Dist: ipykernel>=6.29; extra == 'dev'
Requires-Dist: mypy>=1.14; extra == 'dev'
Requires-Dist: nbclient>=0.10; extra == 'dev'
Requires-Dist: nbformat>=5.10; extra == 'dev'
Requires-Dist: optuna>=3.6; extra == 'dev'
Requires-Dist: pandas-stubs>=2.2; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest-github-actions-annotate-failures>=0.2; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.9; extra == 'dev'
Requires-Dist: types-pytz>=2025.1; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material<10,>=9.6; extra == 'docs'
Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
Requires-Dist: mkdocstrings[python]<2,>=1.0; extra == 'docs'
Requires-Dist: ruff>=0.9; extra == 'docs'
Provides-Extra: track
Requires-Dist: mlflow>=2.14; extra == 'track'
Provides-Extra: tune
Requires-Dist: optuna>=3.6; extra == 'tune'
Description-Content-Type: text/markdown

# pmlab

[![CI](https://github.com/ArtBreguez/polymarket-lab/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/ArtBreguez/polymarket-lab/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/pmlab)](https://pypi.org/project/pmlab/)
[![Python](https://img.shields.io/pypi/pyversions/pmlab)](https://pypi.org/project/pmlab/)
[![License: MIT](https://img.shields.io/pypi/l/pmlab)](https://github.com/ArtBreguez/polymarket-lab/blob/main/LICENSE)
[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://artbreguez.github.io/polymarket-lab/)

**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 `pmlab` command places an order. Only a `LiveBroker` you build yourself, with
> your own Polymarket credentials, does, and `dry_run=True` is 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_eval` trains only on rows from before the day it predicts.
  `build_panel` refuses a price observed at or after the decision it informs, and `check_no_leakage`
  raises when a feature gives the outcome away. [How the checks work](https://artbreguez.github.io/polymarket-lab/leakage/)
- **A failed gate cannot ship.** `ChampionManifest.publish` raises unless the gate says GO, and that
  is not configurable. Paper trading reads its allowed segments from `champion.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 reproduce` re-runs it and verifies the metrics come back.
  [Reproducibility](https://artbreguez.github.io/polymarket-lab/reproducibility/)
- **Stable.** From 1.0 pmlab follows [semantic versioning](https://semver.org). The public API (the
  names, the `pmlab` command 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](https://artbreguez.github.io/polymarket-lab/api-stability/)

## Install

```shell
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.

```python
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:

```bash
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](https://artbreguez.github.io/polymarket-lab/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: prints how to settle with `SettlementEngine`; it settles nothing |
| `pmlab version` | Print the version |

`pmlab --help` and `pmlab <command> --help` list every option. [Command line reference](https://artbreguez.github.io/polymarket-lab/cli/)

## Bring your own market family

A plugin is one class with four methods. Everything else is shared.

```python
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): ...
```

[Plugin authoring guide](https://artbreguez.github.io/polymarket-lab/plugin-authoring/)

## Documentation

Everything else lives on the [documentation site](https://artbreguez.github.io/polymarket-lab/).

| | |
|---|---|
| Start here | [Tutorial](https://artbreguez.github.io/polymarket-lab/tutorial/) · [Architecture](https://artbreguez.github.io/polymarket-lab/architecture/) · [Command line](https://artbreguez.github.io/polymarket-lab/cli/) · [Plugin authoring](https://artbreguez.github.io/polymarket-lab/plugin-authoring/) |
| Build models | [Data layer](https://artbreguez.github.io/polymarket-lab/data/) · [Features](https://artbreguez.github.io/polymarket-lab/features/) · [Modeling](https://artbreguez.github.io/polymarket-lab/modeling/) · [Tuning](https://artbreguez.github.io/polymarket-lab/tuning/) · [Costs](https://artbreguez.github.io/polymarket-lab/costs/) · [Position sizing](https://artbreguez.github.io/polymarket-lab/sizing/) |
| Run it | [Live trading](https://artbreguez.github.io/polymarket-lab/live-trading/) · [Drift monitoring](https://artbreguez.github.io/polymarket-lab/drift-monitoring/) · [Watchdog](https://artbreguez.github.io/polymarket-lab/watchdog/) · [Champion registry](https://artbreguez.github.io/polymarket-lab/registry/) |
| Reference | [API reference](https://artbreguez.github.io/polymarket-lab/reference/) · [Public API and stability](https://artbreguez.github.io/polymarket-lab/api-stability/) · [Deprecation policy](https://artbreguez.github.io/polymarket-lab/deprecation/) |
| Project | [Changelog](https://github.com/ArtBreguez/polymarket-lab/blob/main/CHANGELOG.md) · [Roadmap](https://github.com/ArtBreguez/polymarket-lab/blob/main/ROADMAP.md) · [Contributing](https://github.com/ArtBreguez/polymarket-lab/blob/main/CONTRIBUTING.md) · [Security](https://github.com/ArtBreguez/polymarket-lab/blob/main/SECURITY.md) |

## Development

```shell
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](https://github.com/ArtBreguez/polymarket-lab/blob/main/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](https://github.com/ArtBreguez/polymarket-lab/blob/main/LICENSE).
