## 项目全流程 **Polymarket Copy Trader** — 一个自动跟单系统:监控 Polymarket 上最赚钱的钱包,找出多个钱包同时下注的"共识信号",然后发送 Telegram 通知(可选自动执行)。 --- ### 启动命令 ``` python -m src.main run # 主程序:跑整个循环(6 个 asyncio 任务) python -m src.main dashboard # Web 看板(FastAPI,端口 8518) python -m src.main stats # CLI 查看命中率 / PnL 统计 python -m src.main backfill # 手动回填已 resolve 市场的信号结果 python -m src.main pool # 立即重建钱包池并退出 python -m src.main test-stream # 测试交易流轮询 ``` --- ### 1. 钱包池构建(Pool Build) 启动时,从 Polymarket 公开 API 拉取 **24h 交易量最高的 200 个活跃市场**,对每个市场拉取前 30 名持仓者,聚合后筛选出约 **100 个最赚钱的钱包**(PnL 最高、交易笔数多、覆盖品类多)。这 100 个钱包就是"被跟单目标"。 > **PnL 计算的数据源**:对每个候选钱包并行调用 `/positions`(当前持仓 cashPnl)+ `/trades`(近 30 天成交)+ `/closed-positions`(近 30 天已平仓 realizedPnl)。`/closed-positions` 用 `sortBy=TIMESTAMP` + `offset` 翻页拉满整个 30 天窗口,避免服务端 50 条上限导致漏数据。详见 `data_api.py:get_closed_positions_since`。 --- ### 2. 交易流监控(Trade Stream) 每隔 30 秒,对这 100 个钱包轮询 Polymarket Data API,抓取他们最新的交易记录。每笔交易触发 `on_trade` 回调,交给信号聚合器处理。 --- ### 3. 共识信号聚合(Signal Aggregator) 对每笔交易做 **五层过滤**: 1. 交易金额 ≥ 500 USD(`MIN_TRADE_SIZE_USD`,可在 .env 调整) 2. 价格在 0.1 ~ 0.9 之间(`MIN_PRICE` / `MAX_PRICE`,过滤已决议或极端价格市场) 3. 钱包信誉分 ≥ 0.3(`MIN_CREDIBILITY`) 4. 同一钱包对同一市场 10 分钟内只算一次(`WALLET_DEBOUNCE_SECONDS`) 5. 通过则进入"市场累加器" **共识规则**:在 10 分钟窗口内(`CONSENSUS_WINDOW_SECONDS`),有 **≥ 2 个钱包** 朝同一方向(都买或都卖)下注同一市场,且正反面力量差 ≥ 0.4(`CONSENSUS_STRENGTH_THRESHOLD`),就发出信号。 信号包含:市场名称、方向、入场价、聚合强度、参与钱包数、Kelly 公式建议仓位。 --- ### 4. 信号输出 - **存库**:写入 SQLite(`data/copytrader.db`),供 Dashboard 展示 - **Telegram 通知**:发送格式化消息(市场名、方向、建议仓位、来源钱包数) - **Dashboard**:Web 页面实时展示 Top 钱包排名 + 最近信号(每 30 秒自动刷新) --- ### 5. 贝叶斯信誉更新(Bayesian Updater) 每 60 分钟(`CREDIBILITY_UPDATE_MINUTES`)运行一次:根据钱包历史交易的实际表现(赢/亏),用贝叶斯方法更新每个钱包的信誉分,形成 **先验→后验** 的正反馈。表现好的钱包权重上升,表现差的下降。 > **数据源**:调用 `/closed-positions`(`sortBy=TIMESTAMP` + 翻页)拉取近 `BAYESIAN_DECAY_DAYS`(默认 14 天)的全部平仓记录,累加 `realizedPnl` 作为更新依据。API 返回的已经全部在窗口内,无需在 Python 端二次过滤。 --- ### 6. 钱包池定期刷新 每 24 小时(`WALLET_REFRESH_HOURS`)重建一次钱包池,淘汰表现差的,引入新晋赚钱钱包。 --- ### 7. 信号结果回填(Outcome Resolver) 第 6 个 asyncio 循环,每 15 分钟(`OUTCOME_RESOLVE_MINUTES`)扫描 `copy_signals` 表中 `resolved_at IS NULL` 的信号,调用 Gamma `/markets` API 拉取市场结算结果,回填以下字段: | 字段 | 含义 | |---|---| | `exit_price` | 1.0(赢) / 0.0(输) | | `pnl_usd` | 理论 PnL(win = `S*(1-p)/p`,loss = `-S`,S=suggested_size,p=entry_price) | | `outcome_correct` | 1(赢) / 0(输) / -1(市场取消) | | `resolved_at` | 市场 `closedTime` | **PnL 模型说明**:理论"全仓按 entry_price 成交" PnL,不建模滑点。用于策略评估,非真实账户 PnL。 **手动 backfill**:`python -m src.main backfill --batch 500` 一次性回填历史信号。 > 解析逻辑严格遵循 Gamma `/markets` 文档:`closed=true` + `outcomePrices` 中价格 ≥ 0.99 的 outcome 即为赢家。 --- ### 8. 策略分析看板(Analytics Dashboard) 访问 `http://localhost:8518/analytics`,展示 6 个维度的聚合统计,每个维度都用于调参: | 维度 | 调参目标 | |---|---| | **强度分桶命中率** | `CONSENSUS_STRENGTH_THRESHOLD` — 理想下强度越高命中率越高 | | **共识钱包数分桶** | `CONSENSUS_MIN_WALLETS` — 理想下钱包数越多命中率越高 | | **各时段命中率** | 优化 `pool_refresh` 调度时间 | | **品类表现** | 发现跟单有效的品类 | | **钱包排行榜** | 反向验证信誉分公式 — 高信誉钱包应排前列 | | **总体表现** | 总命中率 / 总 PnL / 平均 PnL | **API 端点**:`/api/outcomes`, `/api/stats/by-strength`, `/by-wallet-count`, `/by-wallet`, `/by-category`, `/by-hour`, `/api/credibility-history?address=...` **调参闭环**:看 Analytics → 发现某分桶表现差 → 调 `.env` → 重启 → 等待新数据 → 再看 Analytics 验证。 --- ### .env 关键配置 | 分组 | 配置项 | 说明 | | ------------ | -------------------------------- | -------------------------------------------- | | **资金** | `INITIAL_CAPITAL_USD` | 模拟本金(默认 $10000) | | | `KELLY_FRACTION` | Kelly 仓位比例(0.5 = 半 Kelly) | | **价格防护** | `MIN_PRICE` / `MAX_PRICE` | 只跟单价格在 0.1~0.9 的市场 | | **交易规则** | `MIN_TRADE_SIZE_USD` | 忽略低于此金额的交易(默认 500) | | | `ENABLE_EXECUTION` | 设为 `true` 才真执行(默认 `false` 只通知) | | **钱包池** | `WALLET_POOL_SIZE` | 监控钱包数(默认 100) | | | `WALLET_PNL_MIN_USD` | 钱包最低 PnL 门槛(默认 $5000) | | | `WALLET_REFRESH_HOURS` | 池刷新间隔(默认 24h) | | **共识** | `CONSENSUS_MIN_WALLETS` | 触发信号最少钱包数(默认 2) | | | `CONSENSUS_WINDOW_SECONDS` | 共识窗口(默认 600s) | | | `CONSENSUS_STRENGTH_THRESHOLD` | 正反面差值门槛(默认 0.4) | | | `MIN_CREDIBILITY` | 钱包最低信誉分(默认 0.3) | | | `WALLET_DEBOUNCE_SECONDS` | 去重窗口(默认 600s) | | **信誉分** | `BAYESIAN_PRIOR_SKILL` | 初始信誉分(默认 0.5) | | | `BAYESIAN_DECAY_DAYS` | PnL 累计窗口(默认 14 天) | | | `BAYESIAN_STEP` | 贝叶斯步长(默认 0.05) | | | `CREDIBILITY_UPDATE_MINUTES` | 更新间隔(默认 60min) | | **信号回填** | `OUTCOME_RESOLVE_MINUTES` | 回填循环间隔(默认 15min) | | | `OUTCOME_RESOLVE_BATCH_SIZE` | 每批最多扫描信号数(默认 50) | | | `OUTCOME_RESOLVE_TIMEOUT` | 单次 /markets 请求超时(默认 5s) | | **Telegram** | `TELEGRAM_ENABLED` | 是否启用(默认 `false`) | | | `TELEGRAM_BOT_TOKEN` | BotFather 给的 token(必填) | | | `TELEGRAM_CHAT_ID` | 发送目标的 chat ID(必填) | | **代理** | `HTTP_PROXY` | 需要翻墙时填写(如 `http://127.0.0.1:7890`) | | **轮询** | `USER_POLL_INTERVAL_SECONDS` | 钱包轮询间隔(默认 30s) | | | `STREAM_MAX_TRADES_PER_WALLET` | 每次拉取最多交易数(默认 20) | | **数据库** | `DB_PATH` | SQLite 路径(默认 `data/copytrader.db`) | | **CLOB** | `POLY_API_KEY` 等 | 仅启用实盘执行时填 | --- ### 数据库并发模型 SQLite 由四个异步协程并发写入:`pool_refresh`(写钱包池)、`trade_stream`(写信号)、`credibility`(写信誉分 + 信誉历史)、`outcome_resolve`(写信号回填),均通过 `asyncio.to_thread` 在线程池里执行。`CopyTraderDatabase` 的并发治理策略(见 `db/database.py`): - **单一长连接** + `check_same_thread=False`(不再每次操作开关连接) - **`threading.RLock`** 串行化所有方法,进程内永远只有一个 writer - **WAL 模式** + `synchronous=NORMAL`:多读不阻塞写 - **`busy_timeout=10000ms`**:跨进程写冲突(如 Dashboard 进程)等待 10s 而非立即抛 `database is locked` ### 数据库表结构 | 表 | 用途 | 关键字段 | |---|---|---| | `wallet_targets` | 跟单目标钱包池 | address, credibility, health_score, pnl_30d_usd | | `copy_signals` | 发出的共识信号 | condition_id, side, entry_price, aggregated_strength, n_contributors, outcome_correct, pnl_usd, resolved_at | | `credibility_history` | 信誉分演化历史(append-only) | address, credibility, realized_pnl_window, n_closed, updated_at | | `trade_executions` | 实盘执行记录 | signal_id, order_id, status, executed_at | | `pool_state` | KV 持久化(重启恢复) | key, value(JSON)| `pool_state` 存储的 key:`last_pool_refresh`、`last_credibility_update`、`open_accumulators`(共识累加器)、`stream_last_seen`(交易去重)、`wallet_debounce`(钱包级去重)。