738 lines
22 KiB
Markdown
738 lines
22 KiB
Markdown
|
|
# DEPLOY.md - 手动部署 paper + live copybot 到新服务器
|
|||
|
|
|
|||
|
|
> 一步一步来,可以一边看一边敲。不要一次性复制粘贴大段命令--每一步做完、看到预期输出,再进下一步。
|
|||
|
|
>
|
|||
|
|
> 适用:把 paper bot + live bot 部署到一台新的 Linux 服务器(Ubuntu/Debian),从你自己的 Gitea 拉代码。如果服务器在 Polymarket 禁单地区(美国/英国/法国等),用 mihomo 代理绕开 geo-block。
|
|||
|
|
>
|
|||
|
|
> 完成时间参考:60-90 分钟(含每步等待 + live 配置)。
|
|||
|
|
>
|
|||
|
|
> 完成部署后提供:
|
|||
|
|
> - **paper bot**:24/7 模拟跟单(直连,不走代理)
|
|||
|
|
> - **live bot**:24/7 真钱跟单(走代理绕 geo-block)
|
|||
|
|
> - **bot 实时账本(中文 web dashboard)**:`http://服务器IP:18080/`
|
|||
|
|
> - **原始 JSON**:`https://<GITEA>/<USERNAME>/winning-wallet-finder/raw/branch/main/live/copybot_live.json`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
|
|||
|
|
- [Part A: Paper Bot 部署](#part-a-paper-bot-部署) (§0-§13)
|
|||
|
|
- [Part B: 代理配置(geo-block 绕行)](#part-b-代理配置geo-block-绕行) (§14-§16)
|
|||
|
|
- [Part C: Live Bot 部署](#part-c-live-bot-部署) (§17-§23)
|
|||
|
|
- [常见坑](#常见坑)
|
|||
|
|
- [日常命令](#日常用的命令)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Part A: Paper Bot 部署
|
|||
|
|
|
|||
|
|
## 0. 前置条件
|
|||
|
|
|
|||
|
|
- [ ] 一台 Linux VPS(Ubuntu 20.04+ / Debian 11+)
|
|||
|
|
- [ ] 你有 root SSH 权限
|
|||
|
|
- [ ] 你的 Gitea 上有这个仓库的镜像
|
|||
|
|
- [ ] Gitea PAT(权限 `repository: Write`)
|
|||
|
|
|
|||
|
|
测 Polymarket 直连:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python3 -c "
|
|||
|
|
import urllib.request, time
|
|||
|
|
for url in [
|
|||
|
|
'https://data-api.polymarket.com/v1/leaderboard?limit=1',
|
|||
|
|
'https://gamma-api.polymarket.com/markets?limit=1',
|
|||
|
|
'https://clob.polymarket.com/markets?next_cursor=',
|
|||
|
|
]:
|
|||
|
|
t = time.time()
|
|||
|
|
try:
|
|||
|
|
urllib.request.urlopen(urllib.request.Request(url, headers={'User-Agent':'Mozilla/5.0'}), timeout=10)
|
|||
|
|
print(f'OK {(time.time()-t)*1000:6.0f}ms {url}')
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f'FAIL {type(e).__name__}: {url}')
|
|||
|
|
"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
三行 `OK` = 能读 API(paper 够用)。
|
|||
|
|
|
|||
|
|
## 1. 装基础依赖
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ssh root@你的服务器IP
|
|||
|
|
apt-get update
|
|||
|
|
apt-get install -y git python3 python3-pip ca-certificates
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 2. 从 Gitea 拉代码
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /opt
|
|||
|
|
git clone https://<USERNAME>:<TOKEN>@<GITEA>/<USERNAME>/winning-wallet-finder.git
|
|||
|
|
cd winning-wallet-finder/live
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 3. 装 duckdb + 首次回测
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pip3 install duckdb --break-system-packages
|
|||
|
|
python3 portfolio.py --days 30
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
预期:`portfolio[30d rolling]: equity $... | ... | -> portfolio.json`
|
|||
|
|
|
|||
|
|
## 4. 前台测试 bot
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python3 ../copybot.py --config copybot.paper.json --state copybot_state.json --poll 60
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
看到 `watching N wallets` + 2-3 条心跳后 `Ctrl+C`。
|
|||
|
|
|
|||
|
|
## 5. 配 Gitea 凭证
|
|||
|
|
|
|||
|
|
### 5.1 生成 PAT
|
|||
|
|
|
|||
|
|
Gitea -> 用户设置 -> Applications -> Generate Token(权限 `repository: Write`)
|
|||
|
|
|
|||
|
|
### 5.2 写 .netrc
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
nano ~/.netrc
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
machine <GITEA域名>
|
|||
|
|
login <USERNAME>
|
|||
|
|
password <TOKEN>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
chmod 600 ~/.netrc
|
|||
|
|
cd /opt/winning-wallet-finder
|
|||
|
|
git push origin main --dry-run
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.3 root git 白名单
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git config --global --add safe.directory /opt/winning-wallet-finder
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 6. 建专用用户 wwf
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
useradd --system --shell /usr/sbin/nologin --home /opt/winning-wallet-finder wwf
|
|||
|
|
chown -R wwf:wwf /opt/winning-wallet-finder
|
|||
|
|
chmod -R u+rwX,g+rX,o-rwx /opt/winning-wallet-finder
|
|||
|
|
sudo cp /root/.netrc /opt/winning-wallet-finder/.netrc
|
|||
|
|
sudo chown wwf:wwf /opt/winning-wallet-finder/.netrc
|
|||
|
|
sudo chmod 600 /opt/winning-wallet-finder/.netrc
|
|||
|
|
sudo -u wwf git config --global user.name "copybot[bot]"
|
|||
|
|
sudo -u wwf git config --global user.email "copybot@wwf.local"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **坑**:wwf 没配 git identity 会导致 commit 报 `Author identity unknown`。上面最后两行不能漏。
|
|||
|
|
|
|||
|
|
## 7. 写 paper systemd unit
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /etc/systemd/system/copybot-paper.service <<'EOF'
|
|||
|
|
[Unit]
|
|||
|
|
Description=Polymarket Paper Copybot
|
|||
|
|
After=network-online.target
|
|||
|
|
Wants=network-online.target
|
|||
|
|
|
|||
|
|
[Service]
|
|||
|
|
Type=simple
|
|||
|
|
User=wwf
|
|||
|
|
Group=wwf
|
|||
|
|
WorkingDirectory=/opt/winning-wallet-finder/live
|
|||
|
|
ExecStart=/usr/bin/python3 /opt/winning-wallet-finder/copybot.py \
|
|||
|
|
--config /opt/winning-wallet-finder/live/copybot.paper.json \
|
|||
|
|
--state /opt/winning-wallet-finder/copybot_state.json \
|
|||
|
|
--poll 60
|
|||
|
|
|
|||
|
|
Restart=always
|
|||
|
|
RestartSec=10
|
|||
|
|
TimeoutStopSec=30
|
|||
|
|
Nice=5
|
|||
|
|
|
|||
|
|
StandardOutput=journal
|
|||
|
|
StandardError=journal
|
|||
|
|
SyslogIdentifier=copybot-paper
|
|||
|
|
|
|||
|
|
EnvironmentFile=-/etc/copybot/paper.env
|
|||
|
|
|
|||
|
|
NoNewPrivileges=true
|
|||
|
|
ProtectSystem=strict
|
|||
|
|
ProtectHome=true
|
|||
|
|
PrivateTmp=true
|
|||
|
|
PrivateDevices=true
|
|||
|
|
RestrictSUIDSGID=true
|
|||
|
|
RestrictNamespaces=true
|
|||
|
|
LockPersonality=true
|
|||
|
|
RestrictRealtime=true
|
|||
|
|
ProtectKernelTunables=true
|
|||
|
|
ProtectKernelModules=true
|
|||
|
|
ProtectControlGroups=true
|
|||
|
|
SystemCallArchitectures=native
|
|||
|
|
ReadWritePaths=/opt/winning-wallet-finder
|
|||
|
|
|
|||
|
|
[Install]
|
|||
|
|
WantedBy=multi-user.target
|
|||
|
|
EOF
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 8. 启动 paper bot
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
systemctl daemon-reload
|
|||
|
|
systemctl enable --now copybot-paper
|
|||
|
|
sleep 5
|
|||
|
|
systemctl status copybot-paper
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 9. 验证 Gitea 推送
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /opt/winning-wallet-finder
|
|||
|
|
sudo -u wwf git add -A
|
|||
|
|
sudo -u wwf git commit -m "manual sync"
|
|||
|
|
sudo -u wwf git push
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 10. 加 Alchemy RPC
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sudo install -d -m 750 -o wwf -g wwf /etc/copybot
|
|||
|
|
sudo tee /etc/copybot/paper.env > /dev/null <<EOF
|
|||
|
|
ALCHEMY_RPC_URL=https://polygon-mainnet.g.alchemy.com/v2/你的KEY
|
|||
|
|
EOF
|
|||
|
|
sudo chown wwf:wwf /etc/copybot/paper.env
|
|||
|
|
sudo chmod 600 /etc/copybot/paper.env
|
|||
|
|
sudo systemctl restart copybot-paper
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 11. 加每日 cron
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
timedatectl | grep "Time zone"
|
|||
|
|
# UTC 服务器 -> 北京 10:00 = cron 0 2
|
|||
|
|
(crontab -l 2>/dev/null; echo "0 2 * * * cd /opt/winning-wallet-finder/live && bash daily.sh >> daily.log 2>&1") | crontab -
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 12. 部署 web dashboard
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /etc/systemd/system/copybot-dashboard.service <<'EOF'
|
|||
|
|
[Unit]
|
|||
|
|
Description=Polymarket Copybot Live Dashboard
|
|||
|
|
After=network-online.target
|
|||
|
|
Wants=network-online.target
|
|||
|
|
|
|||
|
|
[Service]
|
|||
|
|
Type=simple
|
|||
|
|
User=wwf
|
|||
|
|
Group=wwf
|
|||
|
|
WorkingDirectory=/opt/winning-wallet-finder/live
|
|||
|
|
ExecStart=/usr/bin/python3 /opt/winning-wallet-finder/live/serve_dashboard.py
|
|||
|
|
Restart=always
|
|||
|
|
RestartSec=5
|
|||
|
|
Nice=10
|
|||
|
|
|
|||
|
|
StandardOutput=journal
|
|||
|
|
StandardError=journal
|
|||
|
|
SyslogIdentifier=copybot-dashboard
|
|||
|
|
|
|||
|
|
NoNewPrivileges=true
|
|||
|
|
ProtectSystem=strict
|
|||
|
|
ProtectHome=true
|
|||
|
|
PrivateTmp=true
|
|||
|
|
PrivateDevices=true
|
|||
|
|
RestrictNamespaces=true
|
|||
|
|
|
|||
|
|
[Install]
|
|||
|
|
WantedBy=multi-user.target
|
|||
|
|
EOF
|
|||
|
|
|
|||
|
|
systemctl daemon-reload
|
|||
|
|
systemctl enable --now copybot-dashboard
|
|||
|
|
sleep 3
|
|||
|
|
curl -s http://localhost:18080/api/feed | python3 -m json.tool | head -10
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
浏览器访问 `http://服务器IP:18080/`。如果打不开,放行防火墙 TCP 18080。
|
|||
|
|
|
|||
|
|
## 13. Paper 完成检查
|
|||
|
|
|
|||
|
|
- [x] paper bot systemd 24/7 跑
|
|||
|
|
- [x] web dashboard @ :18080
|
|||
|
|
- [x] Alchemy RPC ON
|
|||
|
|
- [x] 状态推 Gitea
|
|||
|
|
- [x] 每日 cron
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Part B: 代理配置(geo-block 绕行)
|
|||
|
|
|
|||
|
|
> **为什么要代理**:Polymarket 禁止美国/英国/法国/德国/意大利/荷兰/波兰/新加坡/澳大利亚/加拿大安大略/巴西 等地区的 IP 下单。如果你的服务器在这些地区,live bot 需要走代理。
|
|||
|
|
>
|
|||
|
|
> paper bot 不受影响(只读 API 全球开放)。
|
|||
|
|
|
|||
|
|
## 14. 检查 geo-gate
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /opt/winning-wallet-finder
|
|||
|
|
python3 host/geocheck.py
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `VERDICT: TRADABLE` -> 跳过 Part B,直接去 Part C
|
|||
|
|
- `VERDICT: BLOCKED` -> 继续往下配代理
|
|||
|
|
|
|||
|
|
## 15. 装 mihomo(Clash Meta 内核)
|
|||
|
|
|
|||
|
|
### 15.1 下载 mihomo
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mkdir -p /etc/mihomo
|
|||
|
|
cd /tmp
|
|||
|
|
wget -q https://github.com/MetaCubeX/mihomo/releases/download/v1.19.0/mihomo-linux-amd64-v1.19.0.gz
|
|||
|
|
gzip -d mihomo-linux-amd64-v1.19.0.gz
|
|||
|
|
mv mihomo-linux-amd64-v1.19.0 /usr/local/bin/mihomo
|
|||
|
|
chmod +x /usr/local/bin/mihomo
|
|||
|
|
mihomo -v
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 15.2 拉订阅配置
|
|||
|
|
|
|||
|
|
> **坑**:机场会检测 User-Agent,wget 默认 UA 会被返回假节点。必须伪装 UA。
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
wget -O /etc/mihomo/config.yaml \
|
|||
|
|
--user-agent="clash-verge/v1.7.7" \
|
|||
|
|
"你的订阅链接&flag=meta"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
验证拿到的是真实节点(不是"当前Clash客户端不支持本机场协议"):
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
grep "name:" /etc/mihomo/config.yaml | grep -i "日本\|香港\|新加坡" | head -10
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 15.3 改全局模式 + 启动
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sed -i 's/^mode: rule/mode: global/' /etc/mihomo/config.yaml
|
|||
|
|
|
|||
|
|
nohup mihomo -d /etc/mihomo > /var/log/mihomo.log 2>&1 &
|
|||
|
|
sleep 5
|
|||
|
|
|
|||
|
|
# 选一个非美国节点(以日本为例)
|
|||
|
|
curl -s -X PUT http://127.0.0.1:9090/proxies/GLOBAL \
|
|||
|
|
-H "Content-Type: application/json" \
|
|||
|
|
-d '{"name":"节点名"}'
|
|||
|
|
|
|||
|
|
# 验证
|
|||
|
|
HTTPS_PROXY=http://127.0.0.1:7890 python3 host/geocheck.py
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
期望 `VERDICT: TRADABLE`。
|
|||
|
|
|
|||
|
|
### 15.4 做 systemd 服务
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
kill $(pgrep mihomo)
|
|||
|
|
|
|||
|
|
cat > /etc/systemd/system/mihomo.service <<'EOF'
|
|||
|
|
[Unit]
|
|||
|
|
Description=Mihomo (Clash Meta) Proxy
|
|||
|
|
After=network-online.target
|
|||
|
|
Wants=network-online.target
|
|||
|
|
|
|||
|
|
[Service]
|
|||
|
|
Type=simple
|
|||
|
|
ExecStart=/usr/local/bin/mihomo -d /etc/mihomo
|
|||
|
|
Restart=always
|
|||
|
|
RestartSec=5
|
|||
|
|
StandardOutput=journal
|
|||
|
|
StandardError=journal
|
|||
|
|
SyslogIdentifier=mihomo
|
|||
|
|
NoNewPrivileges=true
|
|||
|
|
|
|||
|
|
[Install]
|
|||
|
|
WantedBy=multi-user.target
|
|||
|
|
EOF
|
|||
|
|
|
|||
|
|
systemctl daemon-reload
|
|||
|
|
systemctl enable --now mihomo
|
|||
|
|
sleep 5
|
|||
|
|
|
|||
|
|
# 重选节点(mihomo 重启会记住,但第一次要手动选)
|
|||
|
|
curl -s -X PUT http://127.0.0.1:9090/proxies/GLOBAL \
|
|||
|
|
-H "Content-Type: application/json" \
|
|||
|
|
-d '{"name":"节点名"}'
|
|||
|
|
|
|||
|
|
# 验证代理 + geo-gate
|
|||
|
|
HTTPS_PROXY=http://127.0.0.1:7890 python3 host/geocheck.py | tail -1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **mihomo 会记住节点选择**:`cache.db` 持久化,重启不丢。
|
|||
|
|
|
|||
|
|
### 15.5 自动健康检查(节点挂了自动换)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /opt/mihomo-healthcheck.sh <<'SCRIPT'
|
|||
|
|
#!/bin/bash
|
|||
|
|
API="http://127.0.0.1:9090"
|
|||
|
|
PROXY="http://127.0.0.1:7890"
|
|||
|
|
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -x $PROXY \
|
|||
|
|
"https://clob.polymarket.com/markets?next_cursor=" 2>/dev/null)
|
|||
|
|
if [ "$code" = "200" ]; then exit 0; fi
|
|||
|
|
nodes=$(curl -s "$API/proxies" | python3 -c "
|
|||
|
|
import sys, json
|
|||
|
|
d = json.load(sys.stdin)
|
|||
|
|
skip = {'DIRECT','REJECT','自动选择','故障转移','良心云','GLOBAL'}
|
|||
|
|
for name, info in d.get('proxies', {}).items():
|
|||
|
|
if name in skip: continue
|
|||
|
|
if info.get('type') in ('Selector','URLTest','Fallback','LoadBalance'): continue
|
|||
|
|
if any(k in name for k in ['美国','US','United States']): continue
|
|||
|
|
print(name)
|
|||
|
|
")
|
|||
|
|
for node in $nodes; do
|
|||
|
|
curl -s -X PUT "$API/proxies/GLOBAL" -H "Content-Type: application/json" -d "{\"name\":\"$node\"}" > /dev/null
|
|||
|
|
sleep 2
|
|||
|
|
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -x $PROXY \
|
|||
|
|
"https://clob.polymarket.com/markets?next_cursor=" 2>/dev/null)
|
|||
|
|
if [ "$code" = "200" ]; then
|
|||
|
|
logger "mihomo-healthcheck: switched to $node"
|
|||
|
|
exit 0
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
logger "mihomo-healthcheck: ALL nodes failed"
|
|||
|
|
exit 1
|
|||
|
|
SCRIPT
|
|||
|
|
|
|||
|
|
chmod +x /opt/mihomo-healthcheck.sh
|
|||
|
|
(crontab -l 2>/dev/null | grep -v mihomo-healthcheck; echo "*/5 * * * * /opt/mihomo-healthcheck.sh") | crontab -
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **代理只影响 live bot**:mihomo 监听 `127.0.0.1:7890`,不是透明代理。只有设了 `HTTPS_PROXY` 的程序才走代理。paper bot / dashboard / Gitea 全部直连,不受影响。
|
|||
|
|
>
|
|||
|
|
> **不要**把 `HTTPS_PROXY` 写进 `/etc/environment` 或 `~/.bashrc`(会全局生效),只放在 `/etc/copybot/live.env` 里。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Part C: Live Bot 部署
|
|||
|
|
|
|||
|
|
> **真钱!** 每一步验证完再进下一步。建议先 $3-5 测试。
|
|||
|
|
|
|||
|
|
## 17. 装 live 依赖
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pip3 install py-clob-client==0.34.6 web3==7.16.0 polymarket-client==0.1.0b16 websocket-client --break-system-packages
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **坑**:`preflight_live.py` 需要 `polymarket-client`(不是 `py-clob-client`),漏装会报 `ModuleNotFoundError: No module named 'polymarket'`。
|
|||
|
|
|
|||
|
|
## 18. 准备钱包
|
|||
|
|
|
|||
|
|
### 18.1 钱包架构说明
|
|||
|
|
|
|||
|
|
Polymarket 2026 新架构有两套钱包系统:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
你的私钥(MetaMask 导出)
|
|||
|
|
|
|
|||
|
|
+-- EOA 地址(MetaMask 本身)
|
|||
|
|
+-- 代理钱包(邮箱登录自动创建,POLY_PROXY type=1)
|
|||
|
|
+-- 存款钱包(API 自动创建,POLY_1271 type=3)<- bot 用这个
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**bot 只能用存款钱包**。pUSD 在 EOA 或代理钱包里 bot 看不到。
|
|||
|
|
|
|||
|
|
### 18.2 用 MetaMask 登录(推荐,比邮箱简单)
|
|||
|
|
|
|||
|
|
1. 浏览器装 MetaMask,创建新钱包
|
|||
|
|
2. 用 MetaMask 登录 polymarket.com
|
|||
|
|
3. MetaMask -> 账户详情 -> 导出私钥
|
|||
|
|
|
|||
|
|
### 18.3 创建存款钱包
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /opt/winning-wallet-finder
|
|||
|
|
export HTTPS_PROXY=http://127.0.0.1:7890
|
|||
|
|
export LIVE_PRIVATE_KEY=0x你的MetaMask私钥
|
|||
|
|
|
|||
|
|
python3 -c "
|
|||
|
|
from polymarket import SecureClient
|
|||
|
|
client = SecureClient.create(private_key='$LIVE_PRIVATE_KEY')
|
|||
|
|
print('存款钱包:', client.wallet)
|
|||
|
|
b = client.get_balance_allowance(asset_type='COLLATERAL')
|
|||
|
|
print('pUSD 余额: \$' + str(b.balance / 1e6))
|
|||
|
|
client.close()
|
|||
|
|
"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **坑(邮箱登录用户)**:如果用邮箱登录的私钥(Magic.link 导出),`SecureClient.create()` 会报 `Gasless transactions require a Builder API Key`。需要用 Builder API 凭证创建存款钱包:
|
|||
|
|
> ```python
|
|||
|
|
> bk = BuilderApiKey(key=..., secret=..., passphrase=...)
|
|||
|
|
> client = SecureClient.create(private_key=pk, api_key=bk)
|
|||
|
|
> ```
|
|||
|
|
> Builder 凭证在 polymarket.com -> Settings -> API Keys 获取。创建完存款钱包后不再需要 Builder 凭证。
|
|||
|
|
>
|
|||
|
|
> **MetaMask 登录用户不受此影响**,存款钱包自动创建。
|
|||
|
|
|
|||
|
|
### 18.4 充值 USDC -> pUSD
|
|||
|
|
|
|||
|
|
**关键**:必须充到**存款钱包地址**,不是 MetaMask 地址。
|
|||
|
|
|
|||
|
|
1. 从交易所提 USDC 到 Polygon 链
|
|||
|
|
2. 接收地址 = 步骤 18.3 打印的存款钱包地址
|
|||
|
|
3. 到账后 USDC 自动转成 pUSD
|
|||
|
|
|
|||
|
|
> **坑**:充到 Polymarket 网站显示的充值地址(代理钱包)是错的,bot 在存款钱包里看不到。必须充到存款钱包地址。
|
|||
|
|
|
|||
|
|
### 18.5 验证余额
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
export HTTPS_PROXY=http://127.0.0.1:7890
|
|||
|
|
export LIVE_PRIVATE_KEY=0x你的私钥
|
|||
|
|
python3 -c "
|
|||
|
|
from polymarket import SecureClient
|
|||
|
|
client = SecureClient.create(private_key='$LIVE_PRIVATE_KEY')
|
|||
|
|
b = client.get_balance_allowance(asset_type='COLLATERAL')
|
|||
|
|
print('pUSD: \$' + str(b.balance / 1e6))
|
|||
|
|
client.close()
|
|||
|
|
"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 19. 创建 config.live.json
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /opt/winning-wallet-finder
|
|||
|
|
cp config.live.example.json config.live.json
|
|||
|
|
# 改 bankroll_usd 为实际 pUSD 余额
|
|||
|
|
sed -i 's/"bankroll_usd": 22.28/"bankroll_usd": 你的余额/' config.live.json
|
|||
|
|
chown wwf:wwf config.live.json
|
|||
|
|
chmod 640 config.live.json
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **坑**:config.live.json 是 root 创建的,wwf 用户读不了会报 `PermissionError`。必须 `chown wwf:wwf`。
|
|||
|
|
|
|||
|
|
## 20. 创建 live.env
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /etc/copybot/live.env <<'EOF'
|
|||
|
|
LIVE_PRIVATE_KEY=0x你的MetaMask私钥
|
|||
|
|
LIVE_FUNDER_ADDRESS=0x你的MetaMask地址
|
|||
|
|
LIVE_SIGNATURE_TYPE=1
|
|||
|
|
LIVE_CONFIRM=TRADE LIVE
|
|||
|
|
HTTPS_PROXY=http://127.0.0.1:7890
|
|||
|
|
EOF
|
|||
|
|
|
|||
|
|
chown wwf:wwf /etc/copybot/live.env
|
|||
|
|
chmod 600 /etc/copybot/live.env
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **坑 1**:`LIVE_CONFIRM` 的值必须是 `TRADE LIVE`(copytrade.py:48 的 `CONFIRM_PHRASE`),不是 `I understand real money is at risk`。填错会报 `Aborted - LIVE_CONFIRM is set but does not match the phrase`。
|
|||
|
|
>
|
|||
|
|
> **坑 2**:systemd EnvironmentFile 里值**不要加引号**。`LIVE_CONFIRM=TRADE LIVE` 是对的,`LIVE_CONFIRM="TRADE LIVE"` 会让值带引号导致不匹配。
|
|||
|
|
>
|
|||
|
|
> **坑 3**:`HTTPS_PROXY` 只在 live.env 里设,不要放 paper.env(paper bot 不需要代理)。
|
|||
|
|
|
|||
|
|
## 21. 跑 preflight
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /opt/winning-wallet-finder
|
|||
|
|
export LIVE_PRIVATE_KEY=$(grep LIVE_PRIVATE_KEY /etc/copybot/live.env | cut -d= -f2-)
|
|||
|
|
export LIVE_FUNDER_ADDRESS=$(grep LIVE_FUNDER_ADDRESS /etc/copybot/live.env | cut -d= -f2-)
|
|||
|
|
export HTTPS_PROXY=http://127.0.0.1:7890
|
|||
|
|
python3 preflight_live.py
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
全绿才能继续。常见失败:
|
|||
|
|
- `ModuleNotFoundError: No module named 'websocket'` -> `pip3 install websocket-client`
|
|||
|
|
- `Gasless transactions require a Builder API Key` -> 见 §18.3 邮箱登录的坑
|
|||
|
|
- `Invalid private_key: 'ascii' codec can't encode` -> 私钥里有非 ASCII 字符(中文占位符没替换)
|
|||
|
|
|
|||
|
|
## 22. 写 live systemd unit
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /etc/systemd/system/copybot-live.service <<'EOF'
|
|||
|
|
[Unit]
|
|||
|
|
Description=Polymarket LIVE Copybot (real money)
|
|||
|
|
After=network-online.target mihomo.service
|
|||
|
|
Wants=network-online.target
|
|||
|
|
Requires=mihomo.service
|
|||
|
|
|
|||
|
|
[Service]
|
|||
|
|
Type=simple
|
|||
|
|
User=wwf
|
|||
|
|
Group=wwf
|
|||
|
|
WorkingDirectory=/opt/winning-wallet-finder/live
|
|||
|
|
ExecStart=/usr/bin/python3 /opt/winning-wallet-finder/copybot.py \
|
|||
|
|
--config /opt/winning-wallet-finder/config.live.json \
|
|||
|
|
--state /opt/winning-wallet-finder/copybot_state.live.json \
|
|||
|
|
--poll 60 --live
|
|||
|
|
|
|||
|
|
Restart=always
|
|||
|
|
RestartSec=10
|
|||
|
|
TimeoutStopSec=30
|
|||
|
|
Nice=5
|
|||
|
|
|
|||
|
|
StandardOutput=journal
|
|||
|
|
StandardError=journal
|
|||
|
|
SyslogIdentifier=copybot-live
|
|||
|
|
|
|||
|
|
EnvironmentFile=/etc/copybot/live.env
|
|||
|
|
|
|||
|
|
NoNewPrivileges=true
|
|||
|
|
ProtectSystem=strict
|
|||
|
|
ProtectHome=true
|
|||
|
|
PrivateTmp=true
|
|||
|
|
PrivateDevices=true
|
|||
|
|
RestrictSUIDSGID=true
|
|||
|
|
RestrictNamespaces=true
|
|||
|
|
LockPersonality=true
|
|||
|
|
RestrictRealtime=true
|
|||
|
|
ProtectKernelTunables=true
|
|||
|
|
ProtectKernelModules=true
|
|||
|
|
ProtectControlGroups=true
|
|||
|
|
SystemCallArchitectures=native
|
|||
|
|
ReadWritePaths=/opt/winning-wallet-finder
|
|||
|
|
|
|||
|
|
[Install]
|
|||
|
|
WantedBy=multi-user.target
|
|||
|
|
EOF
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> `Requires=mihomo.service` + `After=mihomo.service`:代理先启动,live bot 才启动。
|
|||
|
|
|
|||
|
|
## 23. 启动 live bot
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
systemctl daemon-reload
|
|||
|
|
systemctl enable --now copybot-live
|
|||
|
|
sleep 10
|
|||
|
|
journalctl -u copybot-live --since "15 seconds ago" --no-pager
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
预期看到:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
geo-gate: TRADABLE - order endpoint reachable
|
|||
|
|
LIVE MODE - this will place REAL orders with REAL money.
|
|||
|
|
confirmed via LIVE_CONFIRM env - armed.
|
|||
|
|
mode: LIVE - REAL MONEY
|
|||
|
|
watching 5 wallets
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 清空 live state(如果出现 LEDGER DRIFT)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
systemctl stop copybot-live
|
|||
|
|
rm -f /opt/winning-wallet-finder/copybot_state.live.json
|
|||
|
|
rm -f /opt/winning-wallet-finder/copybot_fills.live.jsonl
|
|||
|
|
rm -f /opt/winning-wallet-finder/live/copybot_live_real.json
|
|||
|
|
systemctl start copybot-live
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 监控第一笔成交
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
journalctl -u copybot-live -f
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
看到 `FOLLOW` + `[LIVE] order placed` = 第一笔真钱成交。然后去 polymarket.com 用 MetaMask 登录确认仓位。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 完整部署架构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
服务器
|
|||
|
|
|
|
|||
|
|
+-- mihomo.service 代理(日本节点,127.0.0.1:7890)
|
|||
|
|
| +-- healthcheck cron 每 5 分钟自动换节点
|
|||
|
|
|
|
|||
|
|
+-- copybot-paper.service paper bot(直连,不走代理)
|
|||
|
|
| +-- EnvironmentFile: paper.env(ALCHEMY_RPC_URL)
|
|||
|
|
| +-- state: copybot_state.json
|
|||
|
|
| +-- feed: live/copybot_live.json
|
|||
|
|
|
|
|||
|
|
+-- copybot-live.service live bot(走代理绕 geo-block)
|
|||
|
|
| +-- EnvironmentFile: live.env(LIVE_PRIVATE_KEY + HTTPS_PROXY + TRADE LIVE)
|
|||
|
|
| +-- state: copybot_state.live.json
|
|||
|
|
| +-- feed: live/copybot_live_real.json
|
|||
|
|
|
|
|||
|
|
+-- copybot-dashboard.service web dashboard(:18080)
|
|||
|
|
|
|
|||
|
|
+-- cron 每日 02:00 UTC daily.sh
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 常见坑
|
|||
|
|
|
|||
|
|
| 坑 | 症状 | 解法 |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| 没装 duckdb | `ModuleNotFoundError: No module named 'duckdb'` | `pip3 install duckdb --break-system-packages` |
|
|||
|
|
| 没装 polymarket-client | `ModuleNotFoundError: No module named 'polymarket'` | `pip3 install polymarket-client==0.1.0b16` |
|
|||
|
|
| 没装 websocket-client | preflight 报 `No module named 'websocket'` / 日志 `rtds off` | `pip3 install websocket-client` |
|
|||
|
|
| wwf 没配 git identity | commit 报 `Author identity unknown` | §6 最后两行 |
|
|||
|
|
| root 看不到 wwf 仓库 | `dubious ownership` | `git config --global --add safe.directory ...` |
|
|||
|
|
| config.live.json 权限 | `PermissionError: [Errno 13]` | `chown wwf:wwf config.live.json` |
|
|||
|
|
| LIVE_CONFIRM 值错 | `Aborted - LIVE_CONFIRM is set but does not match` | 值必须是 `TRADE LIVE`,不加引号 |
|
|||
|
|
| 私钥有非 ASCII 字符 | `UnicodeEncodeError: 'ascii' codec can't encode` | env 文件里私钥必须是纯 hex(0x + 64 位),不能有中文占位符 |
|
|||
|
|
| 邮箱登录建存款钱包失败 | `Gasless transactions require a Builder API Key` | 用 Builder API 凭证创建(§18.3),或改用 MetaMask 登录 |
|
|||
|
|
| 充错钱包 | bot 余额 $0 | 必须充到存款钱包地址,不是 MetaMask 地址或网站充值地址 |
|
|||
|
|
| geo-gate BLOCKED | `VERDICT: BLOCKED` | 服务器在禁单区,需配代理(Part B) |
|
|||
|
|
| 机场返回假节点 | 节点名是"当前Clash客户端不支持" | wget 加 `--user-agent="clash-verge/v1.7.7"` + `&flag=meta` |
|
|||
|
|
| mihomo 重启丢节点 | 出口 IP 变回美国 | mihomo cache.db 自动记忆;第一次需手动选节点 |
|
|||
|
|
| LEDGER DRIFT | `check_book could not attribute it` | 清空 live state 重来(§23) |
|
|||
|
|
| bot 不自动 commit | dashboard 数据陈旧 | commit-on-change 节流,无新交易不写;手动 commit 见 §9 |
|
|||
|
|
| 浏览器 dashboard 不更新 | 数据没变 | `Ctrl+Shift+R` 强制刷新 |
|
|||
|
|
| cron 时区错 | daily.sh 时间不对 | 服务器 UTC,北京 10:00 = cron `0 2` |
|
|||
|
|
| 防火墙挡 18080 | 浏览器打不开 dashboard | 放行 TCP 18080 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 日常用的命令
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# === 看日志 ===
|
|||
|
|
journalctl -u copybot-paper -f # paper bot
|
|||
|
|
journalctl -u copybot-live -f # live bot
|
|||
|
|
journalctl -u copybot-dashboard -f # dashboard
|
|||
|
|
journalctl -u mihomo -f # 代理
|
|||
|
|
|
|||
|
|
# === 看 dashboard ===
|
|||
|
|
# 浏览器:http://服务器IP:18080/
|
|||
|
|
|
|||
|
|
# === 改配置 ===
|
|||
|
|
nano /opt/winning-wallet-finder/live/copybot.paper.json # paper 跟单钱包/金额
|
|||
|
|
sudo systemctl restart copybot-paper
|
|||
|
|
|
|||
|
|
nano /opt/winning-wallet-finder/config.live.json # live 跟单钱包/金额
|
|||
|
|
sudo systemctl restart copybot-live
|
|||
|
|
|
|||
|
|
# === 代理管理 ===
|
|||
|
|
# 看当前节点
|
|||
|
|
curl -s http://127.0.0.1:9090/proxies/GLOBAL | python3 -c "import sys,json;print(json.load(sys.stdin).get('now'))"
|
|||
|
|
# 换节点
|
|||
|
|
curl -s -X PUT http://127.0.0.1:9090/proxies/GLOBAL -H "Content-Type: application/json" -d '{"name":"节点名"}'
|
|||
|
|
# 手动健康检查
|
|||
|
|
/opt/mihomo-healthcheck.sh
|
|||
|
|
|
|||
|
|
# === 停/启动 ===
|
|||
|
|
sudo systemctl stop copybot-live # 停 live(真钱!)
|
|||
|
|
sudo systemctl start copybot-live
|
|||
|
|
sudo systemctl stop copybot-paper # 停 paper
|
|||
|
|
sudo systemctl stop mihomo # 停代理(live 也会停)
|
|||
|
|
|
|||
|
|
# === 完全卸载 ===
|
|||
|
|
sudo systemctl disable --now copybot-paper copybot-live copybot-dashboard mihomo
|
|||
|
|
sudo rm /etc/systemd/system/copybot-*.service /etc/systemd/system/mihomo.service
|
|||
|
|
sudo userdel wwf
|
|||
|
|
rm -rf /opt/winning-wallet-finder
|
|||
|
|
```
|