Files
PolyHermes/docs/ternary-market-orderbook.md
T

214 lines
6.3 KiB
Markdown
Raw Normal View History

# 三元市场订单簿实现说明
## 概述
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. 计算 indexSetindexSet = 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<OrderbookResponse>
```
### 订单簿响应结构
```kotlin
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
)
```
### 订单簿排序规则
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 个 outcomeoutcomeIndex = 0, 1
- 可以通过 `market` 参数获取整个市场的订单簿
- Gamma API 提供 `bestBid``bestAsk`(但可能只针对主要 outcome
### 三元及以上市场
- 有 3 个或更多 outcomeoutcomeIndex = 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()`