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.
- pmwallets_copytrade-0.1.0/.github/workflows/ci.yml +18 -0
- pmwallets_copytrade-0.1.0/.github/workflows/publish.yml +24 -0
- pmwallets_copytrade-0.1.0/.gitignore +9 -0
- pmwallets_copytrade-0.1.0/LICENSE +21 -0
- pmwallets_copytrade-0.1.0/PKG-INFO +134 -0
- pmwallets_copytrade-0.1.0/README.md +114 -0
- pmwallets_copytrade-0.1.0/README.zh.md +108 -0
- pmwallets_copytrade-0.1.0/config.example.yaml +45 -0
- pmwallets_copytrade-0.1.0/pyproject.toml +34 -0
- pmwallets_copytrade-0.1.0/scripts/live_readonly_smoke.py +34 -0
- pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/__init__.py +3 -0
- pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/cli.py +131 -0
- pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/config.py +190 -0
- pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/engine.py +508 -0
- pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/filters.py +88 -0
- pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/log.py +36 -0
- pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/polymarket.py +447 -0
- pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/run.py +162 -0
- pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/state.py +267 -0
- pmwallets_copytrade-0.1.0/src/pmwallets_copytrade/units.py +72 -0
- pmwallets_copytrade-0.1.0/testdata/config-contract.expected.json +15 -0
- pmwallets_copytrade-0.1.0/testdata/config-contract.yaml +23 -0
- pmwallets_copytrade-0.1.0/testdata/post-classification.json +14 -0
- pmwallets_copytrade-0.1.0/tests/test_config.py +67 -0
- pmwallets_copytrade-0.1.0/tests/test_engine.py +667 -0
- pmwallets_copytrade-0.1.0/tests/test_filters.py +49 -0
- pmwallets_copytrade-0.1.0/tests/test_polymarket.py +119 -0
- 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,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
|
+
[](https://pypi.org/project/pmwallets-copytrade/) [](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
|
+
[](https://pypi.org/project/pmwallets-copytrade/) [](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
|
+
[](https://pypi.org/project/pmwallets-copytrade/) [](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())
|