Files
PolyHermes/docs/zh/copy-trading-logic-summary.md
WrBug c4ac3a8fe0 feat: 实现链上 WebSocket 监听和并行监控策略
- 创建 OnChainWsService,支持通过 Polygon RPC eth_subscribe 实时监听链上交易
- 实现并行监控策略:链上 WS 和轮询同时运行,哪个数据先返回用哪个
- 支持通过 eth_unsubscribe 取消单个 Leader 的订阅
- 在 CopyOrderTrackingService 中添加 Mutex 保证线程安全
- 实现链上交易解析逻辑(USDC Transfer + ERC1155 Transfer)
- 实现 Gamma API 元数据补齐逻辑
- 优化 WebSocket 连接管理:只创建一个连接,没有跟单配置时自动取消
- 跟单配置生效/失效时及时更新 WebSocket 订阅
- 使用 Gson 替换所有 JSON 解析,JsonRpcResponse.result 使用 JsonElement 类型
- 添加相关文档:copy-trading-logic-summary.md 和 copy-trading-monitor-strategy.md
2025-12-26 01:36:00 +08:00

8.2 KiB
Raw Permalink Blame History

跟单买卖逻辑简易文档

一、跟单信号检测

1.1 监控方式

系统使用轮询方式监控 Leader 的交易活动:

  • 数据源Polymarket Data API 的 /activity 接口
  • 轮询间隔:默认 2 秒(可配置)
  • 查询参数
    • user: Leader 钱包地址
    • type: ["TRADE"](只查询交易类型)
    • limit: 100(每次最多查询 100 条)
    • sortBy: TIMESTAMP
    • sortDirection: DESC(按时间戳降序,最新的在前)

1.2 增量检测机制

通过 diff 算法检测新增交易:

  1. 首次轮询

    • 查询最近 100 条交易
    • 缓存所有交易 ID(不处理)
    • 标记首次轮询完成
  2. 后续轮询

    • 查询最近 100 条交易
    • 与缓存的交易 ID 集合进行 diff
    • 找出新增的交易 ID
    • 处理新增交易
    • 更新缓存(添加新增的交易 ID
  3. 去重机制

    • 使用 leaderId + tradeId 作为唯一标识
    • processed_trade 表中记录已处理的交易
    • 避免重复处理同一笔交易

1.3 交易数据转换

UserActivityResponse 转换为 TradeResponse

TradeResponse(
    id = activity.transactionHash,  // 交易ID(用于去重)
    market = activity.conditionId,  // 市场ID
    side = activity.side,  // "BUY" 或 "SELL"
    price = activity.price,  // 交易价格
    size = activity.size,  // 交易数量
    timestamp = activity.timestamp,  // 时间戳(秒)
    user = activity.proxyWallet,  // 用户钱包地址
    outcomeIndex = activity.outcomeIndex,  // 结果索引(0=第一个outcome1=第二个outcome
    outcome = activity.outcome  // 结果名称
)

1.4 信号触发流程

轮询任务启动
    ↓
定期轮询所有 Leader(每 2 秒)
    ↓
查询 Leader 活动(/activity 接口)
    ↓
转换为 TradeResponse
    ↓
diff 检测新增交易
    ↓
调用 processTrade() 处理交易
    ↓
根据 side 字段判断:
    - "BUY" → processBuyTrade()
    - "SELL" → processSellTrade()

二、订单构建

2.1 买入订单构建流程

2.1.1 前置检查

  1. 查找跟单关系

    • 查询所有启用且支持该 Leader 的跟单配置
    • 验证账户 API 凭证是否配置
    • 验证账户是否启用
  2. 获取 Token ID

    • 使用 outcomeIndexmarket 获取 tokenId
    • 支持多元市场(不限于 YES/NO
  3. 计算买入数量

    • RATIO 模式买入数量 = Leader 数量 × 跟单比例
    • FIXED 模式买入数量 = 固定金额 / 买入价格
  4. 过滤条件检查

    • 价格区间检查
    • 仓位限制检查
    • 市场分类检查
    • 订单簿检查(获取最佳卖单价格)
  5. 价格调整

    • 应用价格容忍度(priceTolerance
    • 买入价格 = Leader 价格 × (1 + 容忍度)
    • 确保调整后的价格不低于最佳卖单价格

2.1.2 订单签名

使用 OrderSigningService.createAndSignOrder() 创建并签名订单:

  1. 计算订单金额

    • BUY 订单
      • makerAmount = price × sizeUSDC 金额,最多 2 位小数)
      • takerAmount = size(shares 数量,最多 4 位小数)
    • 转换为 wei6 位小数)
  2. 生成订单参数

    • salt: 时间戳(毫秒)
    • maker: 代理钱包地址(proxyAddress
    • signer: 从私钥推导的签名地址
    • taker: 零地址(0x0000...
    • tokenId: 从 outcomeIndex 获取
    • makerAmount: 计算出的 maker 金额(wei
    • takerAmount: 计算出的 taker 金额(wei
    • expiration: "0"(永不过期)
    • nonce: "0"
    • feeRateBps: "0"
    • side: "BUY"
    • signatureType: 2Browser Wallet
  3. EIP-712 签名

    • 编码域分隔符(Exchange Contract + Chain ID
    • 编码订单消息哈希
    • 计算结构化数据哈希
    • 使用私钥签名(r + s + v

2.1.3 创建订单请求

构建 NewOrderRequest

NewOrderRequest(
    order = signedOrder,  // 签名的订单对象
    owner = account.apiKey,  // API Key
    orderType = "FAK",  // Fill-And-Kill(允许部分成交,未成交部分立即取消)
    deferExec = false  // 立即执行
)

2.1.4 提交订单

  1. 创建 CLOB API 客户端(带认证):

    • 使用账户的 API Key、Secret、Passphrase
    • 解密 API 凭证
  2. 调用 API 创建订单

    • POST /orders
    • 带重试机制(最多重试 2 次)
    • 每次重试都重新生成 salt 并重新签名
  3. 记录订单跟踪

    • 保存到 copy_order_tracking
    • 记录买入订单 ID、数量、价格等信息
    • 状态:filled

2.2 卖出订单构建流程

2.2.1 前置检查

  1. 查找跟单关系

    • 查询所有启用且支持该 Leader 的跟单配置
    • 验证是否支持卖出(supportSell = true
  2. 计算需要匹配的数量

    • 需要匹配数量 = Leader 卖出数量 × 跟单比例
  3. 查找未匹配的买入订单

    • 使用 outcomeIndex 匹配(支持多元市场)
    • 按 FIFO 顺序(先进先出)
    • 查询 copy_order_tracking 表中未匹配的订单
  4. 计算实际可卖出数量

    • 按 FIFO 顺序匹配
    • 支持部分匹配(一个买入订单可以被多次卖出匹配)

2.2.2 价格计算

  1. 优先使用订单簿 bestBid

    • 查询订单簿(getOrderbookByTokenId
    • 获取最佳买单价格(bestBid
    • 使用 bestBid 作为卖出价格
  2. 备选方案

    • 如果获取订单簿失败,使用 Leader 价格
    • 卖出价格 = Leader 价格 × 0.9(固定按 90% 计算)

2.2.3 订单签名

与买入订单类似,但:

  • side: "SELL"
  • SELL 订单金额计算
    • makerAmount = size(shares 数量,最多 4 位小数)
    • takerAmount = price × sizeUSDC 金额,使用原始价格计算)

2.2.4 创建订单请求

与买入订单相同:

  • orderType: "FAK"
  • deferExec: false

2.2.5 提交订单

  1. 调用 API 创建卖出订单(带重试机制)

  2. 更新买入订单状态

    • 更新 copy_order_tracking 表中的 remainingQuantity
    • 如果完全匹配,状态更新为 fully_matched
    • 如果部分匹配,状态更新为 partially_matched
  3. 记录匹配关系

    • 保存到 sell_match_record 表(卖出匹配记录)
    • 保存到 sell_match_detail 表(匹配明细,包含盈亏计算)

三、关键配置

3.1 跟单配置参数

  • copyMode: 跟单模式("RATIO" 或 "FIXED"
  • copyRatio: 跟单比例(RATIO 模式)
  • fixedAmount: 固定金额(FIXED 模式)
  • priceTolerance: 价格容忍度(买入时使用)
  • supportSell: 是否支持卖出
  • enabled: 是否启用

3.2 订单类型

  • FAK (Fill-And-Kill)
    • 允许部分成交
    • 未成交部分立即取消
    • 快速响应 Leader 交易,避免订单长期挂单导致价格不匹配

3.3 重试机制

  • 最多重试次数2 次(首次 + 1 次重试)
  • 重试延迟3 秒
  • 重试策略:每次重试都重新生成 salt 并重新签名,确保签名唯一性

四、数据流向

Leader 交易(链上)
    ↓
Polymarket Data API (/activity)
    ↓
轮询服务(CopyTradingPollingService
    ↓
交易处理服务(CopyOrderTrackingService
    ↓
订单签名服务(OrderSigningService
    ↓
CLOB API (POST /orders)
    ↓
订单跟踪表(copy_order_tracking

五、注意事项

  1. 去重机制

    • 使用 leaderId + tradeId 作为唯一标识
    • processed_trade 表中记录已处理的交易
    • 避免重复处理同一笔交易
  2. 价格调整

    • 买入时应用价格容忍度(提高买入价格)
    • 卖出时优先使用订单簿 bestBid,失败则使用 Leader 价格的 90%
  3. 订单簿检查

    • 买入前检查订单簿中是否有可匹配的卖单
    • 确保调整后的买入价格不低于最佳卖单价格
  4. FIFO 匹配

    • 卖出时按买入时间顺序匹配(先进先出)
    • 支持部分匹配
  5. 错误处理

    • 订单创建失败时记录到 failed_trade
    • 支持重试机制(最多 2 次)
    • 发送失败通知(如果配置了 pushFailedOrders