# 三元市场订单簿实现说明 ## 概述 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` 通过以下步骤计算: ```kotlin // 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` 接口获取订单簿: ```kotlin @GET("/book") suspend fun getOrderbook( @Query("token_id") tokenId: String? = null, @Query("market") market: String? = null ): Response ``` ### 订单簿响应结构 ```kotlin data class OrderbookResponse( val bids: List, // 买入订单列表(按价格从高到低排序) val asks: List // 卖出订单列表(按价格从低到高排序) ) data class OrderbookEntry( val price: String, // 价格(0.01 - 0.99) val size: String // 数量(shares) ) ``` ### 订单簿排序规则 1. **Bids(买入订单)**: - 按价格从高到低排序 - 第一个元素是 `bestBid`(最高买入价) 2. **Asks(卖出订单)**: - 按价格从低到高排序 - 第一个元素是 `bestAsk`(最低卖出价) ## 三元市场订单簿实现 ### 1. 获取特定 Outcome 的订单簿 对于三元市场,需要为每个 outcome 单独获取订单簿: ```kotlin // 示例:三元市场 "谁会赢得选举?" // - 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` 方法中: ```kotlin 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. 完整流程示例 ```kotlin // 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` ## 注意事项 1. **必须提供 outcomeIndex**: - 三元及以上市场无法通过 `side` 字符串推断 `outcomeIndex` - 必须明确提供 `outcomeIndex` 参数 2. **每个 Outcome 独立订单簿**: - 不同 outcome 的订单簿是独立的 - 不能通过 `market` 参数获取所有 outcome 的订单簿 3. **价格范围**: - 所有 outcome 的价格都在 0.01 - 0.99 范围内 - 所有 outcome 的价格之和应该接近 1.0(考虑套利机会) 4. **后备价格机制**: - 如果无法获取订单簿,使用后备价格: - 市价卖单:0.06 - 市价买单:1.0 ## 代码位置 - **TokenId 计算**:`BlockchainService.getTokenId()` - **订单簿获取**:`PolymarketClobService.getOrderbookByTokenId()` - **最优价获取**:`AccountService.getOptimalPriceFromOrderbook()` - **订单创建**:`AccountService.sellPosition()`