pmquant 0.4.1__tar.gz → 0.4.2__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.
- {pmquant-0.4.1 → pmquant-0.4.2}/CHANGELOG.md +9 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/PKG-INFO +1 -1
- {pmquant-0.4.1 → pmquant-0.4.2}/pyproject.toml +1 -1
- {pmquant-0.4.1 → pmquant-0.4.2}/server.json +1 -1
- {pmquant-0.4.1 → pmquant-0.4.2}/src/pmq/__init__.py +1 -1
- {pmquant-0.4.1 → pmquant-0.4.2}/src/pmq/executor.py +14 -2
- {pmquant-0.4.1 → pmquant-0.4.2}/tests/test_executor.py +17 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/.github/workflows/canary.yml +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/.github/workflows/publish.yml +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/.github/workflows/test.yml +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/.gitignore +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/AGENTS.md +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/CLAUDE.md +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/CONTRIBUTING.md +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/LICENSE +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/README.md +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/SECURITY.md +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/bot-template/README.md +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/bot-template/bot.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/bot-template/dash/bot_dash.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/bot-template/dash/dash.html +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/bot-template/pmq-bot.service +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/bot-template/strategy.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/docs/assets/pmq-doctor.svg +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/docs/recipes.md +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/docs/rounding-study.md +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/docs/war-story.md +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/examples/fak_buy_guarded.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/examples/read_market.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/llms.txt +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/src/pmq/data.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/src/pmq/doctor.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/src/pmq/exceptions.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/src/pmq/mcp.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/src/pmq/py.typed +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/tests/test_canary_live.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/tests/test_data.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/tests/test_doctor.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/tests/test_mcp.py +0 -0
- {pmquant-0.4.1 → pmquant-0.4.2}/tests/test_template_engine.py +0 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.2 (2026-07-03)
|
|
4
|
+
|
|
5
|
+
* Fix: `buy_fak`/`sell_fak` cent-rounding used `int(x * 100) / 100`, which
|
|
6
|
+
floors a binary-drifted float (`16.90` stored as `16.8999…` became
|
|
7
|
+
`16.89`), silently shaving a cent off intended-clean amounts. Replaced with
|
|
8
|
+
a `Decimal`-based `_floor_cents` that rounds down without the drift, keeping
|
|
9
|
+
the never-exceed-budget contract. Matches the behavior documented in
|
|
10
|
+
docs/rounding-study.md.
|
|
11
|
+
|
|
3
12
|
## 0.4.1 (2026-07-03)
|
|
4
13
|
|
|
5
14
|
* Introspection guard now also verifies `OrderArgsV2` fields (including
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pmquant
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
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
5
|
Project-URL: Homepage, https://github.com/crp4222/pmq
|
|
6
6
|
Project-URL: Issues, https://github.com/crp4222/pmq/issues
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pmquant"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.2"
|
|
8
8
|
description = "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."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.crp4222/pmq",
|
|
4
4
|
"title": "pmq (Polymarket CLOB V2)",
|
|
5
|
-
"description": "Fail-closed Polymarket CLOB V2 data and execution
|
|
5
|
+
"description": "Fail-closed Polymarket CLOB V2 data and execution; trading tools gated behind PMQ_MCP_LIVE=1.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "https://github.com/crp4222/pmq",
|
|
8
8
|
"source": "github"
|
|
@@ -25,7 +25,7 @@ from .data import (
|
|
|
25
25
|
)
|
|
26
26
|
from .exceptions import IntrospectionMismatch, OrderUncertain, PmqError
|
|
27
27
|
|
|
28
|
-
__version__ = "0.4.
|
|
28
|
+
__version__ = "0.4.2"
|
|
29
29
|
__all__ = [
|
|
30
30
|
"FEE_RATES", "band_ask_depth_usd", "best_bid_ask", "book_inferred_winner",
|
|
31
31
|
"book_meta", "event_markets", "fee", "get_book", "get_market", "get_tape",
|
|
@@ -36,6 +36,7 @@ import logging
|
|
|
36
36
|
import os
|
|
37
37
|
import re
|
|
38
38
|
from dataclasses import dataclass, field
|
|
39
|
+
from decimal import ROUND_DOWN, Decimal
|
|
39
40
|
from typing import Any
|
|
40
41
|
|
|
41
42
|
from .data import FEE_RATES, fee
|
|
@@ -55,6 +56,17 @@ BYTES32_RE = re.compile(r"0x[0-9a-fA-F]{64}")
|
|
|
55
56
|
DEFAULT_BUILDER_CODE = "0x4b22812cf929165a247b575eb417a3b6c9e3c12e96f0159c4d0ad39f78d17371"
|
|
56
57
|
_UNSET: Any = object()
|
|
57
58
|
|
|
59
|
+
_CENT = Decimal("0.01")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _floor_cents(x: float) -> float:
|
|
63
|
+
"""Round DOWN to the cent without the binary-float drift that makes
|
|
64
|
+
``int(16.90 * 100) / 100`` return ``16.89``. ``str(x)`` yields the shortest
|
|
65
|
+
decimal repr, so ``16.90`` stays ``16.90`` while a genuine ``16.907`` still
|
|
66
|
+
floors to ``16.90``. Rounding down preserves the never-exceed-budget
|
|
67
|
+
contract for buys and the never-oversell contract for sells."""
|
|
68
|
+
return float(Decimal(str(x)).quantize(_CENT, rounding=ROUND_DOWN))
|
|
69
|
+
|
|
58
70
|
|
|
59
71
|
@dataclass
|
|
60
72
|
class Fill:
|
|
@@ -264,7 +276,7 @@ class PolymarketExecutor:
|
|
|
264
276
|
killed by the exchange; nothing ever rests. Returns a :class:`Fill`;
|
|
265
277
|
book ONLY ``fill.matched_shares`` and ``fill.matched_usd``.
|
|
266
278
|
Raises :class:`OrderUncertain` when the outcome is unknown."""
|
|
267
|
-
usd =
|
|
279
|
+
usd = _floor_cents(usd)
|
|
268
280
|
if usd <= 0:
|
|
269
281
|
return Fill(rejected=True, error="usd amount rounds to zero")
|
|
270
282
|
return self._market_order(token_id, usd, "BUY", price_cap)
|
|
@@ -274,7 +286,7 @@ class PolymarketExecutor:
|
|
|
274
286
|
``price_floor``. Same confirmation contract as :meth:`buy_fak`.
|
|
275
287
|
The buy path has carried live volume; the sell path follows the same
|
|
276
288
|
documented semantics but flag it as less battle-tested."""
|
|
277
|
-
shares =
|
|
289
|
+
shares = _floor_cents(shares)
|
|
278
290
|
if shares <= 0:
|
|
279
291
|
return Fill(rejected=True, error="share amount rounds to zero")
|
|
280
292
|
return self._market_order(token_id, shares, "SELL", price_floor)
|
|
@@ -114,6 +114,23 @@ def test_buy_amount_rounds_down_to_cent():
|
|
|
114
114
|
assert fc.calls[0][1].amount == 4.99
|
|
115
115
|
|
|
116
116
|
|
|
117
|
+
def test_cent_rounding_has_no_binary_drift():
|
|
118
|
+
from pmq.executor import _floor_cents
|
|
119
|
+
# int(x*100)/100 would return one cent low here; _floor_cents must not.
|
|
120
|
+
assert _floor_cents(16.90) == 16.90
|
|
121
|
+
assert _floor_cents(33.30) == 33.30
|
|
122
|
+
assert _floor_cents(66.60) == 66.60
|
|
123
|
+
# genuine sub-cent values still floor
|
|
124
|
+
assert _floor_cents(5.007) == 5.00
|
|
125
|
+
assert _floor_cents(4.999) == 4.99
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_buy_fak_sends_intended_cents_not_drifted():
|
|
129
|
+
fc = FakeClient(market_resp={"orderID": "0x1", "makingAmount": "0", "takingAmount": "0"})
|
|
130
|
+
make(fc).buy_fak("tok", 0.97, 16.90)
|
|
131
|
+
assert fc.calls[0][1].amount == 16.90 # not 16.89
|
|
132
|
+
|
|
133
|
+
|
|
117
134
|
def test_zero_amount_never_reaches_the_wire():
|
|
118
135
|
fc = FakeClient()
|
|
119
136
|
f = make(fc).buy_fak("tok", 0.97, 0.004)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|