pmquant 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+
21
+ - name: Install
22
+ run: pip install -e ".[dev]"
23
+
24
+ - name: Test
25
+ run: python -m pytest tests/ -q
26
+
27
+ - name: Build
28
+ run: |
29
+ python -m pip install --upgrade build
30
+ python -m build
31
+
32
+ - name: Publish to PyPI
33
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,17 @@
1
+ name: tests
2
+ on:
3
+ push:
4
+ pull_request:
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ python-version: ["3.10", "3.12"]
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: ${{ matrix.python-version }}
16
+ - run: pip install -e ".[dev]"
17
+ - run: pytest -q
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ .venv/
7
+ .pytest_cache/
8
+ .env
@@ -0,0 +1,40 @@
1
+ # Working with pmq (agent instructions)
2
+
3
+ pmq is a small Python library for Polymarket CLOB V2 with two layers.
4
+
5
+ ## Data layer (no credentials, safe anywhere)
6
+
7
+ ```python
8
+ import pmq
9
+ m = pmq.parse_market(pmq.get_market(slug)) # condition id + token ids
10
+ book = pmq.get_book(m["token_a"]) # REAL TIME, trade off this
11
+ bid, bid_sz, ask, ask_sz = pmq.best_bid_ask(book)
12
+ pmq.fee(price, shares) # taker $, crypto rate 0.07
13
+ ```
14
+
15
+ Never make live decisions from `get_tape` (data-api): it lags matching by 1
16
+ to 3 minutes. It is for offline scoring at least 5 minutes after close.
17
+
18
+ ## Execution layer (needs POLY_PRIVATE_KEY; treat as production)
19
+
20
+ Rules an agent MUST follow:
21
+
22
+ 1. Default to reading data, not trading. Only place orders when the user
23
+ explicitly asked for live execution in this session.
24
+ 2. Book only `fill.matched_shares` and `fill.matched_usd`. A falsy `Fill`
25
+ means nothing happened, whatever the request was.
26
+ 3. Wrap every order in `try/except OrderUncertain`; on that exception call
27
+ `executor.reconcile(condition_id)` and do not place new orders on that
28
+ market until it returns.
29
+ 4. Call `executor.require_collateral(stake)` before a session of trading.
30
+ If it reports 0 while the user says the account is funded, the
31
+ signature_type is wrong: the Polymarket app's default wallet needs
32
+ `signature_type=3` (deposit wallet).
33
+ 5. Budget with fees: a share at price p costs `p + pmq.fee(p, 1)`.
34
+ 6. Never print or log POLY_PRIVATE_KEY or any environment secret. Addresses
35
+ and builder codes are public; keys are not.
36
+ 7. FAK buys spend dollars (`buy_fak(token, price_cap, usd)`), FAK sells
37
+ spend shares. Amounts are rounded down internally to exchange accuracy.
38
+ 8. Builder attribution: the library defaults to the maintainer's
39
+ zero-commission builder code. Respect the user's choice if they set
40
+ `builder_code=None` or their own code; do not change it silently.
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-07-03)
4
+
5
+ First release.
6
+
7
+ * `pmq.data`: real-time books (with per-market exchange rules via
8
+ `book_meta`), gamma slug resolution with expired-market fallback,
9
+ market-agnostic `parse_market` (any binary outcomes, close time), settled
10
+ and book-inferred winners, offline trade tape, official per-category taker
11
+ fee formula.
12
+ * `pmq.executor.PolymarketExecutor`: fail-closed CLOB V2 execution. FAK
13
+ buys/sells through the market-order path, exchange-confirmed fills only,
14
+ `OrderUncertain` + `reconcile()` from get_trades, deposit-wallet
15
+ (POLY_1271) support, collateral fail-fast, builder-code default with
16
+ disclosure and opt-out, startup introspection of the installed
17
+ py-clob-client-v2.
18
+ * `pmq.mcp` (`pmq-mcp`): MCP server. Read tools always available
19
+ (find_markets, market, book, taker_fee, account tools); trading tools only
20
+ registered when the operator sets `PMQ_MCP_LIVE=1`, per-order cap via
21
+ `PMQ_MCP_MAX_USD`.
22
+ * `bot-template/`: market-agnostic bot engine (strategy owns `watchlist()`
23
+ and `decide()`), honest paper mode against real books, risk rails (budget
24
+ with fee headroom, poisoning, halts with exit code 42, disk-persisted
25
+ daily halt), systemd unit, phone dashboard.
pmquant-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 crp4222
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pmquant-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: pmquant
3
+ Version: 0.1.0
4
+ Summary: Fail-closed execution and market-data layer for Polymarket CLOB V2: local signing, confirmed fills only, fee-correct math, working deposit-wallet (POLY_1271) support.
5
+ Project-URL: Homepage, https://github.com/crp4222/pmq
6
+ Project-URL: Issues, https://github.com/crp4222/pmq/issues
7
+ Author: crp4222
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: builder-code,clob,clob-v2,deposit-wallet,execution,fak,market-order,orderbook,poly-1271,polymarket,prediction-markets,quant,trading
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Financial and Insurance Industry
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Office/Business :: Financial :: Investment
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: py-clob-client-v2<2,>=1.0.2
19
+ Provides-Extra: dev
20
+ Requires-Dist: mcp>=1.2; extra == 'dev'
21
+ Requires-Dist: pytest>=8; extra == 'dev'
22
+ Provides-Extra: mcp
23
+ Requires-Dist: mcp>=1.2; extra == 'mcp'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # pmq
27
+
28
+ Fail-closed execution and market data for **Polymarket CLOB V2**, in Python.
29
+ Local signing (your keys never leave your process), exchange-confirmed fills
30
+ only, fee-correct math, and deposit-wallet (`POLY_1271`) support that actually
31
+ works in production.
32
+
33
+ ```bash
34
+ pip install pmquant # distribution name pmquant, import name pmq
35
+ ```
36
+
37
+ (PyPI's similarity check reserves the bare name; the module you import is
38
+ `pmq`, same pattern as beautifulsoup4/bs4.)
39
+
40
+ As of 2026-07-03 this is, to our knowledge, the **only maintained Python
41
+ layer combining local CLOB V2 signing, an exchange-confirmed fill contract,
42
+ and working deposit-wallet (POLY_1271) auth**. That claim is dated and
43
+ falsifiable: the comparison table below names the alternatives and what each
44
+ does instead; open an issue if it goes stale.
45
+
46
+ ## Why this exists
47
+
48
+ Polymarket cut over to CLOB V2 on 2026-04-28. V1-signed orders are rejected in
49
+ production, the fee schedule is decided at match time, and the official client
50
+ examples leave several traps undocumented. Every line of pmq was paid for with
51
+ a real error in live trading:
52
+
53
+ * `invalid amounts, the market buy orders maker amount supports a max accuracy
54
+ of 2 decimals`: the CLOB treats FAK/FOK buys as **market orders**. pmq routes
55
+ them through the market-order builder with the correct rounding.
56
+ * `no orders found to match with FAK order` (HTTP 400, yet with an `orderID`):
57
+ a clean no-fill, not an error. pmq returns an empty `Fill` instead of crashing
58
+ or, worse, retrying blindly.
59
+ * CLOB shows `balance: 0` while your pUSD sits on-chain: the balance endpoint
60
+ ignores your `funder` parameter and derives the wallet from your EOA and
61
+ `signature_type`. Funds in the Polymarket app's default wallet (an ERC-1271
62
+ deposit wallet) are only visible with `signature_type=3`.
63
+
64
+ The full write-up with reproduction details: [docs/war-story.md](docs/war-story.md).
65
+
66
+ ## The contract: nothing is booked without exchange confirmation
67
+
68
+ | Situation | What pmq does |
69
+ |---|---|
70
+ | Response is a dict with `orderID`, not flagged failed | `Fill` with the **matched** size read from the response |
71
+ | Error dict on HTTP 200, string body, `success: false` | `Fill(rejected=True)`, zero booked |
72
+ | HTTP 4xx (incl. FAK no-match) | `Fill(rejected=True)`, zero booked |
73
+ | Timeout, 5xx, exception after send | raises `OrderUncertain`: the order MAY exist. Call `reconcile()` before trading that market again |
74
+ | Unparseable matched amounts | zero booked (fail closed) |
75
+
76
+ `reconcile(condition_id)` cancels anything resting, verifies nothing stayed
77
+ open, and returns `(shares, usd, fees)` from `get_trades`: the exchange truth,
78
+ not your hopes.
79
+
80
+ At startup pmq **introspects the installed py-clob-client-v2** against the API
81
+ surface it was verified on, and refuses to trade on drift instead of sending
82
+ orders through changed semantics.
83
+
84
+ ## Quickstart
85
+
86
+ Market data needs no keys:
87
+
88
+ ```python
89
+ import pmq
90
+
91
+ m = pmq.parse_market(pmq.get_market("btc-updown-15m-1783062000"))
92
+ book = pmq.get_book(m["token_a"])
93
+ bid, bid_sz, ask, ask_sz = pmq.best_bid_ask(book)
94
+ print(ask, pmq.band_ask_depth_usd(book, 0.90, 0.97))
95
+ print(pmq.fee(price=0.95, shares=100)) # taker fee in $, crypto rate
96
+ ```
97
+
98
+ Execution (reads `POLY_PRIVATE_KEY`, `POLY_FUNDER`, `POLY_SIG_TYPE` from the
99
+ environment):
100
+
101
+ ```python
102
+ from pmq import PolymarketExecutor, OrderUncertain
103
+
104
+ ex = PolymarketExecutor() # signature_type=3 for the app's deposit wallet
105
+ ex.require_collateral(5.0) # fail fast, with a diagnostic that names sig_type
106
+
107
+ try:
108
+ fill = ex.buy_fak(token_id=m["token_a"], price_cap=0.95, usd=5.00)
109
+ except OrderUncertain:
110
+ ex.reconcile(m["condition_id"], m["token_a"]) # exchange truth before anything else
111
+ else:
112
+ if fill: # book ONLY what matched
113
+ print(fill.matched_shares, "shares at", fill.price, "order", fill.order_id)
114
+ ```
115
+
116
+ `sell_fak` and `limit_gtc` follow the same contract. The buy path has carried
117
+ live volume; treat the sell path as following the same documented semantics
118
+ with less battle time.
119
+
120
+ ## The signature_type table nobody gives you
121
+
122
+ | `signature_type` | Wallet | When it is yours |
123
+ |---|---|---|
124
+ | 0 | the EOA itself | you trade from a bare private key |
125
+ | 1 | `POLY_PROXY` | email/Magic accounts (legacy) |
126
+ | 2 | `POLY_GNOSIS_SAFE` | browser-wallet proxy |
127
+ | 3 | `POLY_1271` deposit wallet | **the Polymarket app's default wallet** |
128
+
129
+ If `collateral()` returns 0 while the funds are visible on-chain on your funder
130
+ address, your `signature_type` is wrong. Debug trick: `eth_call` `owner()`
131
+ (`0x8da5cb5b`) on the funder; if it returns your EOA and the wallet bytecode is
132
+ an ERC-1167 proxy, you want `signature_type=3`.
133
+
134
+ ## Comparison (2026-07-03, factual)
135
+
136
+ | | pmq | py-clob-client-v2 (official) | pmxt | NautilusTrader | caiovicentino MCP |
137
+ |---|---|---|---|---|---|
138
+ | CLOB V2 signing | yes, local | yes, local | writes via its hosted backend | yes, local | V1 only (rejected in prod since 2026-04-28) |
139
+ | Confirmed-fill contract | yes (core design) | no (raw responses) | n/a | engine-level | no |
140
+ | Deposit wallet / POLY_1271 | yes, production-proven | open issues (#70 and others) | n/a | untested claim | no |
141
+ | Fee math | official per-category formula | fee at match, no helper | via backend | fee model | fee-blind |
142
+ | Reconciliation helper | yes | no | n/a | engine-level | no |
143
+ | Footprint | one small lib | one small lib | multi-venue platform | full trading framework | MCP server |
144
+
145
+ NautilusTrader is excellent if you want a full framework; pmq is the small
146
+ library you embed in your own bot. pmxt is convenient if you accept routing
147
+ writes through their backend; pmq exists for self-custody.
148
+
149
+ ## Builder code disclosure
150
+
151
+ pmq ships with the maintainer's public Polymarket **builder code** as default
152
+ attribution inside signed orders (`pmq.executor.DEFAULT_BUILDER_CODE`). Its
153
+ commission is set to **0/0: it never adds any fee to your orders**. Attribution
154
+ feeds Polymarket's builder program and funds this project at zero cost to you.
155
+
156
+ Opt out or replace it, one line either way:
157
+
158
+ ```python
159
+ PolymarketExecutor(builder_code=None) # no attribution
160
+ PolymarketExecutor(builder_code="0xYOURS...") # your own code
161
+ ```
162
+
163
+ or set the `POLY_BUILDER_CODE` environment variable. (Same model as
164
+ JKorf/Polymarket.Net; the official client defaults to zero attribution.)
165
+
166
+ ## MCP server (agents)
167
+
168
+ `pip install "pmquant[mcp]"` then run `pmq-mcp` (stdio). Read tools (market,
169
+ book, taker_fee, account_collateral, account_trades) always exist. Trading
170
+ tools (`fak_buy`, `fak_sell`, `cancel_and_reconcile`) are **only registered
171
+ when the operator sets `PMQ_MCP_LIVE=1`** in the server environment: an
172
+ agent cannot talk its way past a tool that was never created. Every order is
173
+ capped per call by `PMQ_MCP_MAX_USD` (default 10).
174
+
175
+ ```json
176
+ {
177
+ "mcpServers": {
178
+ "pmq": {
179
+ "command": "pmq-mcp",
180
+ "env": { "POLY_PRIVATE_KEY": "...", "POLY_FUNDER": "0x...", "POLY_SIG_TYPE": "3" }
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ Leave the `POLY_*` variables out entirely for a read-only market-data server.
187
+
188
+ ## Bot template
189
+
190
+ [bot-template/](bot-template/) is a complete bot minus the strategy, for ANY
191
+ market (politics, sports, crypto, culture): paper mode against real books
192
+ with real fees, per-market budgets with fee headroom, poisoned-market
193
+ reconciliation, consecutive-failure halt, disk-persisted daily loss halt, a
194
+ systemd unit with `RestartPreventExitStatus=42` so halts stay halted, and a
195
+ lightweight phone dashboard. You implement `watchlist()` and `decide()`; the
196
+ shipped demo strategy is an API illustration meant to be replaced.
197
+
198
+ ## Security posture
199
+
200
+ * Keys are read from the environment, used to instantiate the signer, and
201
+ never logged. No custody, no backend, no telemetry, zero network calls
202
+ besides Polymarket endpoints.
203
+ * Beware of the documented wave of fake "polymarket bot" repositories that
204
+ steal private keys. Read the source: pmq is small on purpose.
205
+ * Fund the trading wallet with what you can afford to lose. Nothing here is
206
+ financial advice; prediction-market access is restricted in some
207
+ jurisdictions and compliance is on you.
208
+
209
+ ## License
210
+
211
+ MIT
@@ -0,0 +1,186 @@
1
+ # pmq
2
+
3
+ Fail-closed execution and market data for **Polymarket CLOB V2**, in Python.
4
+ Local signing (your keys never leave your process), exchange-confirmed fills
5
+ only, fee-correct math, and deposit-wallet (`POLY_1271`) support that actually
6
+ works in production.
7
+
8
+ ```bash
9
+ pip install pmquant # distribution name pmquant, import name pmq
10
+ ```
11
+
12
+ (PyPI's similarity check reserves the bare name; the module you import is
13
+ `pmq`, same pattern as beautifulsoup4/bs4.)
14
+
15
+ As of 2026-07-03 this is, to our knowledge, the **only maintained Python
16
+ layer combining local CLOB V2 signing, an exchange-confirmed fill contract,
17
+ and working deposit-wallet (POLY_1271) auth**. That claim is dated and
18
+ falsifiable: the comparison table below names the alternatives and what each
19
+ does instead; open an issue if it goes stale.
20
+
21
+ ## Why this exists
22
+
23
+ Polymarket cut over to CLOB V2 on 2026-04-28. V1-signed orders are rejected in
24
+ production, the fee schedule is decided at match time, and the official client
25
+ examples leave several traps undocumented. Every line of pmq was paid for with
26
+ a real error in live trading:
27
+
28
+ * `invalid amounts, the market buy orders maker amount supports a max accuracy
29
+ of 2 decimals`: the CLOB treats FAK/FOK buys as **market orders**. pmq routes
30
+ them through the market-order builder with the correct rounding.
31
+ * `no orders found to match with FAK order` (HTTP 400, yet with an `orderID`):
32
+ a clean no-fill, not an error. pmq returns an empty `Fill` instead of crashing
33
+ or, worse, retrying blindly.
34
+ * CLOB shows `balance: 0` while your pUSD sits on-chain: the balance endpoint
35
+ ignores your `funder` parameter and derives the wallet from your EOA and
36
+ `signature_type`. Funds in the Polymarket app's default wallet (an ERC-1271
37
+ deposit wallet) are only visible with `signature_type=3`.
38
+
39
+ The full write-up with reproduction details: [docs/war-story.md](docs/war-story.md).
40
+
41
+ ## The contract: nothing is booked without exchange confirmation
42
+
43
+ | Situation | What pmq does |
44
+ |---|---|
45
+ | Response is a dict with `orderID`, not flagged failed | `Fill` with the **matched** size read from the response |
46
+ | Error dict on HTTP 200, string body, `success: false` | `Fill(rejected=True)`, zero booked |
47
+ | HTTP 4xx (incl. FAK no-match) | `Fill(rejected=True)`, zero booked |
48
+ | Timeout, 5xx, exception after send | raises `OrderUncertain`: the order MAY exist. Call `reconcile()` before trading that market again |
49
+ | Unparseable matched amounts | zero booked (fail closed) |
50
+
51
+ `reconcile(condition_id)` cancels anything resting, verifies nothing stayed
52
+ open, and returns `(shares, usd, fees)` from `get_trades`: the exchange truth,
53
+ not your hopes.
54
+
55
+ At startup pmq **introspects the installed py-clob-client-v2** against the API
56
+ surface it was verified on, and refuses to trade on drift instead of sending
57
+ orders through changed semantics.
58
+
59
+ ## Quickstart
60
+
61
+ Market data needs no keys:
62
+
63
+ ```python
64
+ import pmq
65
+
66
+ m = pmq.parse_market(pmq.get_market("btc-updown-15m-1783062000"))
67
+ book = pmq.get_book(m["token_a"])
68
+ bid, bid_sz, ask, ask_sz = pmq.best_bid_ask(book)
69
+ print(ask, pmq.band_ask_depth_usd(book, 0.90, 0.97))
70
+ print(pmq.fee(price=0.95, shares=100)) # taker fee in $, crypto rate
71
+ ```
72
+
73
+ Execution (reads `POLY_PRIVATE_KEY`, `POLY_FUNDER`, `POLY_SIG_TYPE` from the
74
+ environment):
75
+
76
+ ```python
77
+ from pmq import PolymarketExecutor, OrderUncertain
78
+
79
+ ex = PolymarketExecutor() # signature_type=3 for the app's deposit wallet
80
+ ex.require_collateral(5.0) # fail fast, with a diagnostic that names sig_type
81
+
82
+ try:
83
+ fill = ex.buy_fak(token_id=m["token_a"], price_cap=0.95, usd=5.00)
84
+ except OrderUncertain:
85
+ ex.reconcile(m["condition_id"], m["token_a"]) # exchange truth before anything else
86
+ else:
87
+ if fill: # book ONLY what matched
88
+ print(fill.matched_shares, "shares at", fill.price, "order", fill.order_id)
89
+ ```
90
+
91
+ `sell_fak` and `limit_gtc` follow the same contract. The buy path has carried
92
+ live volume; treat the sell path as following the same documented semantics
93
+ with less battle time.
94
+
95
+ ## The signature_type table nobody gives you
96
+
97
+ | `signature_type` | Wallet | When it is yours |
98
+ |---|---|---|
99
+ | 0 | the EOA itself | you trade from a bare private key |
100
+ | 1 | `POLY_PROXY` | email/Magic accounts (legacy) |
101
+ | 2 | `POLY_GNOSIS_SAFE` | browser-wallet proxy |
102
+ | 3 | `POLY_1271` deposit wallet | **the Polymarket app's default wallet** |
103
+
104
+ If `collateral()` returns 0 while the funds are visible on-chain on your funder
105
+ address, your `signature_type` is wrong. Debug trick: `eth_call` `owner()`
106
+ (`0x8da5cb5b`) on the funder; if it returns your EOA and the wallet bytecode is
107
+ an ERC-1167 proxy, you want `signature_type=3`.
108
+
109
+ ## Comparison (2026-07-03, factual)
110
+
111
+ | | pmq | py-clob-client-v2 (official) | pmxt | NautilusTrader | caiovicentino MCP |
112
+ |---|---|---|---|---|---|
113
+ | CLOB V2 signing | yes, local | yes, local | writes via its hosted backend | yes, local | V1 only (rejected in prod since 2026-04-28) |
114
+ | Confirmed-fill contract | yes (core design) | no (raw responses) | n/a | engine-level | no |
115
+ | Deposit wallet / POLY_1271 | yes, production-proven | open issues (#70 and others) | n/a | untested claim | no |
116
+ | Fee math | official per-category formula | fee at match, no helper | via backend | fee model | fee-blind |
117
+ | Reconciliation helper | yes | no | n/a | engine-level | no |
118
+ | Footprint | one small lib | one small lib | multi-venue platform | full trading framework | MCP server |
119
+
120
+ NautilusTrader is excellent if you want a full framework; pmq is the small
121
+ library you embed in your own bot. pmxt is convenient if you accept routing
122
+ writes through their backend; pmq exists for self-custody.
123
+
124
+ ## Builder code disclosure
125
+
126
+ pmq ships with the maintainer's public Polymarket **builder code** as default
127
+ attribution inside signed orders (`pmq.executor.DEFAULT_BUILDER_CODE`). Its
128
+ commission is set to **0/0: it never adds any fee to your orders**. Attribution
129
+ feeds Polymarket's builder program and funds this project at zero cost to you.
130
+
131
+ Opt out or replace it, one line either way:
132
+
133
+ ```python
134
+ PolymarketExecutor(builder_code=None) # no attribution
135
+ PolymarketExecutor(builder_code="0xYOURS...") # your own code
136
+ ```
137
+
138
+ or set the `POLY_BUILDER_CODE` environment variable. (Same model as
139
+ JKorf/Polymarket.Net; the official client defaults to zero attribution.)
140
+
141
+ ## MCP server (agents)
142
+
143
+ `pip install "pmquant[mcp]"` then run `pmq-mcp` (stdio). Read tools (market,
144
+ book, taker_fee, account_collateral, account_trades) always exist. Trading
145
+ tools (`fak_buy`, `fak_sell`, `cancel_and_reconcile`) are **only registered
146
+ when the operator sets `PMQ_MCP_LIVE=1`** in the server environment: an
147
+ agent cannot talk its way past a tool that was never created. Every order is
148
+ capped per call by `PMQ_MCP_MAX_USD` (default 10).
149
+
150
+ ```json
151
+ {
152
+ "mcpServers": {
153
+ "pmq": {
154
+ "command": "pmq-mcp",
155
+ "env": { "POLY_PRIVATE_KEY": "...", "POLY_FUNDER": "0x...", "POLY_SIG_TYPE": "3" }
156
+ }
157
+ }
158
+ }
159
+ ```
160
+
161
+ Leave the `POLY_*` variables out entirely for a read-only market-data server.
162
+
163
+ ## Bot template
164
+
165
+ [bot-template/](bot-template/) is a complete bot minus the strategy, for ANY
166
+ market (politics, sports, crypto, culture): paper mode against real books
167
+ with real fees, per-market budgets with fee headroom, poisoned-market
168
+ reconciliation, consecutive-failure halt, disk-persisted daily loss halt, a
169
+ systemd unit with `RestartPreventExitStatus=42` so halts stay halted, and a
170
+ lightweight phone dashboard. You implement `watchlist()` and `decide()`; the
171
+ shipped demo strategy is an API illustration meant to be replaced.
172
+
173
+ ## Security posture
174
+
175
+ * Keys are read from the environment, used to instantiate the signer, and
176
+ never logged. No custody, no backend, no telemetry, zero network calls
177
+ besides Polymarket endpoints.
178
+ * Beware of the documented wave of fake "polymarket bot" repositories that
179
+ steal private keys. Read the source: pmq is small on purpose.
180
+ * Fund the trading wallet with what you can afford to lose. Nothing here is
181
+ financial advice; prediction-market access is restricted in some
182
+ jurisdictions and compliance is on you.
183
+
184
+ ## License
185
+
186
+ MIT
@@ -0,0 +1,65 @@
1
+ # pmq bot template
2
+
3
+ A complete Polymarket bot minus the strategy, for ANY market: politics,
4
+ sports, crypto, culture. The engine (`bot.py`) is market-agnostic: it tracks
5
+ whatever slugs your `strategy.py` returns, executes your intents through the
6
+ pmq fail-closed layer, scores at resolution, and enforces the risk rails.
7
+ You write two small functions.
8
+
9
+ ## What is deliberately NOT here
10
+
11
+ A trading edge, or even a direction to look for one. The shipped demo
12
+ strategy buys 2$ of any 0.98+ favorite once per market: an API illustration
13
+ with negative expectancy, present only so paper mode has something to show.
14
+ The engine is the part that is the same for everyone; the strategy is the
15
+ part that is not.
16
+
17
+ ## Quickstart (paper, safe anywhere)
18
+
19
+ ```bash
20
+ pip install pmquant
21
+ BOT_SLUGS="<any-gamma-market-slug>" python bot.py 24
22
+ ```
23
+
24
+ Fills land in `bot_runs/fills.csv`, resolved markets in
25
+ `bot_runs/windows.csv`, and `python dash/bot_dash.py` serves a phone
26
+ dashboard on port 8080 reading those files.
27
+
28
+ ## The two functions you own (`strategy.py`)
29
+
30
+ * `watchlist()` returns the gamma slugs to track right now. Static from an
31
+ env var, discovered via the gamma API, or generated for recurring
32
+ markets.
33
+ * `decide(pm, book_a, book_b, remaining_usd, side_held, state)` returns
34
+ `None` or `(side_key, price_cap, usd)`. You see real-time books and the
35
+ market metadata (`pm['outcome_a']`, `pm['end_ts']`, ...); the engine
36
+ enforces budget with fee headroom, one side per market, poisoning,
37
+ reconciliation and halts. A buggy strategy can lose its stake; it cannot
38
+ un-guard the execution.
39
+
40
+ ## The rails (engine-enforced, whatever your strategy does)
41
+
42
+ * Paper mode by default; live requires `BOT_MODE=live` AND `LIVE=1`.
43
+ * Nothing booked without exchange confirmation; unknown outcomes poison the
44
+ market until reconciled from `get_trades`.
45
+ * `BOT_MAX_CONSEC_FAILS` failed buys in a row exit with code 42, and the
46
+ systemd unit's `RestartPreventExitStatus=42` keeps that halt halted.
47
+ * Daily realized loss under `BOT_DAILY_HALT` stops entries; in live it is
48
+ persisted to disk so a same-day restart stays stopped.
49
+ * Collateral checked before the first live order (deposit wallets need
50
+ `POLY_SIG_TYPE=3`).
51
+
52
+ ## Env reference
53
+
54
+ | Var | Default | Meaning |
55
+ |---|---|---|
56
+ | BOT_MODE | paper | paper or live (live also needs LIVE=1) |
57
+ | BOT_SLUGS | empty | comma-separated slugs for the demo watchlist |
58
+ | BOT_STAKE | 5 | $ budget per market, fees included |
59
+ | BOT_DAILY_HALT | -25 | UTC-day realized loss that halts |
60
+ | BOT_POLL | 2.5 | seconds between polls |
61
+ | BOT_SCORE_POLL | 60 | seconds between resolution checks |
62
+ | BOT_FEE_CATEGORY | crypto | fee schedule for budgeting and scoring |
63
+ | BOT_OUT_DIR | ./bot_runs | CSV output directory |
64
+ | BOT_MAX_CONSEC_FAILS | 10 | failed buys in a row before exit 42 |
65
+ | BOT_MAX_TRACK_HOURS | 48 | drop unresolved markets after this long |