pmwallets-copytrade 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.
Files changed (28) hide show
  1. pmwallets_copytrade-0.1.0/.github/workflows/ci.yml +18 -0
  2. pmwallets_copytrade-0.1.0/.github/workflows/publish.yml +24 -0
  3. pmwallets_copytrade-0.1.0/.gitignore +9 -0
  4. pmwallets_copytrade-0.1.0/LICENSE +21 -0
  5. pmwallets_copytrade-0.1.0/PKG-INFO +134 -0
  6. pmwallets_copytrade-0.1.0/README.md +114 -0
  7. pmwallets_copytrade-0.1.0/README.zh.md +108 -0
  8. pmwallets_copytrade-0.1.0/config.example.yaml +45 -0
  9. pmwallets_copytrade-0.1.0/pyproject.toml +34 -0
  10. pmwallets_copytrade-0.1.0/scripts/live_readonly_smoke.py +34 -0
  11. pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/__init__.py +3 -0
  12. pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/cli.py +131 -0
  13. pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/config.py +190 -0
  14. pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/engine.py +508 -0
  15. pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/filters.py +88 -0
  16. pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/log.py +36 -0
  17. pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/polymarket.py +447 -0
  18. pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/run.py +162 -0
  19. pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/state.py +267 -0
  20. pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/units.py +72 -0
  21. pmwallets_copytrade-0.1.0/testdata/config-contract.expected.json +15 -0
  22. pmwallets_copytrade-0.1.0/testdata/config-contract.yaml +23 -0
  23. pmwallets_copytrade-0.1.0/testdata/post-classification.json +14 -0
  24. pmwallets_copytrade-0.1.0/tests/test_config.py +67 -0
  25. pmwallets_copytrade-0.1.0/tests/test_engine.py +667 -0
  26. pmwallets_copytrade-0.1.0/tests/test_filters.py +49 -0
  27. pmwallets_copytrade-0.1.0/tests/test_polymarket.py +119 -0
  28. pmwallets_copytrade-0.1.0/tests/test_units.py +34 -0
@@ -0,0 +1,18 @@
1
+ name: ci
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ matrix:
11
+ python: ["3.10", "3.12"]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: ${{ matrix.python }}
17
+ - run: pip install -e '.[dev]'
18
+ - run: pytest -q
@@ -0,0 +1,24 @@
1
+ # Publishes to PyPI through a Trusted Publisher (OIDC): no token is stored anywhere.
2
+ # Release = push a tag v<version> matching pyproject.toml.
3
+ name: publish
4
+ on:
5
+ push:
6
+ tags: ["v*"]
7
+ jobs:
8
+ pypi:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write
13
+ contents: read
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+ - name: tag must match the package version
20
+ run: |
21
+ v=$(python -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
22
+ test "v$v" = "$GITHUB_REF_NAME" || { echo "tag $GITHUB_REF_NAME != v$v"; exit 1; }
23
+ - run: pip install build && python -m build
24
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .venv/
4
+ .pytest_cache/
5
+ build/
6
+ dist/
7
+ .env
8
+ config.yaml
9
+ pmw-data/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PMWallets
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.
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.5
2
+ Name: pmwallets-copytrade
3
+ Version: 0.1.0
4
+ Summary: Ready-to-run Polymarket copy-trading bot: follows the wallets you subscribe to on PMWallets and mirrors their trades on the Polymarket CLOB, with dry-run, risk caps and exactly-once fill handling.
5
+ Project-URL: Homepage, https://pmwallets.com/copy-trading
6
+ Project-URL: Repository, https://github.com/polymarketwallets/polymarket-copy-trading-bot-python
7
+ Project-URL: Issues, https://github.com/polymarketwallets/polymarket-copy-trading-bot-python/issues
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: copy-trading,copytrade,pmwallets,polymarket,prediction-markets,trading-bot
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: httpx>=0.27
13
+ Requires-Dist: pmwallets>=0.1.0
14
+ Requires-Dist: py-clob-client-v2>=1.1.0
15
+ Requires-Dist: pyyaml>=6
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
18
+ Requires-Dist: pytest>=8; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Polymarket copy-trading bot (Python)
22
+
23
+ [![PyPI](https://img.shields.io/pypi/v/pmwallets-copytrade.svg)](https://pypi.org/project/pmwallets-copytrade/) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
24
+
25
+ A **ready-to-run Polymarket copy-trading bot** built on [PMWallets](https://pmwallets.com/copy-trading): it follows the
26
+ traders you subscribe to and mirrors their fills on the Polymarket CLOB with your own account, inside the limits you set.
27
+
28
+ [`pmwallets-copytrade` on PyPI](https://pypi.org/project/pmwallets-copytrade/) · [中文说明](README.zh.md) · Node.js version: [polymarket-copy-trading-bot](https://github.com/polymarketwallets/polymarket-copy-trading-bot) ·
29
+ SDK: [pmwallets-python](https://github.com/polymarketwallets/pmwallets-python)
30
+
31
+ ## What the bot does
32
+
33
+ 1. **You pick the traders** on [pmwallets.com](https://pmwallets.com): filter the board by the lower bound of the
34
+ win-rate confidence interval, profit without the single best market, activity and style, then subscribe.
35
+ 2. **The bot receives every fill** of those traders over the PMWallets WebSocket, typically within a second of the
36
+ block ([measured latency](https://pmwallets.com/latency)). Missed frames are detected (`session`/`seq`) and
37
+ replayed from the last fill handled, including everything that happened while the bot was down.
38
+ 3. **It mirrors them on Polymarket** with your own account, inside the limits you set. Your keys never leave your
39
+ machine; PMWallets never sees them and never places orders.
40
+
41
+ ## Quick start
42
+
43
+ ```bash
44
+ # Python ≥ 3.10
45
+ pip install pmwallets-copytrade
46
+ pmwallets-copytrade init # writes config.yaml
47
+ export PMW_API_KEY=pmw_... # https://pmwallets.com/keys
48
+ pmwallets-copytrade run # dry-run: logs every decision, trades nothing
49
+ ```
50
+
51
+ When the dry-run log looks right, switch to live:
52
+
53
+ ```yaml
54
+ mode: live
55
+ polymarket:
56
+ privateKey: ${POLY_PRIVATE_KEY} # signs your orders
57
+ signatureType: 2 # 0 plain wallet · 1 email/Magic login · 2 browser-wallet login
58
+ funderAddress: ${POLY_FUNDER_ADDRESS} # your Polymarket profile address (holds the USDC)
59
+ ```
60
+
61
+ `pmwallets-copytrade status` prints open positions, today's spend and any order still being confirmed. If the bot
62
+ cannot establish by itself whether an order filled, it keeps the order's reservation (no further BUY can exceed
63
+ your limits because of it) and asks you: check polymarket.com and run
64
+ `pmwallets-copytrade reconcile <key> --none` or `--filled <shares> --usdc <usdc>` with the bot stopped. Every decision — including each skip and its
65
+ reason — is appended to `pmw-data/decisions.<mode>.jsonl`.
66
+
67
+ ## How it decides
68
+
69
+ The rules come from a copy-trading bot that ran live on Polymarket; each one is there because of a lost trade or a
70
+ rejected order.
71
+
72
+ **BUY** (the trader bought)
73
+ - skip fills older than `maxFillAgeSec` (a replay after downtime must not buy history), below
74
+ `minTargetNotionalUsdc`, or from a role you excluded;
75
+ - one copy per trader transaction, one position per (trader, outcome), at most `maxBuysPerOutcome` buys into it
76
+ (DCA), `maxOpenPositionsPerTarget` / `maxOpenPositions` / `maxDailySpendUsdc` caps;
77
+ - the market must be open, accepting orders and not settle within `minSecondsToEndDate`;
78
+ - the best ask must sit in `[minPrice, maxPrice]`, be at most `maxSlippage` above what the trader paid, and the
79
+ book must hold `minBookDepthUsdc`;
80
+ - order: **fill-or-kill for an exact number of shares** at the best ask (`orderSizeUsdc` / ask), rounded so the
81
+ CLOB accepts it. Share-denominated on purpose — a USDC-budget market order can overfill.
82
+
83
+ **SELL** (the trader sold)
84
+ - `sellMode: all` exits everything the bot bought following **that** trader in that outcome. Shares booked to
85
+ another trader in the same outcome are never sold: the most it sells is your token balance minus what the other
86
+ traders hold. If that comes to nothing, it stops and asks you to reconcile;
87
+ - the exit is saved and retried (with backoff, across restarts) until the position is gone. Freshness does not
88
+ apply: if the trader left while the bot was down, it still gets out;
89
+ - order: **fill-and-kill** at the best bid — a partial exit beats none, and the rest is retried.
90
+
91
+ **Safety**
92
+ - **dry-run by default**; live needs an explicit `mode: live`;
93
+ - **at most once**: a fill is marked decided in the state file *before* any order is sent, so a crash can miss a
94
+ copy but never place it twice. One bot per data directory: a second instance refuses to start;
95
+ - **no lost fills**: every order is saved as *pending* before it is sent. An order the exchange reports as killed
96
+ (such replies have carried real partial fills) is looked up again by its order id with backoff until its fill —
97
+ or its absence — is certain, even across restarts. One that got no answer at all has no id to look up: if a
98
+ matching trade appears, the bot does not guess whose it is (it could be yours by hand, or another trader's copy)
99
+ and hands it to you to `reconcile`;
100
+ - resolved markets are swept every 10 minutes so they stop counting against the caps.
101
+
102
+ **One stream per account.** PMWallets allows one WebSocket per account and the newest connection wins, so the bot
103
+ and the live-feed page on pmwallets.com (or a second bot) will take the stream from each other. Run one consumer
104
+ per account.
105
+
106
+ ## Development
107
+
108
+ ```bash
109
+ pip install -e '.[dev]' # or: uv pip install -e . pytest pytest-asyncio
110
+ pytest
111
+ ```
112
+
113
+ The same `config.yaml`, state files and lock file work with the Node.js version. `testdata/` holds the
114
+ contract cases both implementations must pass — keep it identical in both repositories.
115
+
116
+ ## Resources
117
+
118
+ - [Polymarket smart-money leaderboard](https://pmwallets.com) — profitable Polymarket traders scored from the Polygon chain, with win-rate confidence intervals
119
+ - [Polymarket copy trading guide](https://pmwallets.com/copy-trading) — which wallets are worth following and how to get their fills in time
120
+ - [How to learn from Polymarket smart money](https://pmwallets.com/learn) — reading a trader's record: confidence intervals, maker vs taker, market specialism
121
+ - [PMWallets API documentation](https://pmwallets.com/docs) — WebSocket and webhook fill push, fills replay, trade-history exports
122
+ - [Measured fill-push latency](https://pmwallets.com/latency) — block-to-push p50 / p95, published live
123
+ - [Ways to follow Polymarket wallets, compared](https://pmwallets.com/compare) — official leaderboard, free trackers, SQL dashboards
124
+ - [FAQ](https://pmwallets.com/faq) · [中文站](https://pmwallets.com/zh)
125
+
126
+ ## Proxies
127
+
128
+ `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` are honoured for the PMWallets API, the WebSocket and the Polymarket CLOB.
129
+
130
+ ## Disclaimer
131
+
132
+ This software places real orders with real money when `mode: live`. A copied order is not the trader's order: the
133
+ price may have moved, fees apply and liquidity is shared. Past results do not predict future ones. PMWallets
134
+ provides data about what traders did, not advice. Use at your own risk; see [LICENSE](LICENSE) (MIT).
@@ -0,0 +1,114 @@
1
+ # Polymarket copy-trading bot (Python)
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/pmwallets-copytrade.svg)](https://pypi.org/project/pmwallets-copytrade/) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
4
+
5
+ A **ready-to-run Polymarket copy-trading bot** built on [PMWallets](https://pmwallets.com/copy-trading): it follows the
6
+ traders you subscribe to and mirrors their fills on the Polymarket CLOB with your own account, inside the limits you set.
7
+
8
+ [`pmwallets-copytrade` on PyPI](https://pypi.org/project/pmwallets-copytrade/) · [中文说明](README.zh.md) · Node.js version: [polymarket-copy-trading-bot](https://github.com/polymarketwallets/polymarket-copy-trading-bot) ·
9
+ SDK: [pmwallets-python](https://github.com/polymarketwallets/pmwallets-python)
10
+
11
+ ## What the bot does
12
+
13
+ 1. **You pick the traders** on [pmwallets.com](https://pmwallets.com): filter the board by the lower bound of the
14
+ win-rate confidence interval, profit without the single best market, activity and style, then subscribe.
15
+ 2. **The bot receives every fill** of those traders over the PMWallets WebSocket, typically within a second of the
16
+ block ([measured latency](https://pmwallets.com/latency)). Missed frames are detected (`session`/`seq`) and
17
+ replayed from the last fill handled, including everything that happened while the bot was down.
18
+ 3. **It mirrors them on Polymarket** with your own account, inside the limits you set. Your keys never leave your
19
+ machine; PMWallets never sees them and never places orders.
20
+
21
+ ## Quick start
22
+
23
+ ```bash
24
+ # Python ≥ 3.10
25
+ pip install pmwallets-copytrade
26
+ pmwallets-copytrade init # writes config.yaml
27
+ export PMW_API_KEY=pmw_... # https://pmwallets.com/keys
28
+ pmwallets-copytrade run # dry-run: logs every decision, trades nothing
29
+ ```
30
+
31
+ When the dry-run log looks right, switch to live:
32
+
33
+ ```yaml
34
+ mode: live
35
+ polymarket:
36
+ privateKey: ${POLY_PRIVATE_KEY} # signs your orders
37
+ signatureType: 2 # 0 plain wallet · 1 email/Magic login · 2 browser-wallet login
38
+ funderAddress: ${POLY_FUNDER_ADDRESS} # your Polymarket profile address (holds the USDC)
39
+ ```
40
+
41
+ `pmwallets-copytrade status` prints open positions, today's spend and any order still being confirmed. If the bot
42
+ cannot establish by itself whether an order filled, it keeps the order's reservation (no further BUY can exceed
43
+ your limits because of it) and asks you: check polymarket.com and run
44
+ `pmwallets-copytrade reconcile <key> --none` or `--filled <shares> --usdc <usdc>` with the bot stopped. Every decision — including each skip and its
45
+ reason — is appended to `pmw-data/decisions.<mode>.jsonl`.
46
+
47
+ ## How it decides
48
+
49
+ The rules come from a copy-trading bot that ran live on Polymarket; each one is there because of a lost trade or a
50
+ rejected order.
51
+
52
+ **BUY** (the trader bought)
53
+ - skip fills older than `maxFillAgeSec` (a replay after downtime must not buy history), below
54
+ `minTargetNotionalUsdc`, or from a role you excluded;
55
+ - one copy per trader transaction, one position per (trader, outcome), at most `maxBuysPerOutcome` buys into it
56
+ (DCA), `maxOpenPositionsPerTarget` / `maxOpenPositions` / `maxDailySpendUsdc` caps;
57
+ - the market must be open, accepting orders and not settle within `minSecondsToEndDate`;
58
+ - the best ask must sit in `[minPrice, maxPrice]`, be at most `maxSlippage` above what the trader paid, and the
59
+ book must hold `minBookDepthUsdc`;
60
+ - order: **fill-or-kill for an exact number of shares** at the best ask (`orderSizeUsdc` / ask), rounded so the
61
+ CLOB accepts it. Share-denominated on purpose — a USDC-budget market order can overfill.
62
+
63
+ **SELL** (the trader sold)
64
+ - `sellMode: all` exits everything the bot bought following **that** trader in that outcome. Shares booked to
65
+ another trader in the same outcome are never sold: the most it sells is your token balance minus what the other
66
+ traders hold. If that comes to nothing, it stops and asks you to reconcile;
67
+ - the exit is saved and retried (with backoff, across restarts) until the position is gone. Freshness does not
68
+ apply: if the trader left while the bot was down, it still gets out;
69
+ - order: **fill-and-kill** at the best bid — a partial exit beats none, and the rest is retried.
70
+
71
+ **Safety**
72
+ - **dry-run by default**; live needs an explicit `mode: live`;
73
+ - **at most once**: a fill is marked decided in the state file *before* any order is sent, so a crash can miss a
74
+ copy but never place it twice. One bot per data directory: a second instance refuses to start;
75
+ - **no lost fills**: every order is saved as *pending* before it is sent. An order the exchange reports as killed
76
+ (such replies have carried real partial fills) is looked up again by its order id with backoff until its fill —
77
+ or its absence — is certain, even across restarts. One that got no answer at all has no id to look up: if a
78
+ matching trade appears, the bot does not guess whose it is (it could be yours by hand, or another trader's copy)
79
+ and hands it to you to `reconcile`;
80
+ - resolved markets are swept every 10 minutes so they stop counting against the caps.
81
+
82
+ **One stream per account.** PMWallets allows one WebSocket per account and the newest connection wins, so the bot
83
+ and the live-feed page on pmwallets.com (or a second bot) will take the stream from each other. Run one consumer
84
+ per account.
85
+
86
+ ## Development
87
+
88
+ ```bash
89
+ pip install -e '.[dev]' # or: uv pip install -e . pytest pytest-asyncio
90
+ pytest
91
+ ```
92
+
93
+ The same `config.yaml`, state files and lock file work with the Node.js version. `testdata/` holds the
94
+ contract cases both implementations must pass — keep it identical in both repositories.
95
+
96
+ ## Resources
97
+
98
+ - [Polymarket smart-money leaderboard](https://pmwallets.com) — profitable Polymarket traders scored from the Polygon chain, with win-rate confidence intervals
99
+ - [Polymarket copy trading guide](https://pmwallets.com/copy-trading) — which wallets are worth following and how to get their fills in time
100
+ - [How to learn from Polymarket smart money](https://pmwallets.com/learn) — reading a trader's record: confidence intervals, maker vs taker, market specialism
101
+ - [PMWallets API documentation](https://pmwallets.com/docs) — WebSocket and webhook fill push, fills replay, trade-history exports
102
+ - [Measured fill-push latency](https://pmwallets.com/latency) — block-to-push p50 / p95, published live
103
+ - [Ways to follow Polymarket wallets, compared](https://pmwallets.com/compare) — official leaderboard, free trackers, SQL dashboards
104
+ - [FAQ](https://pmwallets.com/faq) · [中文站](https://pmwallets.com/zh)
105
+
106
+ ## Proxies
107
+
108
+ `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` are honoured for the PMWallets API, the WebSocket and the Polymarket CLOB.
109
+
110
+ ## Disclaimer
111
+
112
+ This software places real orders with real money when `mode: live`. A copied order is not the trader's order: the
113
+ price may have moved, fees apply and liquidity is shared. Past results do not predict future ones. PMWallets
114
+ provides data about what traders did, not advice. Use at your own risk; see [LICENSE](LICENSE) (MIT).
@@ -0,0 +1,108 @@
1
+ # Polymarket 跟单机器人(Python)
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/pmwallets-copytrade.svg)](https://pypi.org/project/pmwallets-copytrade/) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
4
+
5
+ 基于 [PMWallets](https://pmwallets.com/zh/copy-trading) 的**开箱即用 Polymarket 跟单机器人**:跟随你订阅的交易者,
6
+ 用你自己的账户在 Polymarket CLOB 上按你设定的限制跟单。
7
+
8
+ [`pmwallets-copytrade` on PyPI](https://pypi.org/project/pmwallets-copytrade/) · [English](README.md) · Node.js 版:[polymarket-copy-trading-bot](https://github.com/polymarketwallets/polymarket-copy-trading-bot) ·
9
+ SDK:[pmwallets-python](https://github.com/polymarketwallets/pmwallets-python)
10
+
11
+ ## 它做什么
12
+
13
+ 1. **你来挑交易者**:在 [pmwallets.com](https://pmwallets.com/zh) 按胜率置信区间下限、去掉最赚一场后的盈利、
14
+ 活跃度与风格筛选,然后订阅。
15
+ 2. **机器人接收他们的每一笔成交**:走 PMWallets 的 WebSocket,通常在出块后一秒内到达
16
+ ([实测延迟](https://pmwallets.com/zh/latency))。漏掉的帧通过 `session`/`seq` 发现,并从最后处理的那笔
17
+ 开始补发 —— 机器人停机期间发生的也会补上。
18
+ 3. **在 Polymarket 上用你自己的账户跟单**,严格限制在你设定的范围内。私钥只在你本机,PMWallets 看不到,
19
+ 也从不代你下单。
20
+
21
+ ## 快速开始
22
+
23
+ ```bash
24
+ # Python ≥ 3.10
25
+ pip install pmwallets-copytrade
26
+ pmwallets-copytrade init # 生成 config.yaml
27
+ export PMW_API_KEY=pmw_... # 在 https://pmwallets.com/keys 创建
28
+ pmwallets-copytrade run # 默认 dry-run:记录每个决策,不下任何单
29
+ ```
30
+
31
+ dry-run 日志确认没问题后再切到实盘:
32
+
33
+ ```yaml
34
+ mode: live
35
+ polymarket:
36
+ privateKey: ${POLY_PRIVATE_KEY} # 用来签名订单
37
+ signatureType: 2 # 0 普通钱包 · 1 邮箱/Magic 登录 · 2 浏览器钱包登录
38
+ funderAddress: ${POLY_FUNDER_ADDRESS} # 你的 Polymarket 主页地址(USDC 在这里)
39
+ ```
40
+
41
+ `pmwallets-copytrade status` 显示持仓、今日花费和仍在确认中的订单。如果机器人自己无法确认某笔订单是否成交,
42
+ 它会保留这笔订单的额度预留(后续买入不会因此突破你的上限),并请你到 polymarket.com 核对后,在机器人停止时运行
43
+ `pmwallets-copytrade reconcile <key> --none` 或 `--filled <股数> --usdc <金额>`。每一个决策(包括每次跳过及原因)都追加写入
44
+ `pmw-data/decisions.<mode>.jsonl`。
45
+
46
+ ## 决策规则
47
+
48
+ 这些规则来自一个在 Polymarket 上实盘跑过的跟单机器人,每一条都对应一次丢单或一次被拒的订单。
49
+
50
+ **买入**(交易者买了)
51
+ - 跳过:超过 `maxFillAgeSec` 的旧成交(停机后的补发不能拿历史去买入)、低于 `minTargetNotionalUsdc` 的小单、
52
+ 你排除掉的角色(挂单/吃单);
53
+ - 每笔交易者交易只跟一次;每个(交易者,结果)一个仓位,最多加仓 `maxBuysPerOutcome` 次;另有
54
+ `maxOpenPositionsPerTarget` / `maxOpenPositions` / `maxDailySpendUsdc` 上限;
55
+ - 市场必须开放、接受订单,且不会在 `minSecondsToEndDate` 内结算;
56
+ - 最优卖价必须在 `[minPrice, maxPrice]` 内、比交易者成交价高出不超过 `maxSlippage`,盘口深度至少
57
+ `minBookDepthUsdc`;
58
+ - 下单:在最优卖价按**固定股数 FOK**(`orderSizeUsdc` / 卖价),按 CLOB 的精度规则取整。故意按股数而不是按
59
+ USDC 预算下单 —— 按预算的市价单会超额成交。
60
+
61
+ **卖出**(交易者卖了)
62
+ - `sellMode: all`:卖掉跟着**这位**交易者在该结果上买入的全部仓位。记在其他交易者名下的同一结果绝不动:
63
+ 最多卖「账户持有量 − 其他交易者的账面仓位」;如果算下来为零,就停下来提示你对账;
64
+ - 退出指令会落盘,按退避重试(重启后继续),直到仓位清掉。新鲜度限制不适用于卖出:交易者在机器人停机时
65
+ 退出了,机器人照样要退;
66
+ - 下单:在最优买价 **FAK**(能成交多少算多少)—— 部分退出好过完全退不出,剩下的继续重试。
67
+
68
+ **安全**
69
+ - **默认 dry-run**,实盘必须显式写 `mode: live`;
70
+ - **最多执行一次**:一笔成交在发出任何订单**之前**就写进状态文件标记为已决策,崩溃最多漏跟一次,绝不会重复下单。
71
+ 一个数据目录只能跑一个机器人,第二个实例会拒绝启动;
72
+ - **不漏记成交**:每笔订单发出前先落盘为「待确认」。交易所回报「被取消」的(这类回报曾带着真实的部分成交),
73
+ 会按订单号退避反复查询,直到确认成交或确认没有成交 —— 重启后也会继续。根本没收到回报的订单没有订单号可查:
74
+ 如果出现形状相符的成交,机器人不会去猜它属于谁(可能是你手动下的,也可能是跟另一位交易者的单),而是交给你
75
+ `reconcile`;
76
+ - 每 10 分钟清理一次已结算的市场,让它们不再占用仓位上限。
77
+
78
+ **每个账户只有一条推送流。** PMWallets 每个账户只允许一条 WebSocket,后连上的会顶掉先连的 —— 所以机器人
79
+ 和 pmwallets.com 上的实时推送页(或者第二个机器人)会互相踢。一个账户只跑一个消费者。
80
+
81
+ ## 开发
82
+
83
+ ```bash
84
+ pip install -e '.[dev]' # or: uv pip install -e . pytest pytest-asyncio
85
+ pytest
86
+ ```
87
+
88
+ `config.yaml`、状态文件和锁文件与 Node.js 版通用。`testdata/` 是两个实现都必须通过的契约用例 —— 两个仓库里保持一致。
89
+
90
+ ## 相关链接
91
+
92
+ - [Polymarket 聪明钱排行榜](https://pmwallets.com/zh) —— 从 Polygon 链上计算的 Polymarket 盈利交易者,胜率带置信区间
93
+ - [Polymarket 跟单指南](https://pmwallets.com/zh/copy-trading) —— 哪些钱包值得跟,以及怎样及时拿到他们的成交
94
+ - [怎样向 Polymarket 聪明钱学习](https://pmwallets.com/zh/learn) —— 读懂一份战绩:置信区间、挂单与吃单、擅长的市场
95
+ - [PMWallets API 文档](https://pmwallets.com/zh/docs) —— WebSocket 与 Webhook 成交推送、补发接口、交易历史导出
96
+ - [成交推送实测延迟](https://pmwallets.com/zh/latency) —— 出块到推送的 p50 / p95,实时公布
97
+ - [追踪 Polymarket 钱包的几种做法对比](https://pmwallets.com/zh/compare) —— 官方榜单、免费追踪器、SQL 看板
98
+ - [常见问题](https://pmwallets.com/zh/faq) · [English site](https://pmwallets.com)
99
+
100
+ ## 代理
101
+
102
+ PMWallets API、WebSocket 与 Polymarket CLOB 都会读取 `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`。
103
+
104
+ ## 免责声明
105
+
106
+ `mode: live` 时本软件会用真金白银下真实订单。跟出去的单不是交易者的那一单:价格可能已经变了、有手续费、
107
+ 流动性是共享的。过往结果不预示未来。PMWallets 提供的是交易者做了什么的数据,不是投资建议。风险自负;
108
+ 许可证见 [LICENSE](LICENSE)(MIT)。
@@ -0,0 +1,45 @@
1
+ # pmwallets-copytrade — copy the Polymarket traders you follow on PMWallets.
2
+ #
3
+ # 1. Create an API key on https://pmwallets.com/keys and subscribe to the traders you want to copy.
4
+ # 2. Run in dry-run first (the default): every decision is logged, nothing is traded.
5
+ # 3. When the log looks right, set `mode: live` and fill in the polymarket section.
6
+ #
7
+ # ${NAME} is read from the environment — keep secrets out of this file.
8
+
9
+ mode: dry-run # dry-run | live
10
+
11
+ pmwallets:
12
+ apiKey: ${PMW_API_KEY}
13
+
14
+ polymarket:
15
+ # only needed for live mode
16
+ # privateKey: ${POLY_PRIVATE_KEY} # the key that signs your Polymarket orders
17
+ # signatureType: 2 # 0 = plain EOA wallet, 1 = email/Magic login, 2 = browser-wallet login
18
+ # funderAddress: ${POLY_FUNDER_ADDRESS} # your Polymarket profile address (holds the USDC)
19
+
20
+ # Leave empty to copy every entity your PMWallets account subscribes to.
21
+ targets: []
22
+ # - entity: "0x9d84ce0306f8551e02efef1680475fc0f1dc1344"
23
+ # orderSizeUsdc: 20 # override per target
24
+ # - entity: "7KQ2MF9X4B1C" # a handle works once you subscribe to it
25
+
26
+ copy:
27
+ orderSizeUsdc: 10 # USDC per mirrored BUY
28
+ roles: [taker, maker] # which of the target's fills to act on
29
+ maxBuysPerOutcome: 3 # follow up to 3 BUYs into one outcome (DCA), then hold
30
+ maxOpenPositions: 20
31
+ maxOpenPositionsPerTarget: 5
32
+ maxFillAgeSec: 60 # older fills (e.g. replayed after downtime) are not copied
33
+ minTargetNotionalUsdc: 25 # ignore the target's small fills
34
+ minPrice: 0.05 # only BUY when the best ask is inside this band
35
+ maxPrice: 0.95
36
+ maxSlippage: 0.03 # skip if the ask is more than 3 cents above what the target paid
37
+ minBookDepthUsdc: 50
38
+ minSecondsToEndDate: 600 # don't enter a market that settles within 10 minutes
39
+ maxSecondsToEndDate: 0 # 0 = no upper limit
40
+ sellMode: all # all = exit when the target sells; none = hold to settlement
41
+
42
+ risk:
43
+ maxDailySpendUsdc: 200 # total BUYs per UTC day; 0 = no limit
44
+
45
+ dataDir: ./pmw-data # state, stream cursor and decisions.<mode>.jsonl
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pmwallets-copytrade"
7
+ version = "0.1.0"
8
+ description = "Ready-to-run Polymarket copy-trading bot: follows the wallets you subscribe to on PMWallets and mirrors their trades on the Polymarket CLOB, with dry-run, risk caps and exactly-once fill handling."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ keywords = ["polymarket", "copy-trading", "copytrade", "trading-bot", "prediction-markets", "pmwallets"]
13
+ dependencies = ["pmwallets>=0.1.0", "py-clob-client-v2>=1.1.0", "pyyaml>=6", "httpx>=0.27"]
14
+
15
+ [project.scripts]
16
+ pmwallets-copytrade = "pmwallets_copytrade.cli:main"
17
+
18
+ [project.optional-dependencies]
19
+ dev = ["pytest>=8", "pytest-asyncio>=0.23"]
20
+
21
+ [project.urls]
22
+ Homepage = "https://pmwallets.com/copy-trading"
23
+ Repository = "https://github.com/polymarketwallets/polymarket-copy-trading-bot-python"
24
+ Issues = "https://github.com/polymarketwallets/polymarket-copy-trading-bot-python/issues"
25
+
26
+ [tool.hatch.build.targets.wheel]
27
+ packages = ["src/pmwallets_copytrade"]
28
+
29
+ [tool.hatch.build.targets.wheel.force-include]
30
+ "config.example.yaml" = "pmwallets_copytrade/config.example.yaml"
31
+
32
+ [tool.pytest.ini_options]
33
+ asyncio_mode = "auto"
34
+ testpaths = ["tests"]
@@ -0,0 +1,34 @@
1
+ """Manual smoke test against the real, public Polymarket CLOB (read-only). Not part of pytest:
2
+ python scripts/live_readonly_smoke.py
3
+ """
4
+ import asyncio
5
+
6
+ import httpx
7
+
8
+ from pmwallets_copytrade.config import build_config
9
+ from pmwallets_copytrade.filters import book_gate, market_gate
10
+ from pmwallets_copytrade.log import ConsoleLogger
11
+ from pmwallets_copytrade.polymarket import PolymarketGateway
12
+ from pmwallets_copytrade.units import from_micro
13
+
14
+
15
+ async def main() -> None:
16
+ cfg = build_config({"pmwallets": {"apiKey": "pmw_x_y"}})
17
+ pm = PolymarketGateway(cfg.polymarket, ConsoleLogger())
18
+ async with httpx.AsyncClient(timeout=15) as http:
19
+ sample = (await http.get("https://clob.polymarket.com/sampling-markets")).json()
20
+ for m in sample["data"][:3]:
21
+ token_id = m["tokens"][0]["token_id"]
22
+ cid = await pm.condition_id_for(token_id)
23
+ market = await pm.market(cid)
24
+ book = await pm.orderbook(token_id)
25
+ print({
26
+ "question": market.question[:50], "cidMatches": cid == m["condition_id"], "endDate": market.endDate,
27
+ "bestAsk": book.asks and from_micro(book.asks[0].price), "bestBid": book.bids and from_micro(book.bids[0].price),
28
+ "sorted": all(a.price <= b.price for a, b in zip(book.asks, book.asks[1:])) and all(a.price >= b.price for a, b in zip(book.bids, book.bids[1:])),
29
+ "tick": book.tickSize and from_micro(book.tickSize), "minOrder": book.minOrderSize and from_micro(book.minOrderSize),
30
+ "marketGate": market_gate(market, "buy", cfg.copy), "bookGate": book_gate(book, "buy", cfg.copy),
31
+ })
32
+
33
+
34
+ asyncio.run(main())
@@ -0,0 +1,3 @@
1
+ """pmwallets-copytrade — copy the Polymarket wallets you follow on PMWallets."""
2
+
3
+ __version__ = "0.1.0"