164 lines
9.9 KiB
Markdown
164 lines
9.9 KiB
Markdown
|
|
## 项目全流程
|
|||
|
|
|
|||
|
|
**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`(钱包级去重)。
|