Files
gavindiaz 125470b24a sync: upstream + our customizations (dashboard, healthcheck, docs)
Synced from github/main: aebcfa2d copybot: live paper feed [skip ci]
Our additions: serve_dashboard.py, bot_dashboard.html, mihomo-healthcheck.sh,
中文文档, 我们的钱包配置
2026-07-21 07:35:48 +08:00

22 KiB
Raw Permalink Blame History

DEPLOY.md - 手动部署 paper + live copybot 到新服务器

一步一步来,可以一边看一边敲。不要一次性复制粘贴大段命令--每一步做完、看到预期输出,再进下一步。

适用:把 paper bot + live bot 部署到一台新的 Linux 服务器(Ubuntu/Debian),从你自己的 Gitea 拉代码。如果服务器在 Polymarket 禁单地区(美国/英国/法国等),用 mihomo 代理绕开 geo-block。

完成时间参考:60-90 分钟(含每步等待 + live 配置)。

完成部署后提供:

  • paper bot24/7 模拟跟单(直连,不走代理)
  • live bot:24/7 真钱跟单(走代理绕 geo-block)
  • bot 实时账本(中文 web dashboardhttp://服务器IP:18080/
  • 原始 JSONhttps://<GITEA>/<USERNAME>/winning-wallet-finder/raw/branch/main/live/copybot_live.json

目录


Part A: Paper Bot 部署

0. 前置条件

  • 一台 Linux VPSUbuntu 20.04+ / Debian 11+
  • 你有 root SSH 权限
  • 你的 Gitea 上有这个仓库的镜像
  • Gitea PAT(权限 repository: Write

测 Polymarket 直连:

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 = 能读 APIpaper 够用)。

1. 装基础依赖

ssh root@你的服务器IP
apt-get update
apt-get install -y git python3 python3-pip ca-certificates

2. 从 Gitea 拉代码

cd /opt
git clone https://<USERNAME>:<TOKEN>@<GITEA>/<USERNAME>/winning-wallet-finder.git
cd winning-wallet-finder/live

3. 装 duckdb + 首次回测

pip3 install duckdb --break-system-packages
python3 portfolio.py --days 30

预期:portfolio[30d rolling]: equity $... | ... | -> portfolio.json

4. 前台测试 bot

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

nano ~/.netrc
machine <GITEA域名>
login <USERNAME>
password <TOKEN>
chmod 600 ~/.netrc
cd /opt/winning-wallet-finder
git push origin main --dry-run

5.3 root git 白名单

git config --global --add safe.directory /opt/winning-wallet-finder

6. 建专用用户 wwf

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

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

systemctl daemon-reload
systemctl enable --now copybot-paper
sleep 5
systemctl status copybot-paper

9. 验证 Gitea 推送

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

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

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

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 完成检查

  • paper bot systemd 24/7 跑
  • web dashboard @ :18080
  • Alchemy RPC ON
  • 状态推 Gitea
  • 每日 cron

Part B: 代理配置(geo-block 绕行)

为什么要代理Polymarket 禁止美国/英国/法国/德国/意大利/荷兰/波兰/新加坡/澳大利亚/加拿大安大略/巴西 等地区的 IP 下单。如果你的服务器在这些地区,live bot 需要走代理。

paper bot 不受影响(只读 API 全球开放)。

14. 检查 geo-gate

cd /opt/winning-wallet-finder
python3 host/geocheck.py
  • VERDICT: TRADABLE -> 跳过 Part B,直接去 Part C
  • VERDICT: BLOCKED -> 继续往下配代理

15. 装 mihomoClash Meta 内核)

15.1 下载 mihomo

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。

wget -O /etc/mihomo/config.yaml \
  --user-agent="clash-verge/v1.7.7" \
  "你的订阅链接&flag=meta"

验证拿到的是真实节点(不是"当前Clash客户端不支持本机场协议"):

grep "name:" /etc/mihomo/config.yaml | grep -i "日本\|香港\|新加坡" | head -10

15.3 改全局模式 + 启动

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 服务

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 自动健康检查(节点挂了自动换)

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 botmihomo 监听 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 依赖

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 创建存款钱包

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 凭证创建存款钱包:

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 验证余额

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

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

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

坑 1LIVE_CONFIRM 的值必须是 TRADE LIVEcopytrade.py:48 的 CONFIRM_PHRASE),不是 I understand real money is at risk。填错会报 Aborted - LIVE_CONFIRM is set but does not match the phrase

坑 2systemd EnvironmentFile 里值不要加引号LIVE_CONFIRM=TRADE LIVE 是对的,LIVE_CONFIRM="TRADE LIVE" 会让值带引号导致不匹配。

坑 3HTTPS_PROXY 只在 live.env 里设,不要放 paper.envpaper bot 不需要代理)。

21. 跑 preflight

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

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

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

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

监控第一笔成交

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.envALCHEMY_RPC_URL
|   +-- state: copybot_state.json
|   +-- feed: live/copybot_live.json
|
+-- copybot-live.service        live bot(走代理绕 geo-block
|   +-- EnvironmentFile: live.envLIVE_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

日常用的命令

# === 看日志 ===
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