feat(whale-monitor): 优化市场选择页交互与分类缓存

- 体育联赛通过 series_id 展示单场比赛,长期市场分块可折叠
- 移除全部分类,支持搜索与分类切换缓存、首屏自动加载
- 单组列表扁平展示,列表行不展示图片(联赛下拉保留 logo)
- 补充大单监听功能说明与 Code Review 清单文档

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WrBug
2026-05-27 04:49:22 +08:00
co-authored by Cursor
parent b3872aa8f2
commit 5d6f0e03f6
8 changed files with 594 additions and 85 deletions
@@ -0,0 +1,97 @@
# Agent 代码产出 Review Checklist
用于:每次 Agent 完成一段功能开发/修复后,你在合并/上线前做快速、系统性的代码审查。
## 0. 变更范围(先看清改了什么)
- **git diff 是否可控**:只包含本需求相关文件,避免“顺手重构”带来风险
- **是否引入新依赖**:新增依赖是否必要、是否有替代、是否影响体积/启动速度
- **配置变更**:是否修改了 `application.properties`(避免无意义的默认值/硬编码)
- **敏感信息**:确认没有提交 `.env`、私钥、API Key、Token、RPC URL 等
## 1. 需求一致性(功能层验收)
- **需求点逐条对照**:每个需求都有对应实现与测试路径
- **边界条件**:空数据、异常响应、重复触发、网络抖动、重连等是否考虑
- **失败行为明确**:API/下单失败时是否记录失败原因并可追踪(禁止“失败返回默认值”掩盖问题)
- **幂等性**:重复请求/重复消息/并发触发不会导致重复下单或重复落库
## 2. 后端(Kotlin / Spring)检查
### 2.1 项目规范硬约束
- **禁止**出现 `TODO/FIXME/XXX` 注释
- **Controller**
- 统一 `@PostMapping`
- **不能**使用 `suspend`
- 调用 suspend 方法时使用 `runBlocking`,且范围最小化
- **数值与时间**
- 金额/数量/价格计算:**必须 BigDecimal**(避免 Double
- DTO/Entity 金额字段:优先 **String 存储**(计算时再转 BigDecimal
- 时间字段:**Long 毫秒时间戳**
- **JSON**
- 优先使用扩展函数 `fromJson<T>()` / `toJson()`,不要直接 new `Gson`
- **API 响应**
- 统一 `ApiResponse { code,data,msg }`
- 错误信息通过 `ErrorCode + MessageSource`,不要硬编码中英文
- 响应里不要直接返回 `Map`
### 2.2 事务与并发
- **@Transactional 边界合理**:避免把网络请求(外部 API/WS)包进大事务
- **并发安全**:涉及下单/触发等必须考虑并发(数据库唯一键、Mutex、幂等表等)
- **重试策略**:重试次数、间隔、是否每次重试重新签名/更新 salt(如适用)
### 2.3 可观测性与排错
- **日志可用**:失败路径有 `logger.error`(包含关键上下文:strategyId/market/tokenId/orderId
- **告警/通知**:关键失败是否能被用户看到(UI/Telegram/推送)
- **性能**:WS 高吞吐场景是否有“快速过滤”避免全量 JSON 解析
## 3. 数据库与实体(如有)
- **Entity 规范**
- `id: Long? = null`
- 时间:Long 毫秒
- 数值字段:DB 用 Decimal,代码用 String/BigDecimal(按项目既定规则)
- **索引/唯一约束**
- 幂等表是否有唯一键(例如 `strategyId + windowStart + tokenId`
- **字段默认值**
- data class 字段必须提供默认值(字符串空串、集合 emptyList 等)
- **迁移脚本**
- 是否存在(如果项目采用迁移工具),是否可回滚/可重复执行
## 4. 前端(React + TypeScript)检查
### 4.1 项目规范硬约束
- **禁止 any**:没有 `any`、没有 `@ts-ignore`(除非明确理由)
- **禁止硬编码文案**:所有 UI 文案都走 `react-i18next``t('...')`
- **移动端适配**:关键页面在 <768px 下可用(按钮 ≥ 44x44
- **金额展示**
- 统一使用现有格式化函数(如 `formatUSDC` 或同类函数),不要手写 `toFixed`
### 4.2 类型与接口
- API 请求/响应类型是否完整、字段命名与后端一致
- 表单校验是否与后端一致(例如价格 0~1、最多两位小数)
- 错误处理是否统一(message/notification、loading 状态)
## 5. 交易/风控相关(若涉及下单)
- **价格区间/精度校验**:前后端双重校验一致
- **深度/滑点校验**:下单前是否检查订单簿深度足够
- **限额**
- 单次最大金额
- 每日最大金额/最大订单数
- cooldown/防连点(策略级)
- **订单类型**:是否符合预期(例如 FAK 快速成交,避免挂单风险)
- **失败不兜底**:下单失败不应该假装成功;必须可追踪与可重试
## 6. 安全与合规
- 私钥/API Secret 解密使用是否符合现有封装(避免重复实现)
- 日志里是否泄露私钥、完整 API Key/Secret、签名原文等
- 外部请求是否走统一的 Retrofit/拦截器(避免绕开认证/代理配置)
## 7. 本地验证(建议最小化操作清单)
- `git status` 干净(除了预期新增/修改文件)
- 后端:能编译通过(至少 `./gradlew test``./gradlew bootJar`,按你们习惯)
- 前端:能构建通过(`pnpm build`/`npm run build`,按你们项目)
- 关键路径手测:创建策略 → 触发 → 过滤/下单 → 记录/通知
## 8. 合并前最后一眼
- 变更是否可回滚(开关、配置、禁用策略等)
- 默认值是否安全(默认禁用自动下单/默认阈值合理/冷却存在)
- 文档是否更新(字段、接口、流程)
+193
View File
@@ -0,0 +1,193 @@
# 市场大单监听策略(10 秒聚合)
## 一、需求概述
目标:对用户配置的**自选市场列表**做实时成交监听。当某个市场的某个 outcome(`tokenId`)在短时间窗口内出现显著的买入成交额(可由多个成交组成),认为存在“聪明钱/趋势”,系统按策略配置自动下单。
本策略的关键特性:
- **监听数据源**Polymarket Activity WebSocket`topic=activity,type=trades`
- **聚合粒度**:按 **`tokenId + side`** 聚合(每个 outcome 单独统计)
- **触发方向**:默认只做 **BUY**(可扩展)
- **触发窗口**:默认 10 秒(可配置)
- **触发阈值**:窗口内累计成交额(金额)达到阈值
- **执行方式**:触发后**自动下单**,并且 **跟随触发成交的 `tokenId`**
- **价格区间**:只在 **下单价** 落入配置区间时才下单(0~1,最多两位小数)
## 二、数据与名词
### 2.1 Activity Trade 关键字段
从 Activity WS trade 消息中我们关注:
- `payload.conditionId`:市场 IDconditionId
- `payload.asset`outcome 的 **tokenId**(下单必须用)
- `payload.side``BUY` / `SELL`
- `payload.price`:成交价(0~1 的概率价)
- `payload.size`:成交数量(shares
- `payload.timestamp`:成交时间
- `payload.transactionHash`:去重用交易哈希(同一笔可能在不同类型推送里出现)
### 2.2 成交额(金额)定义
每条成交的金额计算:
\[
notional = price \times size
\]
其中:
- `price` 取 BigDecimal(字符串化后再解析)
- `size` 取 BigDecimal
- `notional` 作为本策略的累计指标与阈值比较对象
说明:
- 前端展示可以使用 `$` 符号,但后端计算统一使用 `String + BigDecimal`,不要在代码里绑定具体稳定币名称。
## 三、产品流程
### 3.1 策略创建/编辑(前端)
用户创建一条“大单监听策略”时需要配置:
- **基础信息**
- 策略名称
- 绑定账户(下单账户)
- 是否启用
- **监听范围**
- 自选市场列表(按 `conditionId`,可多选)
- **触发条件**
- 窗口秒数 `windowSeconds`(默认 10
- 触发阈值 `thresholdAmount`(金额)
- 冷却时间 `cooldownSeconds`(默认例如 60
- **下单参数**
- 固定下单金额 `orderAmount`(金额)
- `priceTolerance`(可选,用于提高成交概率的容忍度)
- **价格区间过滤(按下单价)**
- `minPrice` / `maxPrice`
- 取值范围:0~1
- 精度:最多两位小数
- 含义:仅当“最终下单价(orderPrice)”满足 `minPrice <= orderPrice <= maxPrice` 才下单;否则视为被过滤(记录过滤原因)。
### 3.2 运行时触发与执行
简化流程:
```
收到 trade 消息
按 tokenId+BUY 入 10 秒滑动窗口,累计 notional
累计 notional ≥ thresholdAmount ?
├─ 否:继续
└─ 是:
cooldown 内已触发过 ?
├─ 是:记录/忽略,不下单
└─ 否:
读取订单簿 bestAsk 计算最终下单价 orderPrice
orderPrice 是否在 [minPrice,maxPrice] ?
├─ 否:记录过滤原因,不下单
└─ 是:
用固定金额 orderAmount 计算 sizeShares
深度/滑点风控通过?
FAK 下单(tokenId=触发tokenId
记录触发与订单结果、推送通知
```
## 四、技术方案(后端)
### 4.1 监听与过滤
推荐新增独立服务(与跟单监听解耦),但复用现有基础设施:
- WebSocket 客户端:`backend/src/main/kotlin/com/wrbug/polymarketbot/websocket/PolymarketWebSocketClient.kt`
- Activity WS 协议:`docs/zh/polymarket-activity-websocket-api.md`
性能要点:
- 订阅全局 trades 可能消息频率较高,必须先做**快速字符串过滤**(按 `conditionId` 集合)再 JSON 解析。
### 4.2 10 秒滑动窗口聚合(按 tokenId+BUY
聚合 key
- `AggKey = (conditionId, tokenId, side)`,本需求默认只统计 `side=BUY`
每个 key 维护:
- `ArrayDeque<TradePoint(tsMillis, notional)>`
- `runningSum`:当前窗口累计 notional
每条新事件:
- 入队并 `runningSum += notional`
- 清理过期事件并 `runningSum -= expiredNotional`
-`runningSum >= thresholdAmount` 且不在 cooldown 内,则触发执行
去重:
- 使用 `txHash` TTL 去重,避免重复触发/重复入窗
### 4.3 冷却与幂等
冷却:
- 对同一 `strategyId + tokenId + side` 记录最近触发时间,`cooldownSeconds` 内不重复触发下单。
幂等落库:
- 触发记录表建议包含:`strategyId, conditionId, tokenId, side, windowStartTs, windowEndTs, windowSumNotional, status, failReason, createdAt`
- 唯一键建议:`strategyId + tokenId + side + windowStartTs`(窗口起点按 `floor(ts/windowMs)*windowMs`
### 4.4 下单执行(FAK,跟随 tokenId
关键步骤:
- 读取订单簿 `bestAsk`
- 生成最终下单价 `orderPrice`
- 若使用 `priceTolerance`:可按 `orderPrice = bestAsk * (1 + priceTolerance)`,并根据业务限制做截位
- **价格区间校验**(必须)
- `minPrice/maxPrice` 均为字符串配置,解析为 BigDecimal
- 必须满足:0~1 范围、最多两位小数(配置校验与下单前双重校验)
- 最终判断:`minPrice <= orderPrice <= maxPrice`
- 计算下单数量:
- `sizeShares = orderAmount / orderPrice`
- 使用你们统一的 BigDecimal 扩展函数做除法精度与四舍五入策略
- 风控建议:
- 订单簿深度能否覆盖 `orderAmount`(必要)
- 单次最大金额 / 日累计金额(建议)
- 最大允许价差/滑点(建议)
- 提交订单:
- 复用 `OrderSigningService.createAndSignOrder`
- `orderType = FAK`(允许部分成交,未成交部分取消)
## 五、配置校验规则
### 5.1 价格区间(minPrice/maxPrice
规则:
- 范围:`0 <= price <= 1`
- 精度:最多两位小数
建议校验点:
- 策略创建/更新时校验(后端)
- 下单前再次校验(避免异常数据导致下单)
### 5.2 价格与金额数据类型
- 后端 DTO/Entity 使用 `String` 存储金额与价格(保持统一,与现有跟单/策略一致)
- 计算时转换为 BigDecimal
- 禁止使用 `Double` 做金额/价格运算
## 六、可观测性与通知
建议输出与推送:
- 触发事件:包含市场、tokenId、窗口累计金额、下单价、是否通过区间、是否下单
- 下单结果:订单 ID、成交数量/均价(如可获取)、失败原因
## 七、后续扩展(可选)
- 支持 SELL 方向(做反向策略或止损策略)
- 触发条件增加“成交笔数”“大额单笔阈值”等组合条件
- 支持“市场整体聚合”(按 conditionId 聚合,而不是 tokenId)作为另一种策略类型