pmquant 0.4.2__tar.gz → 0.4.4__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.4/.githooks/pre-push +10 -0
- pmquant-0.4.4/.github/dependabot.yml +13 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/.github/workflows/canary.yml +8 -4
- pmquant-0.4.4/.github/workflows/codeql.yml +21 -0
- pmquant-0.4.4/.github/workflows/mcp-publish.yml +35 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/.github/workflows/publish.yml +6 -3
- pmquant-0.4.4/.github/workflows/scorecard.yml +28 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/.github/workflows/test.yml +5 -2
- {pmquant-0.4.2 → pmquant-0.4.4}/.gitignore +1 -0
- pmquant-0.4.4/.hypothesis/.gitignore +9 -0
- pmquant-0.4.4/.hypothesis/constants/07a2a0eac57d1dd0 +4 -0
- pmquant-0.4.4/.hypothesis/constants/3687cdf4cf8f7af3 +4 -0
- pmquant-0.4.4/.hypothesis/constants/6c9ffb0a1efc27b6 +4 -0
- pmquant-0.4.4/.hypothesis/constants/855d9c2e5b4693f1 +4 -0
- pmquant-0.4.4/.hypothesis/constants/ef909bf87e6ac33f +4 -0
- pmquant-0.4.4/.hypothesis/unicode_data/15.0.0/charmap.json.gz +0 -0
- pmquant-0.4.4/.hypothesis/unicode_data/15.0.0/codec-utf-8.json.gz +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/CHANGELOG.md +50 -0
- pmquant-0.4.4/CLAUDE.md +80 -0
- pmquant-0.4.4/CONTRIBUTING.md +80 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/PKG-INFO +70 -12
- {pmquant-0.4.2 → pmquant-0.4.4}/README.md +68 -11
- {pmquant-0.4.2 → pmquant-0.4.4}/SECURITY.md +18 -6
- pmquant-0.4.4/bot-template/bot.py +354 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/bot-template/dash/bot_dash.py +19 -7
- {pmquant-0.4.2 → pmquant-0.4.4}/llms.txt +6 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/pyproject.toml +2 -2
- {pmquant-0.4.2 → pmquant-0.4.4}/server.json +3 -3
- {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/__init__.py +1 -1
- {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/doctor.py +88 -58
- {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/executor.py +41 -12
- {pmquant-0.4.2 → pmquant-0.4.4}/tests/test_canary_live.py +34 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/tests/test_executor.py +53 -0
- pmquant-0.4.4/tests/test_fill_fuzz.py +118 -0
- pmquant-0.4.4/tests/test_template_engine.py +398 -0
- pmquant-0.4.2/CLAUDE.md +0 -52
- pmquant-0.4.2/CONTRIBUTING.md +0 -52
- pmquant-0.4.2/bot-template/bot.py +0 -305
- pmquant-0.4.2/tests/test_template_engine.py +0 -82
- {pmquant-0.4.2 → pmquant-0.4.4}/AGENTS.md +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/LICENSE +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/bot-template/README.md +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/bot-template/dash/dash.html +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/bot-template/pmq-bot.service +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/bot-template/strategy.py +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/docs/assets/pmq-doctor.svg +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/docs/recipes.md +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/docs/rounding-study.md +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/docs/war-story.md +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/examples/fak_buy_guarded.py +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/examples/read_market.py +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/data.py +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/exceptions.py +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/mcp.py +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/py.typed +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/tests/test_data.py +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/tests/test_doctor.py +0 -0
- {pmquant-0.4.2 → pmquant-0.4.4}/tests/test_mcp.py +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# pmq pre-push guard: refuse to push red. CI is the backstop; this catches
|
|
3
|
+
# it before the remote does. Bypass knowingly with --no-verify.
|
|
4
|
+
set -e
|
|
5
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
6
|
+
PY=./.venv/bin/python
|
|
7
|
+
[ -x "$PY" ] || PY=python3
|
|
8
|
+
$PY -m ruff check . || { echo "pre-push: ruff rouge"; exit 1; }
|
|
9
|
+
$PY -m mypy || { echo "pre-push: mypy rouge"; exit 1; }
|
|
10
|
+
$PY -m pytest -q || { echo "pre-push: tests rouges"; exit 1; }
|
|
@@ -12,8 +12,8 @@ jobs:
|
|
|
12
12
|
canary:
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
16
|
-
- uses: actions/setup-python@v6
|
|
15
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
16
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
|
17
17
|
with:
|
|
18
18
|
python-version: "3.12"
|
|
19
19
|
- run: pip install -e ".[dev]"
|
|
@@ -21,6 +21,10 @@ jobs:
|
|
|
21
21
|
env:
|
|
22
22
|
PMQ_CANARY: "1"
|
|
23
23
|
run: pytest tests/test_canary_live.py -q
|
|
24
|
+
- name: Dependency vulnerability audit
|
|
25
|
+
run: |
|
|
26
|
+
pip install pip-audit
|
|
27
|
+
pip-audit --skip-editable
|
|
24
28
|
- name: Open an issue if Polymarket drifted
|
|
25
29
|
if: failure()
|
|
26
30
|
env:
|
|
@@ -29,6 +33,6 @@ jobs:
|
|
|
29
33
|
existing=$(gh issue list --label canary --state open --json number --jq length)
|
|
30
34
|
if [ "$existing" = "0" ]; then
|
|
31
35
|
gh issue create --label canary \
|
|
32
|
-
--title "Canary:
|
|
33
|
-
--body "The weekly
|
|
36
|
+
--title "Canary failed: endpoint drift or vulnerable dependency ($(date -u +%F))" \
|
|
37
|
+
--body "The weekly canary failed: an endpoint shape or the installed py-clob-client-v2 surface no longer matches what pmq was verified against, the egress allowlist saw a foreign host, or pip-audit found a vulnerable dependency. See the failed run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
34
38
|
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: codeql
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "41 7 * * 1"
|
|
8
|
+
|
|
9
|
+
permissions: read-all
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
analyze:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
security-events: write
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
18
|
+
- uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
|
|
19
|
+
with:
|
|
20
|
+
languages: python
|
|
21
|
+
- uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: mcp-publish
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write # OIDC login to the MCP registry
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
16
|
+
- name: Fetch mcp-publisher
|
|
17
|
+
env:
|
|
18
|
+
GH_TOKEN: ${{ github.token }}
|
|
19
|
+
run: |
|
|
20
|
+
gh release download --repo modelcontextprotocol/registry \
|
|
21
|
+
--pattern "mcp-publisher_*linux_amd64.tar.gz" --output mp.tgz
|
|
22
|
+
tar -xzf mp.tgz
|
|
23
|
+
./mcp-publisher --version
|
|
24
|
+
- name: Wait for the version to exist on PyPI
|
|
25
|
+
run: |
|
|
26
|
+
V=$(python3 -c "import json; print(json.load(open('server.json'))['version'])")
|
|
27
|
+
for i in $(seq 1 20); do
|
|
28
|
+
curl -s "https://pypi.org/pypi/pmquant/$V/json" | grep -q '"version"' && exit 0
|
|
29
|
+
echo "PyPI does not serve $V yet ($i/20)"; sleep 30
|
|
30
|
+
done
|
|
31
|
+
echo "giving up: registry would reject an unpublished version"; exit 1
|
|
32
|
+
- name: Publish server.json to the MCP registry
|
|
33
|
+
run: |
|
|
34
|
+
./mcp-publisher login github-oidc
|
|
35
|
+
./mcp-publisher publish
|
|
@@ -5,16 +5,19 @@ on:
|
|
|
5
5
|
types: [published]
|
|
6
6
|
workflow_dispatch:
|
|
7
7
|
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
8
11
|
jobs:
|
|
9
12
|
publish:
|
|
10
13
|
runs-on: ubuntu-latest
|
|
11
14
|
permissions:
|
|
12
15
|
id-token: write
|
|
13
16
|
steps:
|
|
14
|
-
- uses: actions/checkout@
|
|
17
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
15
18
|
|
|
16
19
|
- name: Set up Python
|
|
17
|
-
uses: actions/setup-python@v6
|
|
20
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
|
18
21
|
with:
|
|
19
22
|
python-version: "3.12"
|
|
20
23
|
|
|
@@ -41,4 +44,4 @@ jobs:
|
|
|
41
44
|
python -m build
|
|
42
45
|
|
|
43
46
|
- name: Publish to PyPI
|
|
44
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
47
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: scorecard
|
|
2
|
+
on:
|
|
3
|
+
schedule:
|
|
4
|
+
- cron: "23 7 * * 1" # weekly, monday 07:23 UTC
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions: read-all
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
analysis:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
security-events: write # SARIF upload
|
|
16
|
+
id-token: write # signed publication to scorecard.dev
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
19
|
+
with:
|
|
20
|
+
persist-credentials: false
|
|
21
|
+
- uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
|
22
|
+
with:
|
|
23
|
+
results_file: results.sarif
|
|
24
|
+
results_format: sarif
|
|
25
|
+
publish_results: true
|
|
26
|
+
- uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
|
|
27
|
+
with:
|
|
28
|
+
sarif_file: results.sarif
|
|
@@ -2,6 +2,9 @@ name: tests
|
|
|
2
2
|
on:
|
|
3
3
|
push:
|
|
4
4
|
pull_request:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
5
8
|
jobs:
|
|
6
9
|
test:
|
|
7
10
|
runs-on: ubuntu-latest
|
|
@@ -9,8 +12,8 @@ jobs:
|
|
|
9
12
|
matrix:
|
|
10
13
|
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
11
14
|
steps:
|
|
12
|
-
- uses: actions/checkout@
|
|
13
|
-
- uses: actions/setup-python@v6
|
|
15
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
16
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
|
14
17
|
with:
|
|
15
18
|
python-version: ${{ matrix.python-version }}
|
|
16
19
|
- run: pip install -e ".[dev]"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# This .gitignore file was automatically created by Hypothesis. Hypothesis gitignores
|
|
2
|
+
# .hypothesis by default, because we generally recommend that .hypothesis not be checked
|
|
3
|
+
# into version control.
|
|
4
|
+
#
|
|
5
|
+
# If you *would* like to check .hypothesis into version control, you should delete this
|
|
6
|
+
# file. Hypothesis will not re-create this .gitignore unless .hypothesis is deleted (and
|
|
7
|
+
# if it does, that's a bug - please report it!)
|
|
8
|
+
|
|
9
|
+
*
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/pmq/pmq/src/pmq/executor.py
|
|
2
|
+
# hypothesis_version: 6.156.1
|
|
3
|
+
|
|
4
|
+
[0.0, 1000000.0, 137, 300, 400, 500, '0', '0.01', '0x[0-9a-fA-F]{64}', '; ', 'AssetType', 'BUY', 'FAILED', 'FAK', 'MAKER', 'MarketOrderArgs', 'MarketOrderArgsV2', 'OpenOrderParams', 'OrderArgs', 'OrderArgsV2', 'OrderType', 'POLY_BUILDER_CODE', 'POLY_FUNDER', 'POLY_PRIVATE_KEY', 'POLY_SIG_TYPE', 'SELL', 'TradeParams', '_pmq_taker4', 'amount', 'balance', 'builder_code', 'cancel_market_orders', 'crypto', 'error_msg', 'fd', 'get_open_orders', 'get_trades', 'makingAmount', 'off', 'on', 'orderID', 'order_args', 'order_type', 'params', 'payload', 'pmq', 'price', 'r', 'side', 'size', 'status', 'status_code', 'success', 'takingAmount', 'token_id', 'trader_side']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/pmq/pmq/src/pmq/__init__.py
|
|
2
|
+
# hypothesis_version: 6.156.1
|
|
3
|
+
|
|
4
|
+
['0.4.4', 'DEFAULT_BUILDER_CODE', 'FEE_RATES', 'Fill', 'OrderUncertain', 'PmqError', 'PolymarketExecutor', '__version__', 'band_ask_depth_usd', 'best_bid_ask', 'book_inferred_winner', 'book_meta', 'event_markets', 'fee', 'get_book', 'get_market', 'get_tape', 'http_get_json', 'parse_market', 'positions', 'resolved_winner']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/pmq/pmq/src/pmq/data.py
|
|
2
|
+
# hypothesis_version: 6.156.1
|
|
3
|
+
|
|
4
|
+
[0.0, 0.03, 0.04, 0.05, 0.07, 0.9, 0.99, 1.0, 1.5, 200, '%Y-%m-%dT%H:%M:%SZ', 'Mozilla/5.0', 'User-Agent', 'a', 'asks', 'b', 'bids', 'clobTokenIds', 'conditionId', 'crypto', 'culture', 'economics', 'endDate', 'endDateIso', 'finance', 'geopolitics', 'idx_a', 'last_trade_price', 'markets', 'mentions', 'min_order_size', 'neg_risk', 'outcomePrices', 'outcome_a', 'outcome_b', 'outcome_prices_raw', 'outcomes', 'politics', 'price', 'size', 'slug', 'sports', 'tech', 'tick_size', 'timestamp', 'weather']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/pmq/pmq/src/pmq/doctor.py
|
|
2
|
+
# hypothesis_version: 6.156.1
|
|
3
|
+
|
|
4
|
+
[1000000.0, 120, 160, 400, '\nverdict:', ', ', '--market', '0', '0x', '0x70a08231', '0x8da5cb5b', '2.0', 'CLOB auth/collateral', 'Content-Type', 'FAK', 'Mozilla/5.0', 'OrderType.FAK', 'POLY_FUNDER', 'POLY_PRIVATE_KEY', 'POLY_SIG_TYPE', 'User-Agent', '[!!]', '[??]', '[ok]', '__main__', 'application/json', 'data', 'drifted: ', 'error', 'eth_call', 'eth_getCode', 'funder', 'id', 'jsonrpc', 'latest', 'method', 'min_order_size', 'params', 'result', 'to', 'token_a']
|
|
Binary file
|
|
Binary file
|
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.4 (2026-07-04)
|
|
4
|
+
|
|
5
|
+
* Harden: json.loads accepts NaN and Infinity, so a drifted or hostile
|
|
6
|
+
exchange response could book non-finite or negative matched amounts.
|
|
7
|
+
`_parse_fill` now zeroes anything non-finite or negative (fail closed),
|
|
8
|
+
and a hypothesis fuzz suite (four property groups, hundreds of generated
|
|
9
|
+
adversarial responses per run) pins the whole fill contract: market and
|
|
10
|
+
limit paths book only confirmed finite amounts, the 4xx/uncertain
|
|
11
|
+
exception partition is total, every transport exception surfaces as
|
|
12
|
+
OrderUncertain.
|
|
13
|
+
* Security surface: CodeQL workflow (its first scan caught and we fixed a
|
|
14
|
+
host-boundary bypass in the egress allowlist), Scorecard alert triage
|
|
15
|
+
with written dismissal reasons, top-level permissions on the publish
|
|
16
|
+
workflow, direct private-advisory link in SECURITY.md, Dependabot
|
|
17
|
+
vulnerability alerts enabled. Listed in the official MCP registry as
|
|
18
|
+
io.github.crp4222/pmq (publish rides releases via OIDC).
|
|
19
|
+
|
|
20
|
+
## 0.4.3 (2026-07-04)
|
|
21
|
+
|
|
22
|
+
* Fix: py-clob-client-v2 1.0.2 reuses its limit-order rounding table for
|
|
23
|
+
MARKET orders, so on markets whose tick size is finer than 0.01 it signs
|
|
24
|
+
taker amounts with 5-6 decimals; the V2 exchange rejects those with
|
|
25
|
+
"invalid amounts ... taker amount a max of 4 decimals" and every FAK on
|
|
26
|
+
such a market fails. The executor now clamps the market-order path to 4
|
|
27
|
+
decimals at client level (round-down: the never-exceed-budget contract is
|
|
28
|
+
intact, the dust given up is under 0.0001 share per order). Found in
|
|
29
|
+
production: a fine-tick market rejected 10 consecutive buys and the
|
|
30
|
+
fail-closed halt fired exactly as designed; nothing was booked, nothing
|
|
31
|
+
was lost. Regression test pins maker 2dp / taker 4dp on both sides for
|
|
32
|
+
ticks 0.01, 0.001 and 0.0001.
|
|
33
|
+
|
|
34
|
+
* Trust batch: executable egress proof in the canary suite (records every
|
|
35
|
+
DNS resolution during a full session incl. a signed zero-fund order;
|
|
36
|
+
fails on any host outside polymarket.com; weekly CI prints the list in
|
|
37
|
+
public logs), a test pinning that the private key never reaches logs,
|
|
38
|
+
OpenSSF Scorecard workflow + badge, GitHub Actions pinned by commit SHA,
|
|
39
|
+
explicit workflow permissions, Dependabot (pip + actions), weekly
|
|
40
|
+
pip-audit wired into the canary alarm, README sections "Verify the
|
|
41
|
+
claims yourself" (egress, PEP 740 provenance, dependency watch) and
|
|
42
|
+
"Stability and maintenance" (pre-1.0 SemVer contract, the stated bar for
|
|
43
|
+
1.0, bus-factor honesty, precisely scoped help-wanted).
|
|
44
|
+
* Quality pass driven by pyscn (CFG complexity, dead code, clones): the
|
|
45
|
+
template engine loop and pmq-doctor were split into single-purpose phase
|
|
46
|
+
functions (worst cyclomatic complexity 36 -> 10 across the repo, zero dead
|
|
47
|
+
code, behavior identical), and the previously untested template main loop
|
|
48
|
+
is now pinned by 17 end-to-end tests (fake clock, stubbed exchange):
|
|
49
|
+
paper fills at the real ask, budget headroom, halts, poisoning,
|
|
50
|
+
consecutive-failure exit, exchange-truth scoring. 113 tests total.
|
|
51
|
+
`pyscn check src/pmq bot-template` passes at default thresholds.
|
|
52
|
+
|
|
3
53
|
## 0.4.2 (2026-07-03)
|
|
4
54
|
|
|
5
55
|
* Fix: `buy_fak`/`sell_fak` cent-rounding used `int(x * 100) / 100`, which
|
pmquant-0.4.4/CLAUDE.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# pmq: engineering invariants for agents CONTRIBUTING to this repo
|
|
2
|
+
|
|
3
|
+
(AGENTS.md in this repo is for agents USING the library; this file is for
|
|
4
|
+
agents EDITING it. Read both before changing code.)
|
|
5
|
+
|
|
6
|
+
## Never weaken (the product IS these properties)
|
|
7
|
+
|
|
8
|
+
1. **The fail-closed fill contract**: a `Fill` books only what the exchange
|
|
9
|
+
confirmed (`orderID` + `success is not False` + matched amounts); 4xx is a
|
|
10
|
+
clean rejection; timeout/5xx raises `OrderUncertain`; unparseable = zero.
|
|
11
|
+
Any change that books more optimistically is a regression by definition,
|
|
12
|
+
whatever it fixes elsewhere. Matched amounts must be finite and
|
|
13
|
+
non-negative (json.loads accepts NaN/Infinity; hostile values book zero).
|
|
14
|
+
`reconcile()` must keep meaning cancel + `get_trades` truth. The
|
|
15
|
+
hypothesis fuzz suite (tests/test_fill_fuzz.py) pins all of this with
|
|
16
|
+
generated adversarial responses: extend it with every parser change,
|
|
17
|
+
never delete it.
|
|
18
|
+
2. **Startup introspection** (`_EXPECTED_METHODS`/`_EXPECTED_MARKET_ARGS`):
|
|
19
|
+
the executor REFUSES to run on a drifted py-clob-client-v2. When bumping
|
|
20
|
+
the client dependency, re-verify signatures by introspection and update
|
|
21
|
+
the tables in the same commit.
|
|
22
|
+
3. **Builder code policy**: default = maintainer's code, defined in exactly
|
|
23
|
+
ONE place (`DEFAULT_BUILDER_CODE` in executor.py) and applied
|
|
24
|
+
automatically by every order path. DISCLOSED in README and code comment,
|
|
25
|
+
opt-out one line (`builder_code=None` / env). Never hide it, never remove
|
|
26
|
+
the disclosure, never make opt-out harder. AND the mirror rule: keep the
|
|
27
|
+
disclosure at the DOCUMENTATION level only; do not surface attribution in
|
|
28
|
+
runtime channels (server startup logs, MCP tools or instructions, order
|
|
29
|
+
responses). It is public on-chain in every signed order; in-band
|
|
30
|
+
reminders just prompt sessions to toggle a setting that costs users
|
|
31
|
+
nothing. This is the trust model (JKorf pattern).
|
|
32
|
+
4. **No strategy content, ever**: the maintainer's private bot strategy
|
|
33
|
+
(bands, timing, hours, families, sizing) must never appear in code, docs,
|
|
34
|
+
tests, commits or issues. The bot-template ships deliberately naive
|
|
35
|
+
demos only.
|
|
36
|
+
5. **Claims must be falsifiable**: no superlatives in README/docs; dated
|
|
37
|
+
claims with evidence (comparison table, on-chain receipts, measured
|
|
38
|
+
studies). If you cannot prove it, do not write it.
|
|
39
|
+
6. **MCP safety gates**: trading tools are REGISTERED only when the operator
|
|
40
|
+
sets `PMQ_MCP_LIVE=1`; per-order `PMQ_MCP_MAX_USD` cap enforced before
|
|
41
|
+
any client call. Read tools must keep working with zero credentials.
|
|
42
|
+
|
|
43
|
+
## Working rules
|
|
44
|
+
|
|
45
|
+
* Tests green (`pytest -q`) and `ruff check .` clean before any push;
|
|
46
|
+
`pyscn check src/pmq bot-template` (complexity <= 10, no dead code)
|
|
47
|
+
must stay green too; clone warnings are informational (the template
|
|
48
|
+
dash deliberately duplicates helpers to stay stdlib-standalone). Add
|
|
49
|
+
tests with every behavior change. Network-touching tests go to
|
|
50
|
+
`tests/test_canary_live.py` behind `PMQ_CANARY=1`, never in default CI.
|
|
51
|
+
* Exchange rules (min size, tick, fee rate) are READ from the venue
|
|
52
|
+
(`book_meta`, `fee_rate`), not hardcoded. `FEE_RATES` is a documented
|
|
53
|
+
snapshot of the official schedule used for estimates.
|
|
54
|
+
* Releases: bump version in `pyproject.toml`, `src/pmq/__init__.py` AND
|
|
55
|
+
`server.json` (both version fields), update CHANGELOG.md, push, then
|
|
56
|
+
`gh release create vX.Y.Z`: PyPI publish (trusted publishing, signed
|
|
57
|
+
attestations) and the MCP registry republish (mcp-publish.yml,
|
|
58
|
+
github-oidc) both fire on the release event. Registry gotchas: the
|
|
59
|
+
server.json description caps at 100 characters, and the version must
|
|
60
|
+
exist on PyPI. PyPI name is `pmquant`, import name `pmq`: keep the
|
|
61
|
+
README line explaining it.
|
|
62
|
+
* CLAUDE.md and CONTRIBUTING.md are THE SAME FILE by contract: after
|
|
63
|
+
editing one, copy it over the other in the same commit (`cp CLAUDE.md
|
|
64
|
+
CONTRIBUTING.md`). Drift between them means an agent read stale rules.
|
|
65
|
+
* Local guard: `git config core.hooksPath .githooks` once per clone
|
|
66
|
+
enables the pre-push hook (ruff + mypy + pytest). CI is the backstop,
|
|
67
|
+
but the hook catches a broken push before it lands.
|
|
68
|
+
* GitHub Actions stay pinned by commit SHA (dependabot bumps them); new
|
|
69
|
+
workflows get an explicit least-privilege permissions block. The egress
|
|
70
|
+
test and pip-audit ride the weekly canary: never move them to default CI
|
|
71
|
+
(they need network) and never widen the egress allowlist beyond
|
|
72
|
+
polymarket.com without updating SECURITY.md and the README section.
|
|
73
|
+
* The weekly canary workflow is the drift alarm: if it opens an issue, the
|
|
74
|
+
fix starts by re-running the introspection against the new surface, not
|
|
75
|
+
by loosening the checks.
|
|
76
|
+
* Keep the library small and auditable (five modules): resist adding
|
|
77
|
+
dependencies; stdlib first. Anything bot-shaped belongs in bot-template/,
|
|
78
|
+
not in the package.
|
|
79
|
+
* Style: no em-dashes and no " - " connectors anywhere (strong user rule);
|
|
80
|
+
keep comments sparse and constraint-focused.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# pmq: engineering invariants for agents CONTRIBUTING to this repo
|
|
2
|
+
|
|
3
|
+
(AGENTS.md in this repo is for agents USING the library; this file is for
|
|
4
|
+
agents EDITING it. Read both before changing code.)
|
|
5
|
+
|
|
6
|
+
## Never weaken (the product IS these properties)
|
|
7
|
+
|
|
8
|
+
1. **The fail-closed fill contract**: a `Fill` books only what the exchange
|
|
9
|
+
confirmed (`orderID` + `success is not False` + matched amounts); 4xx is a
|
|
10
|
+
clean rejection; timeout/5xx raises `OrderUncertain`; unparseable = zero.
|
|
11
|
+
Any change that books more optimistically is a regression by definition,
|
|
12
|
+
whatever it fixes elsewhere. Matched amounts must be finite and
|
|
13
|
+
non-negative (json.loads accepts NaN/Infinity; hostile values book zero).
|
|
14
|
+
`reconcile()` must keep meaning cancel + `get_trades` truth. The
|
|
15
|
+
hypothesis fuzz suite (tests/test_fill_fuzz.py) pins all of this with
|
|
16
|
+
generated adversarial responses: extend it with every parser change,
|
|
17
|
+
never delete it.
|
|
18
|
+
2. **Startup introspection** (`_EXPECTED_METHODS`/`_EXPECTED_MARKET_ARGS`):
|
|
19
|
+
the executor REFUSES to run on a drifted py-clob-client-v2. When bumping
|
|
20
|
+
the client dependency, re-verify signatures by introspection and update
|
|
21
|
+
the tables in the same commit.
|
|
22
|
+
3. **Builder code policy**: default = maintainer's code, defined in exactly
|
|
23
|
+
ONE place (`DEFAULT_BUILDER_CODE` in executor.py) and applied
|
|
24
|
+
automatically by every order path. DISCLOSED in README and code comment,
|
|
25
|
+
opt-out one line (`builder_code=None` / env). Never hide it, never remove
|
|
26
|
+
the disclosure, never make opt-out harder. AND the mirror rule: keep the
|
|
27
|
+
disclosure at the DOCUMENTATION level only; do not surface attribution in
|
|
28
|
+
runtime channels (server startup logs, MCP tools or instructions, order
|
|
29
|
+
responses). It is public on-chain in every signed order; in-band
|
|
30
|
+
reminders just prompt sessions to toggle a setting that costs users
|
|
31
|
+
nothing. This is the trust model (JKorf pattern).
|
|
32
|
+
4. **No strategy content, ever**: the maintainer's private bot strategy
|
|
33
|
+
(bands, timing, hours, families, sizing) must never appear in code, docs,
|
|
34
|
+
tests, commits or issues. The bot-template ships deliberately naive
|
|
35
|
+
demos only.
|
|
36
|
+
5. **Claims must be falsifiable**: no superlatives in README/docs; dated
|
|
37
|
+
claims with evidence (comparison table, on-chain receipts, measured
|
|
38
|
+
studies). If you cannot prove it, do not write it.
|
|
39
|
+
6. **MCP safety gates**: trading tools are REGISTERED only when the operator
|
|
40
|
+
sets `PMQ_MCP_LIVE=1`; per-order `PMQ_MCP_MAX_USD` cap enforced before
|
|
41
|
+
any client call. Read tools must keep working with zero credentials.
|
|
42
|
+
|
|
43
|
+
## Working rules
|
|
44
|
+
|
|
45
|
+
* Tests green (`pytest -q`) and `ruff check .` clean before any push;
|
|
46
|
+
`pyscn check src/pmq bot-template` (complexity <= 10, no dead code)
|
|
47
|
+
must stay green too; clone warnings are informational (the template
|
|
48
|
+
dash deliberately duplicates helpers to stay stdlib-standalone). Add
|
|
49
|
+
tests with every behavior change. Network-touching tests go to
|
|
50
|
+
`tests/test_canary_live.py` behind `PMQ_CANARY=1`, never in default CI.
|
|
51
|
+
* Exchange rules (min size, tick, fee rate) are READ from the venue
|
|
52
|
+
(`book_meta`, `fee_rate`), not hardcoded. `FEE_RATES` is a documented
|
|
53
|
+
snapshot of the official schedule used for estimates.
|
|
54
|
+
* Releases: bump version in `pyproject.toml`, `src/pmq/__init__.py` AND
|
|
55
|
+
`server.json` (both version fields), update CHANGELOG.md, push, then
|
|
56
|
+
`gh release create vX.Y.Z`: PyPI publish (trusted publishing, signed
|
|
57
|
+
attestations) and the MCP registry republish (mcp-publish.yml,
|
|
58
|
+
github-oidc) both fire on the release event. Registry gotchas: the
|
|
59
|
+
server.json description caps at 100 characters, and the version must
|
|
60
|
+
exist on PyPI. PyPI name is `pmquant`, import name `pmq`: keep the
|
|
61
|
+
README line explaining it.
|
|
62
|
+
* CLAUDE.md and CONTRIBUTING.md are THE SAME FILE by contract: after
|
|
63
|
+
editing one, copy it over the other in the same commit (`cp CLAUDE.md
|
|
64
|
+
CONTRIBUTING.md`). Drift between them means an agent read stale rules.
|
|
65
|
+
* Local guard: `git config core.hooksPath .githooks` once per clone
|
|
66
|
+
enables the pre-push hook (ruff + mypy + pytest). CI is the backstop,
|
|
67
|
+
but the hook catches a broken push before it lands.
|
|
68
|
+
* GitHub Actions stay pinned by commit SHA (dependabot bumps them); new
|
|
69
|
+
workflows get an explicit least-privilege permissions block. The egress
|
|
70
|
+
test and pip-audit ride the weekly canary: never move them to default CI
|
|
71
|
+
(they need network) and never widen the egress allowlist beyond
|
|
72
|
+
polymarket.com without updating SECURITY.md and the README section.
|
|
73
|
+
* The weekly canary workflow is the drift alarm: if it opens an issue, the
|
|
74
|
+
fix starts by re-running the introspection against the new surface, not
|
|
75
|
+
by loosening the checks.
|
|
76
|
+
* Keep the library small and auditable (five modules): resist adding
|
|
77
|
+
dependencies; stdlib first. Anything bot-shaped belongs in bot-template/,
|
|
78
|
+
not in the package.
|
|
79
|
+
* Style: no em-dashes and no " - " connectors anywhere (strong user rule);
|
|
80
|
+
keep comments sparse and constraint-focused.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pmquant
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
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
|
|
@@ -23,6 +23,7 @@ Classifier: Typing :: Typed
|
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: py-clob-client-v2<2,>=1.0.2
|
|
25
25
|
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: hypothesis>=6.100; extra == 'dev'
|
|
26
27
|
Requires-Dist: mcp>=1.2; extra == 'dev'
|
|
27
28
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
28
29
|
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
@@ -41,6 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
41
42
|
[](https://github.com/crp4222/pmq/actions/workflows/canary.yml)
|
|
42
43
|
[](.github/workflows/test.yml)
|
|
43
44
|
[](pyproject.toml)
|
|
45
|
+
[](https://scorecard.dev/viewer/?uri=github.com/crp4222/pmq)
|
|
44
46
|
[](LICENSE)
|
|
45
47
|
|
|
46
48
|
Fail-closed execution and market data for **Polymarket CLOB V2**, in Python.
|
|
@@ -81,13 +83,15 @@ a real error in live trading:
|
|
|
81
83
|
|
|
82
84
|
The full write-up with reproduction details: [docs/war-story.md](docs/war-story.md).
|
|
83
85
|
|
|
84
|
-
## Runs in production
|
|
86
|
+
## Runs in production: my own money, daily
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
I built pmq for my own trading. It executes real volume with my funds every
|
|
89
|
+
day, and it has never booked a fill the exchange did not confirm. If you
|
|
90
|
+
want to see it on-chain, here is a settlement from one of my wallets
|
|
91
|
+
(2026-07-03):
|
|
88
92
|
[`0x387f5f09...100d88a8`](https://polygonscan.com/tx/0x387f5f09c031bb36a71c54adc978b1ed4d50c67f6dd3f0c2c8068391100d88a8)
|
|
89
93
|
on the CTF Exchange V2: a FAK market buy built by this library, matched and
|
|
90
|
-
settled, with the builder code visible in the calldata.
|
|
94
|
+
settled, with the builder code visible in the calldata. A weekly
|
|
91
95
|
[canary workflow](.github/workflows/canary.yml) exercises the real endpoints
|
|
92
96
|
and the installed client surface, and opens an issue by itself if Polymarket
|
|
93
97
|
drifts.
|
|
@@ -126,7 +130,10 @@ not your hopes.
|
|
|
126
130
|
|
|
127
131
|
At startup pmq **introspects the installed py-clob-client-v2** against the API
|
|
128
132
|
surface it was verified on, and refuses to trade on drift instead of sending
|
|
129
|
-
orders through changed semantics.
|
|
133
|
+
orders through changed semantics. The whole table is pinned by an executable
|
|
134
|
+
test per row plus a hypothesis fuzz suite (hundreds of generated adversarial
|
|
135
|
+
responses per run, including NaN/Infinity and negative amounts, which book
|
|
136
|
+
zero).
|
|
130
137
|
|
|
131
138
|
## Quickstart
|
|
132
139
|
|
|
@@ -160,9 +167,10 @@ else:
|
|
|
160
167
|
print(fill.matched_shares, "shares at", fill.price, "order", fill.order_id)
|
|
161
168
|
```
|
|
162
169
|
|
|
163
|
-
`sell_fak` and `limit_gtc` follow the same contract.
|
|
164
|
-
|
|
165
|
-
|
|
170
|
+
`sell_fak` and `limit_gtc` follow the same contract. Both FAK paths have
|
|
171
|
+
carried real volume: a production round trip (buy 5.149 @ 0.94, sell back
|
|
172
|
+
5.14 @ 0.94, cross-checked via `get_trades`) confirmed the mirrored
|
|
173
|
+
`makingAmount`/`takingAmount` semantics on 2026-07-03.
|
|
166
174
|
|
|
167
175
|
## The signature_type table nobody gives you
|
|
168
176
|
|
|
@@ -212,7 +220,9 @@ JKorf/Polymarket.Net; the official client defaults to zero attribution.)
|
|
|
212
220
|
|
|
213
221
|
## MCP server (agents)
|
|
214
222
|
|
|
215
|
-
`pip install "pmquant[mcp]"` then run `pmq-mcp` (stdio).
|
|
223
|
+
`pip install "pmquant[mcp]"` then run `pmq-mcp` (stdio). Listed in the
|
|
224
|
+
[official MCP registry](https://registry.modelcontextprotocol.io) as
|
|
225
|
+
`io.github.crp4222/pmq`. Read tools (market,
|
|
216
226
|
book, taker_fee, account_collateral, account_trades) always exist. Trading
|
|
217
227
|
tools (`fak_buy`, `fak_sell`, `cancel_and_reconcile`) are **only registered
|
|
218
228
|
when the operator sets `PMQ_MCP_LIVE=1`** in the server environment: an
|
|
@@ -247,12 +257,60 @@ shipped demo strategy is an API illustration meant to be replaced.
|
|
|
247
257
|
* Keys are read from the environment, used to instantiate the signer, and
|
|
248
258
|
never logged. No custody, no backend, no telemetry, zero network calls
|
|
249
259
|
besides Polymarket endpoints.
|
|
250
|
-
*
|
|
251
|
-
|
|
260
|
+
* A documented wave of fake "polymarket bot" repositories steals private
|
|
261
|
+
keys; pmq is deliberately small so the entire execution path stays
|
|
262
|
+
readable in minutes by anyone who wants to look.
|
|
252
263
|
* Fund the trading wallet with what you can afford to lose. Nothing here is
|
|
253
264
|
financial advice; prediction-market access is restricted in some
|
|
254
265
|
jurisdictions and compliance is on you.
|
|
255
266
|
|
|
267
|
+
## If you feel like checking any of it
|
|
268
|
+
|
|
269
|
+
None of the claims above require taking my word; each one comes with a
|
|
270
|
+
handle you can pull, whenever you care to:
|
|
271
|
+
|
|
272
|
+
* **Egress.** `PMQ_CANARY=1 pytest tests/test_canary_live.py -k egress -s`
|
|
273
|
+
records every DNS resolution during a full session (market data, auth
|
|
274
|
+
derivation, one signed order) and fails on any host outside
|
|
275
|
+
`polymarket.com`. Last observed list: `clob.polymarket.com`,
|
|
276
|
+
`gamma-api.polymarket.com`, nothing else. The weekly
|
|
277
|
+
[canary](../../actions/workflows/canary.yml) prints that list in public
|
|
278
|
+
CI logs. One designed exception: `pmq-doctor`'s optional on-chain checks
|
|
279
|
+
use the public Polygon RPCs named in its source.
|
|
280
|
+
* **Provenance.** Releases carry a signed PEP 740 attestation (Sigstore,
|
|
281
|
+
via PyPI trusted publishing): click "provenance" next to any file on the
|
|
282
|
+
[PyPI files page](https://pypi.org/project/pmquant/#files), or fetch it
|
|
283
|
+
raw from PyPI's integrity API. The signing identity is this repository's
|
|
284
|
+
`publish.yml` workflow.
|
|
285
|
+
* **Dependencies.** Dependabot files weekly bump PRs (Python and
|
|
286
|
+
SHA-pinned GitHub Actions), and the weekly canary runs `pip-audit`; a
|
|
287
|
+
hit opens an issue by itself.
|
|
288
|
+
* **The source.** Five small modules; the whole execution path reads in
|
|
289
|
+
minutes. The grep targets that answer the important questions fastest
|
|
290
|
+
are listed in [SECURITY.md](SECURITY.md).
|
|
291
|
+
|
|
292
|
+
## Stability and maintenance
|
|
293
|
+
|
|
294
|
+
* Pre-1.0 SemVer: PATCH releases only fix, MINOR releases may change the
|
|
295
|
+
public API with the migration named in [CHANGELOG.md](CHANGELOG.md).
|
|
296
|
+
Nothing changes silently.
|
|
297
|
+
* Deprecated APIs keep working and warn for at least one MINOR release
|
|
298
|
+
before removal.
|
|
299
|
+
* The bar for 1.0, stated in advance: months of green weekly canaries, the
|
|
300
|
+
maker path (`limit_gtc`) production-proven with real volume the way the
|
|
301
|
+
FAK paths already are, and external production users.
|
|
302
|
+
* Bus-factor honesty: one maintainer, who trades real money through this
|
|
303
|
+
exact code daily (strongest available incentive to keep it correct). The
|
|
304
|
+
mitigations are structural, not promises: five small modules, the
|
|
305
|
+
executable fill-contract test table, a weekly canary that opens issues by
|
|
306
|
+
itself, SHA-pinned CI. Operational rule: if the canary badge goes red and
|
|
307
|
+
stays red, treat the project as unmaintained and pin your last known-good
|
|
308
|
+
version.
|
|
309
|
+
* Help wanted, precisely scoped: production receipts for `signature_type`
|
|
310
|
+
1 and 2 accounts (legacy Magic/email and browser-wallet proxies). Both
|
|
311
|
+
paths are introspection-tested but have never carried real money through
|
|
312
|
+
this library; the maintainer's own accounts are all types 0 and 3.
|
|
313
|
+
|
|
256
314
|
## License
|
|
257
315
|
|
|
258
316
|
MIT
|