4e89f53c24
- 创建 MarketController,将市场相关接口从 AccountController 移出 - 添加 /api/copy-trading/markets/latest-price 接口供前端获取最新价 - 在 PolymarketClobService 中封装获取订单表和最优价的逻辑 - 支持多元市场(二元、三元及以上)的最优价获取 - 市价单价格添加调整系数(买单+0.01,卖单-0.02) - 移除所有测试文件中的真实私钥和API凭证 - 更新前端API服务,添加markets相关接口
6.3 KiB
6.3 KiB
三元市场订单簿实现说明
概述
Polymarket 的三元市场(Ternary Market)是指具有三个或更多可能结果的市场。与二元市场(YES/NO)不同,三元市场需要为每个 outcome 维护独立的订单簿。
核心概念
1. TokenId 与 Outcome 的关系
在 Polymarket 中,每个 outcome 都有唯一的 tokenId:
-
二元市场:
- YES (outcomeIndex = 0) → tokenId_0
- NO (outcomeIndex = 1) → tokenId_1
-
三元市场:
- Outcome A (outcomeIndex = 0) → tokenId_0
- Outcome B (outcomeIndex = 1) → tokenId_1
- Outcome C (outcomeIndex = 2) → tokenId_2
-
多元市场(N 个结果):
- Outcome 0 → tokenId_0
- Outcome 1 → tokenId_1
- ...
- Outcome N-1 → tokenId_N-1
2. TokenId 的计算方式
tokenId 通过以下步骤计算:
// 1. 计算 indexSet:indexSet = 2^outcomeIndex
val indexSet = BigInteger.TWO.pow(outcomeIndex)
// 2. 调用链上合约 getCollectionId(EMPTY_SET, conditionId, indexSet)
val collectionId = getCollectionId(EMPTY_SET, conditionId, indexSet)
// 3. 调用链上合约 getPositionId(collateralToken, collectionId)
val tokenId = getPositionId(collateralToken, collectionId)
示例:
- outcomeIndex = 0 → indexSet = 1 (2^0)
- outcomeIndex = 1 → indexSet = 2 (2^1)
- outcomeIndex = 2 → indexSet = 4 (2^2)
- outcomeIndex = 3 → indexSet = 8 (2^3)
订单簿结构
API 接口
Polymarket CLOB API 提供 /book 接口获取订单簿:
@GET("/book")
suspend fun getOrderbook(
@Query("token_id") tokenId: String? = null,
@Query("market") market: String? = null
): Response<OrderbookResponse>
订单簿响应结构
data class OrderbookResponse(
val bids: List<OrderbookEntry>, // 买入订单列表(按价格从高到低排序)
val asks: List<OrderbookEntry> // 卖出订单列表(按价格从低到高排序)
)
data class OrderbookEntry(
val price: String, // 价格(0.01 - 0.99)
val size: String // 数量(shares)
)
订单簿排序规则
-
Bids(买入订单):
- 按价格从高到低排序
- 第一个元素是
bestBid(最高买入价)
-
Asks(卖出订单):
- 按价格从低到高排序
- 第一个元素是
bestAsk(最低卖出价)
三元市场订单簿实现
1. 获取特定 Outcome 的订单簿
对于三元市场,需要为每个 outcome 单独获取订单簿:
// 示例:三元市场 "谁会赢得选举?"
// - Outcome 0: "候选人A"
// - Outcome 1: "候选人B"
// - Outcome 2: "候选人C"
// 获取 Outcome 0 的订单簿
val tokenId0 = blockchainService.getTokenId(conditionId, 0)
val orderbook0 = clobService.getOrderbookByTokenId(tokenId0)
// orderbook0.bids[0].price 是 Outcome 0 的 bestBid
// orderbook0.asks[0].price 是 Outcome 0 的 bestAsk
// 获取 Outcome 1 的订单簿
val tokenId1 = blockchainService.getTokenId(conditionId, 1)
val orderbook1 = clobService.getOrderbookByTokenId(tokenId1)
// 获取 Outcome 2 的订单簿
val tokenId2 = blockchainService.getTokenId(conditionId, 2)
val orderbook2 = clobService.getOrderbookByTokenId(tokenId2)
2. 市价单价格获取
在 AccountService.getOptimalPriceFromOrderbook 方法中:
private suspend fun getOptimalPriceFromOrderbook(tokenId: String, isSellOrder: Boolean): String {
// 通过 tokenId 获取特定 outcome 的订单簿
val orderbookResult = clobService.getOrderbookByTokenId(tokenId)
if (orderbookResult.isSuccess) {
val orderbook = orderbookResult.getOrNull()
if (orderbook != null) {
if (isSellOrder) {
// 市价卖单:需要 bestBid(最高买入价)
val bestBid = orderbook.bids.firstOrNull()?.price
// 返回 bestBid 或后备价格
} else {
// 市价买单:需要 bestAsk(最低卖出价)
val bestAsk = orderbook.asks.firstOrNull()?.price
// 返回 bestAsk 或后备价格
}
}
}
// 如果获取失败,返回后备价格
return fallbackPrice
}
3. 完整流程示例
// 1. 用户请求卖出 Outcome 2 的仓位
val request = PositionSellRequest(
accountId = 1,
marketId = "0x123...", // conditionId
side = "候选人C",
outcomeIndex = 2, // 关键:指定 outcome 索引
orderType = "MARKET",
quantity = "100"
)
// 2. 计算 tokenId
val tokenId = blockchainService.getTokenId(request.marketId, request.outcomeIndex)
// tokenId = "87660119269436753918591605029528224889066452434179554814663664703244066132110"
// 3. 获取订单簿并提取最优价
val optimalPrice = getOptimalPriceFromOrderbook(tokenId, isSellOrder = true)
// 从 orderbook.bids[0].price 获取 bestBid
// 4. 创建并提交订单
val signedOrder = orderSigningService.createAndSignOrder(
tokenId = tokenId,
side = "SELL",
price = optimalPrice,
size = request.quantity
)
与二元市场的区别
二元市场(YES/NO)
- 只有 2 个 outcome(outcomeIndex = 0, 1)
- 可以通过
market参数获取整个市场的订单簿 - Gamma API 提供
bestBid和bestAsk(但可能只针对主要 outcome)
三元及以上市场
- 有 3 个或更多 outcome(outcomeIndex = 0, 1, 2, ...)
- 必须通过
tokenId参数获取特定 outcome 的订单簿 - 每个 outcome 都有独立的订单簿
- 需要明确指定
outcomeIndex来计算tokenId
注意事项
-
必须提供 outcomeIndex:
- 三元及以上市场无法通过
side字符串推断outcomeIndex - 必须明确提供
outcomeIndex参数
- 三元及以上市场无法通过
-
每个 Outcome 独立订单簿:
- 不同 outcome 的订单簿是独立的
- 不能通过
market参数获取所有 outcome 的订单簿
-
价格范围:
- 所有 outcome 的价格都在 0.01 - 0.99 范围内
- 所有 outcome 的价格之和应该接近 1.0(考虑套利机会)
-
后备价格机制:
- 如果无法获取订单簿,使用后备价格:
- 市价卖单:0.06
- 市价买单:1.0
- 如果无法获取订单簿,使用后备价格:
代码位置
- TokenId 计算:
BlockchainService.getTokenId() - 订单簿获取:
PolymarketClobService.getOrderbookByTokenId() - 最优价获取:
AccountService.getOptimalPriceFromOrderbook() - 订单创建:
AccountService.sellPosition()