diff --git a/docs/sports-tail-strategy/README.md b/docs/sports-tail-strategy/README.md new file mode 100644 index 0000000..ea3b095 --- /dev/null +++ b/docs/sports-tail-strategy/README.md @@ -0,0 +1,41 @@ +# 体育尾盘策略文档 (Sports Tail Strategy) + +本目录集中存放与 Polymarket 体育市场尾盘策略相关的文档。 + +## 目录结构 + +``` +sports-tail-strategy/ +├── README.md # 本说明 +└── zh/ # 中文文档 + ├── sports-tail-strategy-tasks.md # 任务与验收 + ├── sports-tail-strategy-ui-spec.md # UI 规格 + ├── sports-tail-strategy-flow.md # 流程说明 + └── sports-tail-strategy-market-data.md # 市场数据与订阅 +``` + +## 文档说明 + +| 文档 | 说明 | +|------|------| +| **tasks** (zh) | 开发任务与验收项 | +| **ui-spec** (zh) | 前端列表、表单、触发记录等 UI 规格 | +| **flow** (zh) | 策略整体流程(创建→触发→止盈止损→完成) | +| **market-data** (zh) | Gamma API 数据获取、WebSocket 订阅、价格监控 | + +## 功能概述 + +体育尾盘策略用于在体育市场接近尾盘(胜率 90%+)时自动买入,利用高胜率市场低风险获利。 + +### 核心特性 + +1. **不区分方向**:只设置触发价格,系统自动监控两个方向,任意方向达到触发价即买入 +2. **实时订阅**:通过 WebSocket 订阅订单簿,实时监控价格变化 +3. **止盈止损**:支持设置止盈/止损价格,自动卖出 +4. **订阅管理**:同一市场多策略共享订阅,无策略时自动取消订阅 + +### 适用场景 + +- 体育比赛接近尾声,一方胜率 90%+ 时买入 +- 大小分市场接近尾盘时套利 +- 低风险稳定收益场景 diff --git a/docs/sports-tail-strategy/zh/sports-tail-strategy-api.md b/docs/sports-tail-strategy/zh/sports-tail-strategy-api.md new file mode 100644 index 0000000..aaaf764 --- /dev/null +++ b/docs/sports-tail-strategy/zh/sports-tail-strategy-api.md @@ -0,0 +1,356 @@ +# 体育尾盘策略 - API 设计 + +## 一、后端 API + +### 1.1 策略管理 + +#### 列表 +``` +POST /api/sports-tail-strategy/list +``` + +**请求**: +```typescript +interface StrategyListRequest { + accountId?: number; // 筛选账户 + sport?: string; // 筛选类别 +} +``` + +**响应**: +```typescript +interface StrategyListResponse { + list: StrategyDto[]; +} + +interface StrategyDto { + id: number; + accountId: number; + accountName: string; + conditionId: string; + marketTitle: string; + eventSlug: string; + triggerPrice: string; + amountMode: "FIXED" | "RATIO"; + amountValue: string; + takeProfitPrice: string | null; + stopLossPrice: string | null; + + // 成交信息 + filled: boolean; + filledPrice: string | null; + filledOutcomeIndex: number | null; + filledOutcomeName: string | null; + filledAmount: string | null; + filledShares: string | null; + filledAt: number | null; + + // 卖出信息 + sold: boolean; + sellPrice: string | null; + sellType: string | null; + sellAmount: string | null; + realizedPnl: string | null; + soldAt: number | null; + + // 实时价格(未成交时返回) + realtimeYesPrice: string | null; + realtimeNoPrice: string | null; + + createdAt: number; + updatedAt: number; +} +``` + +#### 创建 +``` +POST /api/sports-tail-strategy/create +``` + +**请求**: +```typescript +interface StrategyCreateRequest { + accountId: number; // 账户ID + conditionId: string; // 市场ID + marketTitle: string; // 市场标题 + eventSlug?: string; // 事件slug + triggerPrice: string; // 触发价格 + amountMode: "FIXED" | "RATIO"; + amountValue: string; // 金额值 + takeProfitPrice?: string; // 止盈价格 + stopLossPrice?: string; // 止损价格 +} +``` + +**响应**: +```typescript +interface StrategyCreateResponse { + id: number; +} +``` + +#### 删除 +``` +POST /api/sports-tail-strategy/delete +``` + +**请求**: +```typescript +interface StrategyDeleteRequest { + id: number; +} +``` + +**响应**: +```typescript +interface StrategyDeleteResponse { + success: boolean; +} +``` + +--- + +### 1.2 市场数据 + +#### 体育类别列表 +``` +POST /api/sports-tail-strategy/sports-list +``` + +**响应**: +```typescript +interface SportsListResponse { + list: SportDto[]; +} + +interface SportDto { + sport: string; // 类别标识:nba, nfl, epl... + image: string; // 图标URL + tagId: number; // 主Tag ID + name: string; // 显示名称(多语言) +} +``` + +#### 市场搜索 +``` +POST /api/sports-tail-strategy/market-search +``` + +**请求**: +```typescript +interface MarketSearchRequest { + sport?: string; // 体育类别 + endDateMin?: string; // 最小结束时间 ISO 8601 + endDateMax?: string; // 最大结束时间 ISO 8601 + minLiquidity?: string; // 最小流动性 + keyword?: string; // 搜索关键词 + limit?: number; // 返回数量,默认50 +} +``` + +**响应**: +```typescript +interface MarketSearchResponse { + list: MarketDto[]; +} + +interface MarketDto { + conditionId: string; + question: string; + outcomes: string[]; // ["Yes", "No"] 或 ["Over", "Under"] + outcomePrices: string[]; // 当前价格 + endDate: string; // 结束时间 ISO 8601 + liquidity: string; // 流动性 + bestBid: number | null; + bestAsk: number | null; + yesTokenId: string; + noTokenId: string; +} +``` + +#### 市场详情 +``` +POST /api/sports-tail-strategy/market-detail +``` + +**请求**: +```typescript +interface MarketDetailRequest { + conditionId: string; +} +``` + +**响应**: +```typescript +interface MarketDetailResponse { + conditionId: string; + question: string; + outcomes: string[]; + outcomePrices: string[]; + endDate: string; + liquidity: string; + bestBid: number | null; + bestAsk: number | null; + yesTokenId: string; + noTokenId: string; + eventSlug: string | null; +} +``` + +--- + +### 1.3 触发记录 + +#### 全局记录列表 +``` +POST /api/sports-tail-strategy/triggers +``` + +**请求**: +```typescript +interface TriggerListRequest { + accountId?: number; // 筛选账户 + status?: string; // 筛选状态: SUCCESS/FAIL + startTime?: number; // 开始时间戳 + endTime?: number; // 结束时间戳 + page?: number; // 页码,默认1 + pageSize?: number; // 每页数量,默认20 +} +``` + +**响应**: +```typescript +interface TriggerListResponse { + total: number; + list: TriggerDto[]; +} + +interface TriggerDto { + id: number; + strategyId: number; + + // 市场信息 + marketTitle: string; + conditionId: string; + + // 买入信息 + buyPrice: string; + outcomeIndex: number; + outcomeName: string | null; + buyAmount: string; + buyShares: string | null; + buyStatus: "PENDING" | "SUCCESS" | "FAIL"; + + // 卖出信息 + sellPrice: string | null; + sellType: string | null; // TAKE_PROFIT/STOP_LOSS/MANUAL + sellAmount: string | null; + sellStatus: string | null; + + // 盈亏 + realizedPnl: string | null; + + // 时间 + triggeredAt: number; + soldAt: number | null; +} +``` + +--- + +## 二、前端 API 封装 + +### 2.1 apiService 方法 + +```typescript +// 策略管理 +sportsTailStrategyList(params: StrategyListRequest): Promise +sportsTailStrategyCreate(data: StrategyCreateRequest): Promise +sportsTailStrategyDelete(id: number): Promise + +// 市场数据 +sportsTailStrategySportsList(): Promise +sportsTailStrategyMarketSearch(params: MarketSearchRequest): Promise +sportsTailStrategyMarketDetail(conditionId: string): Promise + +// 触发记录 +sportsTailStrategyTriggers(params: TriggerListRequest): Promise +``` + +--- + +## 三、多语言 Key + +### 3.1 页面标题 +``` +sportsTailStrategy.list.title=体育尾盘策略 +sportsTailStrategy.list.addStrategy=新增策略 +sportsTailStrategy.list.filter.account=账户 +sportsTailStrategy.list.filter.sport=类别 +sportsTailStrategy.list.filter.all=全部 +``` + +### 3.2 表单字段 +``` +sportsTailStrategy.form.account=账户 +sportsTailStrategy.form.market=市场 +sportsTailStrategy.form.triggerPrice=触发价格 +sportsTailStrategy.form.amount=金额 +sportsTailStrategy.form.amountMode=金额模式 +sportsTailStrategy.form.fixed=固定金额 +sportsTailStrategy.form.ratio=余额比例 +sportsTailStrategy.form.takeProfit=止盈价格 +sportsTailStrategy.form.stopLoss=止损价格 +sportsTailStrategy.form.autoSell=自动卖出 +``` + +### 3.3 列表字段 +``` +sportsTailStrategy.list.triggerPrice=触发价 +sportsTailStrategy.list.amount=金额 +sportsTailStrategy.list.takeProfitStopLoss=止盈/止损 +sportsTailStrategy.list.filledPrice=成交价 +sportsTailStrategy.list.shares=份 +sportsTailStrategy.list.pnl=盈亏 +sportsTailStrategy.list.realtimePrice=实时价格 +sportsTailStrategy.list.pending=待结算 +sportsTailStrategy.list.viewRecords=查看记录 +sportsTailStrategy.list.delete=删除 +``` + +### 3.4 市场筛选 +``` +sportsTailStrategy.market.filter.sport=类别 +sportsTailStrategy.market.filter.allSports=全部类别 +sportsTailStrategy.market.filter.endTime=结束时间 +sportsTailStrategy.market.filter.today=今天 +sportsTailStrategy.market.filter.next24h=未来24小时 +sportsTailStrategy.market.filter.next7days=未来7天 +sportsTailStrategy.market.filter.minLiquidity=最小流动性 +sportsTailStrategy.market.filter.keyword=关键词 +sportsTailStrategy.market.filter.search=搜索 +sportsTailStrategy.market.select=选择市场 +``` + +### 3.5 触发记录 +``` +sportsTailStrategy.records.title=触发记录 +sportsTailStrategy.records.market=市场 +sportsTailStrategy.records.direction=方向 +sportsTailStrategy.records.buyPrice=买入价 +sportsTailStrategy.records.buyAmount=买入金额 +sportsTailStrategy.records.sellPrice=卖出价 +sportsTailStrategy.records.sellType=卖出类型 +sportsTailStrategy.records.pnl=盈亏 +sportsTailStrategy.records.time=时间 +sportsTailStrategy.records.status=状态 +``` + +### 3.6 消息提示 +``` +sportsTailStrategy.message.createSuccess=策略创建成功 +sportsTailStrategy.message.deleteSuccess=策略删除成功 +sportsTailStrategy.message.deleteConfirm=确定删除该策略吗? +sportsTailStrategy.message.noMarketSelected=请选择市场 +sportsTailStrategy.message.invalidPrice=价格格式无效 +``` diff --git a/docs/sports-tail-strategy/zh/sports-tail-strategy-flow.md b/docs/sports-tail-strategy/zh/sports-tail-strategy-flow.md new file mode 100644 index 0000000..f1325f9 --- /dev/null +++ b/docs/sports-tail-strategy/zh/sports-tail-strategy-flow.md @@ -0,0 +1,262 @@ +# 体育尾盘策略 - 流程说明 + +## 一、策略状态流转 + +``` +┌─────────────┐ 价格>=触发价 ┌─────────────┐ +│ 待触发 │ ──────────────────▶ │ 已成交 │ +│ (filled=F) │ │ (filled=T) │ +└─────────────┘ └─────────────┘ + │ │ + │ ┌─────────────────┼─────────────────┐ + │ │ │ │ + ▼ │ 有止盈止损 │ 无止盈止损 │ + │ │ │ + ▼ ▼ ▼ + ┌─────────────┐ ┌─────────────────────────────────────┐ + │ 保持订阅 │ │ 检查同市场是否有其他未完成策略 │ + │ 监控卖出 │ │ - 有: 保持订阅 │ + └─────────────┘ │ - 无: 取消订阅 │ + │ └─────────────────────────────────────┘ + │ + ▼ + ┌──────────────────────────────────────────────┐ + │ 价格 >= 止盈价 │ + │ 或 价格 <= 止损价 │ + └──────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────┐ + │ 自动卖出 │ + │ (sold=T) │ + └─────────────┘ +``` + +--- + +## 二、订阅生命周期 + +### 2.1 策略创建时 + +``` +策略创建 + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ SubscriptionManager.subscribeStrategy(strategy) │ +│ 1. 检查市场是否已有订阅(marketSubscriptions 计数) │ +│ 2. 如果市场已有订阅:仅增加计数,不新建连接 │ +│ 3. 如果市场无订阅:建立 WebSocket 连接,订阅订单簿频道 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.2 实时监控 + +``` +WebSocket 订单簿推送 + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ SportsTailWebSocketHandler.onOrderbookUpdate() │ +│ 1. 解析消息,获取当前价格 │ +│ 2. 查找该 Token ID 对应的所有未完成策略 │ +│ 3. 遍历策略,检查触发条件: │ +│ - 未成交:检查买入条件(价格 >= 触发价) │ +│ - 已成交未卖出:检查卖出条件(止盈/止损) │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.3 成交后处理 + +``` +策略成交 + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ SubscriptionManager.onStrategyFilled(strategy) │ +│ 1. 更新策略状态为已成交 │ +│ 2. 检查是否需要保持订阅: │ +│ - 有止盈止损:保持订阅,继续监控卖出条件 │ +│ - 无止盈止损:检查同市场是否有其他未完成策略 │ +│ - 有:保持订阅 │ +│ - 无:取消订阅,关闭 WebSocket 连接 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.4 卖出后处理 + +``` +策略卖出 + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ SubscriptionManager.onStrategySold(strategy) │ +│ 1. 更新策略状态为已卖出 │ +│ 2. 计算并记录盈亏 │ +│ 3. 检查同市场是否有其他未完成策略: │ +│ - 有:保持订阅 │ +│ - 无:取消订阅,关闭 WebSocket 连接 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.5 策略删除时 + +``` +策略删除 + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ SubscriptionManager.unsubscribeStrategy(strategy) │ +│ 1. 减少市场的订阅计数 │ +│ 2. 如果计数归零:取消订阅,关闭 WebSocket 连接 │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 三、买入逻辑(不区分方向) + +### 3.1 触发条件检查 + +```kotlin +fun checkBuyTrigger(strategy: SportsTailStrategy, yesPrice: BigDecimal, noPrice: BigDecimal): BuyTrigger? { + // 如果已成交,不检查 + if (strategy.filled) return null + + // 检查 YES 方向 + if (yesPrice >= strategy.triggerPrice) { + return BuyTrigger(outcomeIndex = 0, price = yesPrice) + } + + // 检查 NO 方向 + if (noPrice >= strategy.triggerPrice) { + return BuyTrigger(outcomeIndex = 1, price = noPrice) + } + + // 都不满足 + return null +} +``` + +### 3.2 执行买入 + +```kotlin +suspend fun executeBuy(strategy: SportsTailStrategy, trigger: BuyTrigger) { + // 1. 创建市价单 + val order = createMarketOrder( + tokenId = getTokenId(strategy.conditionId, trigger.outcomeIndex), + side = "BUY", + amount = calculateAmount(strategy) + ) + + // 2. 提交订单 + val result = clobApi.createOrder(order) + + // 3. 更新策略状态 + strategy.filled = true + strategy.filledPrice = trigger.price + strategy.filledOutcomeIndex = trigger.outcomeIndex + strategy.filledAmount = order.amount + strategy.filledShares = calculateShares(order.amount, trigger.price) + strategy.filledAt = System.currentTimeMillis() + strategyRepository.save(strategy) + + // 4. 记录触发记录 + createTriggerRecord(strategy, trigger, result) + + // 5. 通知订阅管理器 + subscriptionManager.onStrategyFilled(strategy) +} +``` + +--- + +## 四、卖出逻辑(止盈止损) + +### 4.1 止盈止损检查 + +```kotlin +fun checkSellTrigger(strategy: SportsTailStrategy, currentPrice: BigDecimal): SellTrigger? { + // 如果已卖出或未成交,不检查 + if (strategy.sold || !strategy.filled) return null + + // 只检查已买入方向的价格 + val filledTokenId = getTokenId(strategy.conditionId, strategy.filledOutcomeIndex) + if (currentTokenId != filledTokenId) return null + + // 检查止盈 + if (strategy.takeProfitPrice != null && currentPrice >= strategy.takeProfitPrice) { + return SellTrigger(type = "TAKE_PROFIT", price = currentPrice) + } + + // 检查止损 + if (strategy.stopLossPrice != null && currentPrice <= strategy.stopLossPrice) { + return SellTrigger(type = "STOP_LOSS", price = currentPrice) + } + + return null +} +``` + +### 4.2 执行卖出 + +```kotlin +suspend fun executeSell(strategy: SportsTailStrategy, trigger: SellTrigger) { + // 1. 创建市价单 + val order = createMarketOrder( + tokenId = getTokenId(strategy.conditionId, strategy.filledOutcomeIndex), + side = "SELL", + shares = strategy.filledShares + ) + + // 2. 提交订单 + val result = clobApi.createOrder(order) + + // 3. 计算盈亏 + val sellAmount = calculateSellAmount(trigger.price, strategy.filledShares) + val pnl = sellAmount - strategy.filledAmount + + // 4. 更新策略状态 + strategy.sold = true + strategy.sellPrice = trigger.price + strategy.sellType = trigger.type + strategy.sellAmount = sellAmount + strategy.realizedPnl = pnl + strategy.soldAt = System.currentTimeMillis() + strategyRepository.save(strategy) + + // 5. 更新触发记录 + updateTriggerRecord(strategy, trigger, result, pnl) + + // 6. 通知订阅管理器 + subscriptionManager.onStrategySold(strategy) +} +``` + +--- + +## 五、关键设计要点 + +### 5.1 订阅共享 + +- 同一市场(conditionId)多个策略共享一个 WebSocket 订阅 +- 使用计数器管理订阅生命周期 +- 避免重复连接和资源浪费 + +### 5.2 不区分方向 + +- 只设置触发价格,不选择 YES/NO +- 系统自动监控两个方向的价格 +- 任意方向满足条件即买入该方向 + +### 5.3 实时订阅 + +- 使用 WebSocket 订阅订单簿,实时接收价格变化 +- 不使用轮询方式 +- 响应速度快,延迟低 + +### 5.4 智能取消 + +- 无策略时取消订阅 +- 策略完成且无止盈止损时检查是否需要取消 +- 有止盈止损时保持订阅直到卖出 diff --git a/docs/sports-tail-strategy/zh/sports-tail-strategy-market-data.md b/docs/sports-tail-strategy/zh/sports-tail-strategy-market-data.md new file mode 100644 index 0000000..2e5b6f6 --- /dev/null +++ b/docs/sports-tail-strategy/zh/sports-tail-strategy-market-data.md @@ -0,0 +1,215 @@ +# 体育尾盘策略 - 市场数据与订阅 + +> 本文档描述体育市场数据获取方式、 WebSocket 订阅管理逻辑。 + +## 一、Gamma API 数据获取 + +> 本文档描述如何从 Gamma API 获取体育市场数据。### 1.1 萜索条件 +### 1.1 获取体育类别列表 +> **前端选择体育类别时,需要获取可选的体育类别列表供用户选择。 +``` +GET https://gamma-api.polymarket.com/sports +``` +> **返回示例**: +```json +[ + {"sport": "nba", "image": "https://...", "tags": "1,745,100639"}, + {"sport": "nfl", "image": "https://...", "tags": "1,450,100639"}, + {"sport": "epl", "image": "https://...", "tags": "1,82,306,100639"}, + ... +] +``` + +**主要类别**: +| sport | 名称 | tag_id | +|-------|------|--------| +| nba | 篮球 NBA | 745 | +| nfl | 美式足球 NFL | 450 | +| epl | 英超 | 82 | +| lal | 西甲 | 780 | +| mlb | 棒球 MLB | 100381 | +| nhl | 冰球 NHL | 899 | + +| ufc | 格斗 UFC | 100639 | + +### 1.2 按类别筛选市场 +> 根据用户选择的体育类别,使用 `tag_id` 参数筛选市场。 +``` +GET https://gamma-api.polymarket.com/markets?tag_id=745&active=true&closed=false&limit=50&order=endDate&ascending=true +``` +> **返回字段**: +- `id` - 市场ID +- `question` - 市场问题 +- `conditionId` - 市场 conditionId +- `outcomes` - 结果选项 `["Yes", "No"]` 或 `["Over", "Under"]` +- `outcomePrices` - 当前价格 `["0.92", "0.08"]` +- `endDate` - 结束时间 +- `bestBid` - 最佳买价 +- `bestAsk` - 最佳卖价 +- `clobTokenIds` - Token IDs +- `gameStartTime` - 比赛开始时间 +- `sportsMarketType` - 市场类型 +- `liquidityNum` - 流动性 +- `volumeNum` - 成交量 + +- `events` - 关联事件信息 + +``` + +> **请求参数**: +```kotlin +data class SportsMarketSearchRequest( + val sport: String? = null, // 体育类别: nba, nfl, epl... + val marketType: String? = null, // 市场类型: moneyline, spreads, totals + val endDateMin: String? = null, // 最小结束时间 + val endDateMax: String? = null, // 最大结束时间 + val minLiquidity: BigDecimal? = null, // 最小流动性 + val keyword: String? = null, // 搜索关键词 + val limit: Int = 50 // 返回数量 +) +``` + +> **marketType 说明**: +- `moneyline` - 胜负市场(谁会赢) +- `spreads` - 让分市场 +- `totals` - 大小分市场 + +### 1.3 获取单个市场详情 +``` +GET https://gamma-api.polymarket.com/markets?condition_ids={conditionId} +``` +> **用于**: +- 创建策略时获取 `clobTokenIds` +- 监控价格时获取实时价格 +- 获取 Token ID 用于下单 + +--- + +## 二、WebSocket 订阅管理 +### 2.1 订阅策略 +> 同一市场可能有多个策略,但只维护一个 WebSocket 订阅。 +> 策略创建/删除时需要更新订阅计数。 +```kotlin +// 市场订阅计数 +private val marketSubscriptions = ConcurrentHashMap() + +// 市场对应的 Token IDs 缓存 +private val marketTokenIds = ConcurrentHashMap>() + +/** + * 订阅策略(创建策略时调用) + */ +fun subscribeStrategy(strategy: SportsTailStrategy) { + val conditionId = strategy.conditionId + + marketSubscriptions.compute(conditionId) { _, count -> + val newCount = (count ?: 0) + 1 + if (newCount == 1) { + // 首次订阅,建立 WebSocket 连接 + subscribeMarket(conditionId) + } + newCount + } +} + +``` +> **订阅管理规则**: +| 场景 | 操作 | +|------|------| +| 策略创建 | 如果市场无订阅,建立订阅;否则计数 +1 | +| 策略删除 | 计数 -1;如果计数为 0,取消订阅 | +| 策略成交(无止盈止损) | 检查同市场是否有其他未完成策略;无则取消订阅 | +| 策略卖出 | 检查同市场是否有其他未完成策略;无则取消订阅 | + +### 2.2 讣阅流程 +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ 策略创建 │────▶│ 检查订阅计数 │────▶│ 首次? 建立连接 │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ WebSocket 接收订单簿更新 │ +│ 1. 解析消息,获取 tokenId 和价格 │ +│ 2. 查找该 Token 对应的未完成策略 │ +│ 3. 检查触发条件 │ +│ 4. 满足条件则执行买入/卖出 │ +└─────────────────────────────────────────────────────────────┘ +``` + +> **WebSocket 消息格式**: +```json +{ + "event_type": "book", + "asset_id": "1234567890", + "market": { + "bids": [...], + "asks": [...], + "timestamp": 1234567890 + } +} +``` + +--- + +## 三、价格监控与触发 +### 3.1 买入触发逻辑 +> 任意方向价格达到触发价即买入 +```kotlin +fun checkBuyTrigger( + strategy: SportsTailStrategy, + yesPrice: BigDecimal, + noPrice: BigDecimal +): TriggerResult? { + // 检查 YES 方向 + if (yesPrice >= strategy.triggerPrice) { + return TriggerResult(outcomeIndex = 0, price = yesPrice) + } + + // 检查 NO 方向 + if (noPrice >= strategy.triggerPrice) { + return TriggerResult(outcomeIndex = 1, price = noPrice) + } + + return null +} +``` +> **注意**:不区分方向,系统自动选择价格满足的方向买入。 +### 3.2 止盈止损逻辑 +> 已成交的策略,监控持仓方向的价格变化 +```kotlin +fun checkSellTrigger( + strategy: SportsTailStrategy, + currentPrice: BigDecimal +): SellTrigger? { + if (!strategy.filled || strategy.sold) { + return null + } + + // 止盈:当前价格 >= 止盈价 + if (strategy.takeProfitPrice != null && currentPrice >= strategy.takeProfitPrice) { + return SellTrigger(type = "TAKE_PROFIT", price = currentPrice) + } + + // 止损:当前价格 <= 止损价 + if (strategy.stopLossPrice != null && currentPrice <= strategy.stopLossPrice) { + return SellTrigger(type = "STOP_LOSS", price = currentPrice) + } + + return null +} +``` +> **注意**:只监控已买入方向的价格,不是两个方向都监控。 +### 3.3 订阅生命周期 +``` +┌─────────────┐ 价格>=触发价 ┌─────────────┐ 止盈/止损 ┌─────────────┐ +│ 监控两个方向 │ ────────────────▶ │ 已成交 │ ────────────────▶ │ 已完成 │ +└─────────────┘ └─────────────┘ └─────────────┘ + │ │ + ▼ ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ 成交后检查止盈止损 │ +│ - 有止盈止损:保持订阅,只监控买入方向 │ +│ - 无止盈止损:检查同市场是否有其他未完成策略,无则取消订阅 │ +└─────────────────────────────────────────────────────────────────────────┘ +``` diff --git a/docs/sports-tail-strategy/zh/sports-tail-strategy-tasks.md b/docs/sports-tail-strategy/zh/sports-tail-strategy-tasks.md new file mode 100644 index 0000000..1ee5008 --- /dev/null +++ b/docs/sports-tail-strategy/zh/sports-tail-strategy-tasks.md @@ -0,0 +1,141 @@ +# 体育尾盘策略 - 任务梳理 + +> 需求与 UI 见 `sports-tail-strategy-ui-spec.md`,市场数据与订阅见 `sports-tail-strategy-market-data.md`。 + +以下按**数据库 / 后端 / 前端**拆分为可执行任务,便于排期与验收。 + +--- + +## 一、数据库 + +| 序号 | 任务 | 说明 | +|------|------|------| +| D1 | 策略表 migration | 新建表 `sports_tail_strategy`,字段:id, account_id, condition_id, market_title, event_slug, trigger_price, amount_mode(FIXED/RATIO), amount_value, take_profit_price, stop_loss_price, filled(BOOL), filled_price, filled_outcome_index, filled_amount, filled_shares, filled_at, sold(BOOL), sell_price, sell_type, sell_amount, realized_pnl, sold_at, created_at, updated_at | +| D2 | 触发记录表 migration | 新建表 `sports_tail_strategy_trigger`,字段:id, strategy_id, market_title, condition_id, account_id, buy_order_id, buy_price, outcome_index, outcome_name, buy_amount, buy_shares, buy_status(PENDING/SUCCESS/FAIL), sell_order_id, sell_price, sell_type(TAKE_PROFIT/STOP_LOSS/MANUAL), sell_amount, sell_status, realized_pnl, triggered_at, sold_at | + +--- + +## 二、后端(Kotlin) + +### 2.1 实体与 Repository + +| 序号 | 任务 | 说明 | +|------|------|------| +| B1 | 策略实体 Entity | 对应 `sports_tail_strategy` 表;ID 用 `Long?`;时间用 `Long` 时间戳;金额用 `BigDecimal`;遵守 backend.mdc 实体规范 | +| B2 | 触发记录实体 Entity | 对应 `sports_tail_strategy_trigger` 表 | +| B3 | JpaRepository | 策略、触发记录的 Repository;按 conditionId、accountId、filled、sold 等查询 | + +### 2.2 Gamma API 扩展 + +| 序号 | 任务 | 说明 | +|------|------|------| +| B4 | 体育类别 API | `GET /sports` 获取体育元数据(sport, tags, image) | +| B5 | 市场搜索 API | `GET /markets` 支持 tag_id、sports_market_types、end_date_min/max、liquidity_num_min 等筛选参数 | +| B6 | 事件列表 API | `GET /events` 支持 tag_slug、active、live 等参数,获取比赛状态 | + +### 2.3 订阅管理 + +| 序号 | 任务 | 说明 | +|------|------|------| +| B7 | 订阅管理器 SubscriptionManager | 维护市场订阅计数,同一市场多策略共享订阅;无策略时自动取消订阅 | +| B8 | WebSocket 订单簿订阅 | 订阅订单簿 `channel: "book:"`,接收实时价格更新 | +| B9 | 订阅生命周期管理 | 策略创建时订阅,成交后检查是否需要保持(有止盈止损则保持),卖出后检查是否需要取消 | + +### 2.4 策略执行核心逻辑 + +| 序号 | 任务 | 说明 | +|------|------|------| +| B10 | 买入触发判断 | 接收订单簿更新,检查未成交策略;当任意方向价格 >= triggerPrice 时买入该方向 | +| B11 | 买入执行 | 调用 CLOB API 创建市价买单;记录成交价格、数量、方向;更新策略状态为已成交 | +| B12 | 止盈判断 | 已成交策略,当前价格 >= takeProfitPrice 时执行卖出 | +| B13 | 止损判断 | 已成交策略,当前价格 <= stopLossPrice 时执行卖出 | +| B14 | 卖出执行 | 调用 CLOB API 创建市价卖单;计算盈亏;更新策略状态为已卖出 | + +### 2.5 API 与 DTO + +| 序号 | 任务 | 说明 | +|------|------|------| +| B15 | 策略 CRUD API | 列表(分页/筛选)、创建、删除;统一 ApiResponse;错误码与 MessageSource | +| B16 | 策略 DTO | 创建请求:accountId, conditionId, marketTitle, eventSlug, triggerPrice, amountMode, amountValue, takeProfitPrice(可选), stopLossPrice(可选) | +| B17 | 触发记录 API | 全局触发记录列表;支持 accountId、status、时间筛选;返回市场信息、成交价、数量、盈亏等 | +| B18 | 市场搜索 API | 体育类别列表、市场搜索(支持筛选)、市场详情(含实时价格) | + +--- + +## 三、前端(React + TypeScript) + +### 3.1 路由与导航 + +| 序号 | 任务 | 说明 | +|------|------|------| +| F1 | 路由 | App.tsx 增加 `/sports-tail-strategy` | +| F2 | 菜单 | Layout 中增加「体育尾盘策略」菜单项 | + +### 3.2 列表页 + +| 序号 | 任务 | 说明 | +|------|------|------| +| F3 | 列表页组件 | SportsTailStrategyList.tsx;页面标题、新增按钮、筛选(账户、类别) | +| F4 | 列表展示 | 桌面 Table / 移动 Card:市场标题、账户、触发价、金额、止盈止损、成交价/数量(未成交显示实时价格)、操作(查看记录、删除) | +| F5 | 实时价格显示 | 未成交策略通过 WebSocket 获取实时价格并显示 | + +### 3.3 新增/编辑表单 + +| 序号 | 任务 | 说明 | +|------|------|------| +| F6 | 表单弹窗 | 账户选择、市场搜索(支持筛选)、触发价格、下注金额、止盈止损(可选) | +| F7 | 市场筛选器 | 体育类别、市场类型、结束时间、最小流动性、搜索关键词 | +| F8 | 市场选择器 | 显示搜索结果列表,包含市场标题、当前价格、结束时间、流动性 | +| F9 | 预估收益 | 根据触发价、金额计算预估份额和收益 | +| F10 | 表单校验 | 触发价 0-1,止盈 > 触发价,止损 < 触发价 | + +### 3.4 触发记录 + +| 序号 | 任务 | 说明 | +|------|------|------| +| F11 | 触发记录列表 | 全局记录列表(非单个市场);支持账户、状态、时间筛选 | +| F12 | 记录详情 | 市场标题、成交价格/方向、数量、卖出价格/类型、盈亏 | + +### 3.5 通用 + +| 序号 | 任务 | 说明 | +|------|------|------| +| F13 | 类型定义 | 策略、触发记录、市场、体育类别等 TypeScript 类型 | +| F14 | API 封装 | apiService 中 sportsTailStrategy.* 方法 | +| F15 | 多语言 | zh-CN、zh-TW、en 的 sportsTailStrategy.* 文案 | + +--- + +## 四、依赖关系简图 + +``` +D1,D2 数据库 + ↓ +B1-B3 实体与 Repository + ↓ +B4-B6 Gamma API 扩展 + ↓ +B7-B9 订阅管理 + ↓ +B10-B14 执行逻辑 + ↓ +B15-B18 API 与 DTO + ↓ +F1-F2 路由与菜单 +F13-F15 类型与 API 封装 + ↓ +F3-F5 列表与实时价格 +F6-F10 表单(含市场筛选) +F11-F12 触发记录 +``` + +--- + +## 五、验收要点 + +- **不区分方向**:只设置触发价格,系统自动监控两个方向,任意方向达到即买入 +- **实时订阅**:通过 WebSocket 订阅订单簿,实时监控价格,不使用轮询 +- **订阅管理**:同一市场多策略共享一个订阅;无策略或策略完成且无止盈止损时取消订阅 +- **止盈止损**:有止盈止损的策略成交后保持订阅,直到卖出 +- **列表显示**:已成交显示成交价和数量,未成交显示实时价格 +- **触发记录**:全局记录列表,每条记录包含完整市场信息 diff --git a/docs/sports-tail-strategy/zh/sports-tail-strategy-ui-spec.md b/docs/sports-tail-strategy/zh/sports-tail-strategy-ui-spec.md new file mode 100644 index 0000000..d9d9627 --- /dev/null +++ b/docs/sports-tail-strategy/zh/sports-tail-strategy-ui-spec.md @@ -0,0 +1,259 @@ +# 体育尾盘策略 - UI 规格 + +## 一、策略列表页 + +### 1.1 页面布局 + +**路由**:`/sports-tail-strategy` + +**桌面端**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 体育尾盘策略 [+ 新增策略] │ +├─────────────────────────────────────────────────────────────────────┤ +│ 筛选: [账户选择▼] [类别: 全部▼] │ +├─────────────────────────────────────────────────────────────────────┤ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ Lakers vs Bulls - Who will win? │ │ +│ │ 账户: Account 1 │ │ +│ │ 触发价: >=0.90 | 金额: 10 USDC │ │ +│ │ 止盈: 0.98 | 止损: 0.85 │ │ +│ │ ─────────────────────────────────────────────────────────── │ │ +│ │ 成交价: 0.91 YES | 数量: 10.99 份 | 盈亏: 待结算 │ │ +│ │ [查看记录] [删除] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +**移动端**:使用卡片布局,信息折叠展示 + +### 1.2 列表字段 + +| 字段 | 说明 | +|------|------| +| 市场标题 | `marketTitle`,可点击跳转 Polymarket | +| 账户 | 关联的账户名称 | +| 触发价 | `>= {triggerPrice}` | +| 金额 | 固定金额 USDC 或 余额比例 % | +| 止盈/止损 | 配置的止盈止损价格,未配置显示 `-` | +| 成交信息 | 已成交:`{filledPrice} {YES/NO} \| {filledShares} 份`
未成交:`实时价格: YES {price} \| NO {price}` | +| 盈亏 | 已卖出:`+{realizedPnl} USDC`
已成交未卖出:`待结算`
未成交:`-` | + +### 1.3 操作按钮 + +| 按钮 | 说明 | +|------|------| +| 查看记录 | 打开该策略的触发记录详情 | +| 删除 | 删除策略(需二次确认) | + +**注意**:不支持启用/禁用功能,无状态列 + +--- + +## 二、新增策略表单 + +### 2.1 表单字段 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| 账户选择 | 下拉 | 是 | 选择账户 | +| 选择市场 | 搜索选择 | 是 | 从市场列表中选择(支持筛选) | +| 触发条件 | 复合输入 | 是 | `当价格 [>=] [0.90] 时触发买入` | +| 下注金额 | 单选+输入 | 是 | 固定金额 USDC 或 余额比例 % | +| 启用自动卖出 | 开关 | 否 | 开启后显示止盈止损 | +| 止盈价格 | 输入 | 否 | 价格上涨到此值时自动卖出 | +| 止损价格 | 输入 | 否 | 价格下跌到此值时自动卖出 | + +**注意**: +- 不选择方向(YES/NO),只设置触发价格 +- 系统自动监控两个方向,任意方向达到触发价即买入 +- 默认只触发一次 + +### 2.2 市场选择器 + +**筛选条件**: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 体育类别 │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ [全部 ▼] │ │ +│ │ 选项: 全部 / NBA / NFL / 英超 / 西甲 / 棒球... │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ 市场类型 │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ [全部 ▼] │ │ +│ │ 选项: 全部 / 胜负 / 让分 / 大小分 │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ 结束时间 │ +│ ○ 全部 ○ 今天 ○ 未来24小时 ○ 未来7天 │ +│ │ +│ 最小流动性 │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ [ ] USDC(留空表示不限制) │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ 搜索关键词 │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ [搜索球队、比赛...] [搜索] │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**市场列表**: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ○ Lakers vs Bulls - Who will win? │ +│ YES: 0.92 NO: 0.08 | 流动性: 50,000 USDC │ +│ 结束: 2024-03-07 15:30 (剩余2小时30分) │ +│ │ +│ ○ Warriors @ Heat - O/U 220.5 │ +│ Over: 0.55 Under: 0.45 | 流动性: 30,000 USDC │ +│ 结束: 2024-03-07 18:00 (剩余5小时) │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.3 预估收益展示 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 预估收益 │ +│ ──────────────────────────────────────────────────────── │ +│ 买入价格: 0.90 | 买入金额: 10 USDC │ +│ 预计份额: 11.11 | 预计收益: +1.11 USDC (11.1%) │ +│ │ +│ 止盈 (0.98): 收益 +8.89 USDC (88.9%) │ +│ 止损 (0.85): 亏损 -1.67 USDC (-16.7%) │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 三、触发记录页(全局) + +### 3.1 页面布局 + +**路由**:`/sports-tail-strategy/records` + +**说明**:全局记录列表,不是单个策略的记录 + +**桌面端表格**: + +``` +┌────────────────────────────────────────────────────────────────────────────────────────┐ +│ 触发记录 │ +├────────────────────────────────────────────────────────────────────────────────────────┤ +│ 筛选: [账户选择▼] [状态: 全部▼] [时间范围: 最近7天▼] │ +├────────────────────────────────────────────────────────────────────────────────────────┤ +│ 时间 │ 市场标题 │ 方向 │ 买入价 │ 金额 │ 卖出价 │ 盈亏 │ +│ 03-07 15:30 │ Lakers vs Bulls │ YES │ 0.91 │ 10.00 │ 0.98 │ +7.89 USDC │ +│ 03-07 14:20 │ Warriors @ Heat │ Over │ 0.55 │ 5.00 │ - │ 待结算 │ +│ 03-06 18:45 │ Celtics vs Heat │ NO │ 0.92 │ 20.00 │ 0.85 │ -7.00 USDC │ +└────────────────────────────────────────────────────────────────────────────────────────┘ +``` + +**移动端**:使用卡片布局 + +### 3.2 记录字段 + +| 字段 | 说明 | +|------|------| +| 时间 | `triggeredAt` 格式化显示 | +| 市场标题 | `marketTitle`,可点击跳转 Polymarket | +| 方向 | `outcomeName` 或 YES/NO/Over/Under | +| 买入价 | `buyPrice` | +| 金额 | `buyAmount` USDC | +| 卖出价 | `sellPrice`,未卖出显示 `-` | +| 盈亏 | `realizedPnl`,未结算显示 `待结算` | + +### 3.3 筛选条件 + +| 条件 | 说明 | +|------|------| +| 账户 | 按账户筛选 | +| 状态 | 全部 / 待结算 / 已止盈 / 已止损 / 已完成 | +| 时间范围 | 最近24小时 / 最近7天 / 最近30天 / 全部 | + +--- + +## 四、响应式适配 + +### 4.1 断点 + +- 移动端: < 768px +- 桌面端: >= 768px + +### 4.2 移动端适配 + +1. 列表使用卡片布局,信息可折叠 +2. 表单使用分步或滚动布局 +3. 触发记录使用卡片列表 +4. 按钮最小触摸目标 44x44px + +--- + +## 五、多语言 Key + +``` +sportsTailStrategy.list.title=体育尾盘策略 +sportsTailStrategy.list.addStrategy=新增策略 +sportsTailStrategy.list.filter.account=账户 +sportsTailStrategy.list.filter.category=类别 +sportsTailStrategy.list.filter.allCategory=全部 +sportsTailStrategy.list.triggerPrice=触发价 +sportsTailStrategy.list.amount=金额 +sportsTailStrategy.list.takeProfitStopLoss=止盈/止损 +sportsTailStrategy.list.filledPrice=成交价 +sportsTailStrategy.list.realtimePrice=实时价格 +sportsTailStrategy.list.shares=份 +sportsTailStrategy.list.pnl=盈亏 +sportsTailStrategy.list.pending=待结算 +sportsTailStrategy.list.viewRecords=查看记录 +sportsTailStrategy.list.delete=删除 +sportsTailStrategy.list.deleteConfirm=确定删除该策略吗? + +sportsTailStrategy.form.title=新增体育尾盘策略 +sportsTailStrategy.form.account=账户 +sportsTailStrategy.form.selectAccount=选择账户 +sportsTailStrategy.form.selectMarket=选择市场 +sportsTailStrategy.form.triggerCondition=触发条件 +sportsTailStrategy.form.triggerPriceHelp=当任意方向价格达到触发价时买入 +sportsTailStrategy.form.amount=下注金额 +sportsTailStrategy.form.fixedAmount=固定金额 +sportsTailStrategy.form.ratio=余额比例 +sportsTailStrategy.form.autoSell=启用自动卖出 +sportsTailStrategy.form.takeProfitPrice=止盈价格 +sportsTailStrategy.form.takeProfitHelp=价格上涨到此值时自动卖出 +sportsTailStrategy.form.stopLossPrice=止损价格 +sportsTailStrategy.form.stopLossHelp=价格下跌到此值时自动卖出 +sportsTailStrategy.form.estimatedReturn=预估收益 +sportsTailStrategy.form.buyPrice=买入价格 +sportsTailStrategy.form.buyAmount=买入金额 +sportsTailStrategy.form.estimatedShares=预计份额 +sportsTailStrategy.form.estimatedPnl=预计收益 + +sportsTailStrategy.marketSearch.sport=体育类别 +sportsTailStrategy.marketSearch.marketType=市场类型 +sportsTailStrategy.marketSearch.endTime=结束时间 +sportsTailStrategy.marketSearch.minLiquidity=最小流动性 +sportsTailStrategy.marketSearch.keyword=搜索关键词 +sportsTailStrategy.marketSearch.search=搜索 +sportsTailStrategy.marketSearch.liquidity=流动性 +sportsTailStrategy.marketSearch.remaining=剩余 + +sportsTailStrategy.records.title=触发记录 +sportsTailStrategy.records.time=时间 +sportsTailStrategy.records.market=市场 +sportsTailStrategy.records.direction=方向 +sportsTailStrategy.records.buyPrice=买入价 +sportsTailStrategy.records.amount=金额 +sportsTailStrategy.records.sellPrice=卖出价 +sportsTailStrategy.records.pnl=盈亏 +sportsTailStrategy.records.pending=待结算 +sportsTailStrategy.records.takeProfit=已止盈 +sportsTailStrategy.records.stopLoss=已止损 +```