385 lines
14 KiB
Markdown
385 lines
14 KiB
Markdown
## Context
|
||
|
||
PolyHermes 当前把跟单流程拆成两层:
|
||
|
||
- `copy_trading_leaders` 保存可被跟单的钱包地址,前端入口是 `Leader 管理`。
|
||
- `copy_trading` 保存真实跟单配置,前端入口是 `跟单配置`。
|
||
|
||
这两层之间缺少“候选池/配仓台”。用户如果想用小仓位摊大饼,只能靠脑子或外部笔记维护候选状态,然后手动创建真实跟单配置。这对真钱交易不够安全:观察、试跟、冷却、淘汰没有独立状态,配置也容易沿用过松默认值。
|
||
|
||
本设计新增 `Leader 池`,把它定位为 `Leader 管理` 与 `跟单配置` 中间的决策层。它不替代现有 leader 地址库,也不替代真实跟单配置;它负责帮助用户决定“谁进入候选、谁小额试跟、谁冷却或淘汰”。
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
|
||
- 新增左侧菜单入口 `跟单交易 -> Leader 池`,路径为 `/leader-pool`。
|
||
- 支持把已有 leader 加入池子,并维护池子状态。
|
||
- 支持从 `Leader 管理` 一键加入池子。
|
||
- 支持从池子创建保守小额跟单配置。
|
||
- 在页面上展示池子人数、试跟人数、估算最坏暴露、待处理风险。
|
||
- 复用现有 leader、账户、跟单配置和统计能力,避免重复业务逻辑。
|
||
- 用显式确认和保守默认值保护真钱交易安全。
|
||
|
||
**Non-Goals:**
|
||
|
||
- 不接入 Polymarket leaderboard 自动发现。
|
||
- 不自动启用大额跟单。
|
||
- 不自动加仓。
|
||
- 不做 AI 预测评分。
|
||
- 不做复杂多账户资金托管。
|
||
- 不修改真实 PnL 统计口径。统计中仍必须区分真实归零仓位和行情/持仓数据不可用。
|
||
|
||
## Decisions
|
||
|
||
### 1. 新增独立 `copy_trading_leader_pool` 表
|
||
|
||
采用独立表,不把池子字段塞进 `copy_trading_leaders`。
|
||
|
||
原因:
|
||
|
||
- `copy_trading_leaders` 是地址库,leader 可以存在但不在当前策略池中。
|
||
- 池子有状态、来源、建议配置、冷却时间、复核时间,这些是策略决策数据,不是 leader 基础资料。
|
||
- 后续 leaderboard radar、手动观察、亏损诊断都可以写入同一池子表,而不污染 leader 地址库。
|
||
|
||
建议迁移:
|
||
|
||
```text
|
||
backend/src/main/resources/db/migration/V41__create_copy_trading_leader_pool.sql
|
||
```
|
||
|
||
建议字段:
|
||
|
||
```text
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY
|
||
leader_id BIGINT NOT NULL
|
||
status VARCHAR(20) NOT NULL
|
||
source VARCHAR(50) NOT NULL DEFAULT 'MANUAL'
|
||
source_rank INT NULL
|
||
score DECIMAL(20, 8) NULL
|
||
reason TEXT NULL
|
||
notes TEXT NULL
|
||
suggested_fixed_amount DECIMAL(20, 8) NOT NULL DEFAULT 1.00000000
|
||
suggested_max_daily_orders INT NOT NULL DEFAULT 10
|
||
suggested_max_daily_loss DECIMAL(20, 8) NOT NULL DEFAULT 5.00000000
|
||
suggested_min_price DECIMAL(20, 8) NULL DEFAULT 0.10000000
|
||
suggested_max_price DECIMAL(20, 8) NULL DEFAULT 0.80000000
|
||
suggested_max_position_value DECIMAL(20, 8) NULL DEFAULT 5.00000000
|
||
last_reviewed_at BIGINT NULL
|
||
last_promoted_at BIGINT NULL
|
||
cooldown_until BIGINT NULL
|
||
locked BOOLEAN NOT NULL DEFAULT FALSE
|
||
created_at BIGINT NOT NULL
|
||
updated_at BIGINT NOT NULL
|
||
```
|
||
|
||
约束与索引:
|
||
|
||
```text
|
||
UNIQUE KEY uk_leader_pool_leader_id (leader_id)
|
||
INDEX idx_leader_pool_status (status)
|
||
INDEX idx_leader_pool_source (source)
|
||
FOREIGN KEY (leader_id) REFERENCES copy_trading_leaders(id) ON DELETE CASCADE
|
||
```
|
||
|
||
v1 池子是全局策略池,不按账户拆分。账户只在创建小额试跟配置时选择;同一个 leader 在池子里只出现一次,避免把“候选观察状态”拆成多份互相打架的笔记。若后续需要多账户独立池,再新增 `account_id` 并迁移为 `UNIQUE(account_id, leader_id)`,本次不做。
|
||
|
||
### 2. 状态机后端完整、前端 v1 精简
|
||
|
||
后端状态枚举预留:
|
||
|
||
```text
|
||
CANDIDATE
|
||
WATCH
|
||
PAPER
|
||
TRIAL
|
||
ACTIVE
|
||
COOLDOWN
|
||
RETIRED
|
||
```
|
||
|
||
前端 v1 展示:
|
||
|
||
```text
|
||
候选
|
||
观察
|
||
小额试跟
|
||
冷却
|
||
淘汰
|
||
```
|
||
|
||
原因:
|
||
|
||
- 后端预留可以让后续 radar 或自动规则不需要迁移状态字段。
|
||
- 前端不一次暴露太多状态,避免用户每天面对一堆概念。
|
||
- 锁定是独立布尔字段 `locked`,不是状态。这样 `TRIAL + locked`、`COOLDOWN + locked` 都能表达,不会把“交易阶段”和“自动任务是否可改”混在一起。
|
||
|
||
状态更新规则:
|
||
|
||
- `RETIRED` 不删除 leader 地址,只让池子项退出关注。
|
||
- `COOLDOWN` 可以设置 `cooldownUntil`。
|
||
- `locked=true` 的池子项不允许被后续自动任务改状态。v1 没有自动任务,但字段先保留。
|
||
|
||
状态流:
|
||
|
||
```text
|
||
+---------+
|
||
| RETIRED |
|
||
+---------+
|
||
^
|
||
|
|
||
CANDIDATE -> WATCH -> TRIAL -> ACTIVE
|
||
| | |
|
||
+----------+--------+
|
||
|
|
||
v
|
||
COOLDOWN
|
||
|
||
locked=true 是覆盖层,不是状态:
|
||
任何状态 + locked=true => 后续自动任务不得改状态
|
||
```
|
||
|
||
### 3. 创建试跟配置必须复用 `CopyTradingService.createCopyTrading`
|
||
|
||
新增 `LeaderPoolService.createTrialConfig`,内部组装 `CopyTradingCreateRequest`,然后调用现有 `CopyTradingService.createCopyTrading`。
|
||
|
||
原因:
|
||
|
||
- 现有服务已经处理账户、leader、模板、监听更新、字段校验。
|
||
- 不复制跟单创建逻辑,避免两套规则不同步。
|
||
|
||
默认请求建议:
|
||
|
||
```text
|
||
enableImmediately = false
|
||
confirm = false
|
||
enabled = false
|
||
copyMode = FIXED
|
||
fixedAmount = suggestedFixedAmount 默认 1
|
||
maxOrderSize = suggestedFixedAmount 默认 1
|
||
minOrderSize = 1
|
||
maxDailyOrders = suggestedMaxDailyOrders 默认 10
|
||
maxDailyLoss = suggestedMaxDailyLoss 默认 5
|
||
priceTolerance = 1
|
||
minPrice = 0.10
|
||
maxPrice = 0.80
|
||
maxPositionValue = 5
|
||
supportSell = true
|
||
pushFailedOrders = true
|
||
pushFilteredOrders = true
|
||
configName = "Leader池-<leaderName或地址后6位>"
|
||
```
|
||
|
||
`enabled=false` 是推荐默认。若前端允许立即启用,必须弹出确认,并明确展示固定金额、最大日单数、最大日亏损和最大持仓。
|
||
|
||
后端也必须执行同一条安全规则:当 `enableImmediately=true` 时,`confirm` 必须为 `true`,否则拒绝创建。真钱交易不能只靠前端弹窗保护。
|
||
|
||
创建前还必须检查同一 `accountId + leaderId` 是否已经存在跟单配置。默认拒绝重复创建,并返回已有配置提示。PolyHermes 允许同一 leader 多个跟单配置,但 Leader 池的一键试跟不应该制造重复配置;真要高级配置,用户应该走 `跟单配置` 页面手动创建。
|
||
|
||
建议配置必须在后端校验,不只靠前端输入框:
|
||
|
||
```text
|
||
suggestedFixedAmount > 0
|
||
suggestedMaxDailyOrders between 1 and 100
|
||
suggestedMaxDailyLoss > 0
|
||
suggestedMinPrice / suggestedMaxPrice in [0, 1] when present
|
||
suggestedMinPrice <= suggestedMaxPrice when both present
|
||
suggestedMaxPositionValue > 0 when present
|
||
```
|
||
|
||
这是“雨露均沾”的安全边界:小额可以分散,但不能因为一个坏输入把单个 leader 变成隐形重仓。
|
||
|
||
### 4. 新增 leader 池 API,路径归在 copy-trading 下
|
||
|
||
新增控制器:
|
||
|
||
```text
|
||
backend/src/main/kotlin/com/wrbug/polymarketbot/controller/copytrading/leaderpool/LeaderPoolController.kt
|
||
```
|
||
|
||
API:
|
||
|
||
```text
|
||
POST /api/copy-trading/leader-pool/list
|
||
POST /api/copy-trading/leader-pool/add
|
||
POST /api/copy-trading/leader-pool/update-status
|
||
POST /api/copy-trading/leader-pool/update-plan
|
||
POST /api/copy-trading/leader-pool/create-trial-config
|
||
POST /api/copy-trading/leader-pool/remove
|
||
```
|
||
|
||
`list` 响应应聚合:
|
||
|
||
- leader 基础信息。
|
||
- 池子状态和建议配置。
|
||
- 该 leader 的跟单配置数量。
|
||
- 该 leader 是否已有启用中的跟单配置。
|
||
- 估算最坏暴露字段。
|
||
|
||
聚合必须使用批量查询或聚合查询,不能对每个池子项逐个查 leader 和跟单配置。计划新增 repository 方法,例如:
|
||
|
||
```text
|
||
LeaderRepository.findAllById(...)
|
||
CopyTradingRepository.findByLeaderIdIn(...)
|
||
CopyTradingRepository.countByLeaderIdInGrouped(...) 或用 findByLeaderIdIn 后内存分组
|
||
```
|
||
|
||
这保持 v1 足够简单,同时避免池子 50 人时打出 100 多次 SQL。软件会在你最懒得排查的时候提醒你什么叫 N+1。
|
||
|
||
主要数据流:
|
||
|
||
```text
|
||
Leader 管理
|
||
-> POST /leader-pool/add
|
||
-> LeaderPoolService.addToPool
|
||
-> copy_trading_leader_pool
|
||
|
||
Leader 池页面
|
||
-> POST /leader-pool/list
|
||
-> LeaderPoolService.getPoolList
|
||
-> batch read leaders + copy_trading
|
||
-> summary cards + table
|
||
|
||
Leader 池创建小额试跟
|
||
-> POST /leader-pool/create-trial-config
|
||
-> LeaderPoolService.createTrialConfig
|
||
-> CopyTradingService.createCopyTrading
|
||
-> copy_trading
|
||
-> pool.status = TRIAL only after success
|
||
```
|
||
|
||
### 5. 前端页面独立,Leader 管理只放轻入口
|
||
|
||
新增页面:
|
||
|
||
```text
|
||
frontend/src/pages/LeaderPool.tsx
|
||
```
|
||
|
||
修改路由:
|
||
|
||
```text
|
||
frontend/src/App.tsx
|
||
```
|
||
|
||
修改菜单:
|
||
|
||
```text
|
||
frontend/src/components/Layout.tsx
|
||
```
|
||
|
||
菜单位置:
|
||
|
||
```text
|
||
跟单交易
|
||
跟单配置
|
||
Leader 池
|
||
Leader 管理
|
||
跟单模板
|
||
回测
|
||
```
|
||
|
||
`LeaderList.tsx` 只新增“加入 Leader 池”操作。不要把池子状态和预算管理塞进 Leader 管理页。
|
||
|
||
`LeaderPool.tsx` 不默认逐行查询 leader 余额。Leader 管理页当前有逐个加载余额的重型交互,但池子页的核心任务是筛选、配仓和创建小额试跟配置;默认列表只展示后端一次返回的聚合字段,profile 和详情可以按需打开。
|
||
|
||
### 6. 预算概览只做提示,不做资金托管
|
||
|
||
页面顶部显示:
|
||
|
||
```text
|
||
池子人数
|
||
试跟中人数
|
||
估算最坏暴露
|
||
待处理风险
|
||
```
|
||
|
||
估算方式:
|
||
|
||
```text
|
||
TRIAL/ACTIVE 状态的池子项数量 * suggestedMaxPositionValue
|
||
```
|
||
|
||
如果某个池子项没有 `suggestedMaxPositionValue`,使用默认 5。
|
||
|
||
v1 可以先不持久化“总试验预算”。页面可用默认 50 显示提示,或提供前端输入但不存库。若实现持久化预算,优先使用现有系统配置能力,不新增复杂账户资金模型。
|
||
|
||
### 7. 错误态与空态
|
||
|
||
前端必须覆盖:
|
||
|
||
- 池子为空:提示从 `Leader 管理` 加入 leader,或在池子页选择已有 leader 加入。
|
||
- leader 已在池子:加入动作返回明确提示,不创建重复项。
|
||
- leader 不存在:提示先添加 leader。
|
||
- 没有账户:创建试跟配置前提示先添加账户。
|
||
- 创建配置失败:展示后端错误,不更新池子状态。
|
||
- 创建配置成功:池子状态更新为 `TRIAL`,并给出跳转到 `跟单配置` 的入口。
|
||
|
||
### 8. 调度与自动化
|
||
|
||
本次不新增定时任务。
|
||
|
||
后续 leaderboard radar 可以把候选写入 `copy_trading_leader_pool`,但必须遵守:
|
||
|
||
- 不自动创建真实跟单配置。
|
||
- 不改 `locked=true` 的池子项。
|
||
- 不自动把 `COOLDOWN` 或 `RETIRED` 改回试跟。
|
||
|
||
### 9. 可观测性
|
||
|
||
后端日志至少记录:
|
||
|
||
- leader 加入池子。
|
||
- 池子状态变化。
|
||
- 创建试跟配置成功/失败。
|
||
- 被拒绝的危险操作,例如重复加入、leader 不存在、账户不存在。
|
||
|
||
未来如果接入审计表,可把这些操作迁入审计事件。本次不强制新增审计表。
|
||
|
||
### 10. 错误码与国际化
|
||
|
||
Leader 池需要独立错误码,不要全部塞进 `BUSINESS_ERROR`。建议在现有错误码体系中新增:
|
||
|
||
```text
|
||
LEADER_POOL_NOT_FOUND(4251)
|
||
LEADER_POOL_ALREADY_EXISTS(4252)
|
||
LEADER_POOL_DUPLICATE_TRIAL_CONFIG(4253)
|
||
LEADER_POOL_CONFIRM_REQUIRED(4254)
|
||
SERVER_LEADER_POOL_LIST_FAILED(5451)
|
||
SERVER_LEADER_POOL_UPDATE_FAILED(5452)
|
||
SERVER_LEADER_POOL_CREATE_TRIAL_FAILED(5453)
|
||
```
|
||
|
||
这些编号避开当前 `ErrorCode` 中已经存在的 `4601` 冲突。实现时必须确认没有新增重复 code。
|
||
|
||
同时补齐 `messages_zh_CN.properties`、`messages_zh_TW.properties`、`messages_en.properties`。前端需要能看到明确错误,而不是一个泛泛的“业务逻辑错误”。
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- [Risk] 用户误以为加入池子等于已经跟单 → Mitigation:页面文案明确区分 `观察`、`小额试跟` 和真实 `跟单配置`,创建配置后给出配置状态。
|
||
- [Risk] 一键创建配置触发真钱交易 → Mitigation:默认 `enabled=false`;若支持立即启用,必须二次确认。
|
||
- [Risk] 双击或多标签重复创建试跟配置 → Mitigation:前端提交时进入 loading 并禁用按钮;后端按 `accountId + leaderId` 检查已有配置,默认拒绝重复创建。
|
||
- [Risk] 池子状态和跟单配置状态不一致 → Mitigation:list 响应聚合当前跟单配置数量和启用状态;不把池子状态当作真实交易状态。
|
||
- [Risk] 同一 leader 重复加入池子 → Mitigation:数据库唯一约束 `leader_id`,服务层返回幂等提示。
|
||
- [Risk] 并发加入同一 leader 时先查再写仍然冲突 → Mitigation:保留数据库唯一约束,并捕获唯一约束异常,返回“已在池子中”的明确结果。
|
||
- [Risk] 建议配置输入为负数、超大值或无效价格区间 → Mitigation:后端校验所有建议配置字段,拒绝危险配置,不只依赖前端表单。
|
||
- [Risk] 池子列表照搬 Leader 管理逐行余额查询导致页面变慢 → Mitigation:池子列表默认只用后端聚合字段,不逐行拉余额。
|
||
- [Risk] 页面诱导追涨 → Mitigation:v1 不做收益率排行榜主导,不做 AI 评分;重点展示状态、预算、风险和保守配置。
|
||
- [Risk] 预算只是估算,不等于真实账户风险 → Mitigation:页面文案使用“估算最坏暴露”,不展示为账户余额或保证金。
|
||
- [Risk] 后续自动 radar 覆盖人工判断 → Mitigation:预留 `locked` 字段,自动任务不得修改锁定项,不得复活冷却/淘汰项。
|
||
|
||
## Migration Plan
|
||
|
||
1. 新增 Flyway 迁移 `V41__create_copy_trading_leader_pool.sql`。
|
||
2. 部署后 Flyway 自动创建新表,不改动现有表数据。
|
||
3. 新增 API 和前端入口上线后,池子默认为空。
|
||
4. 用户可以从现有 `Leader 管理` 手动加入 leader。
|
||
5. 回滚时可隐藏前端菜单和 API;新表保留不会影响现有跟单流程。
|
||
6. 若必须数据库回滚,可删除 `copy_trading_leader_pool` 表;不会影响 `copy_trading_leaders` 和 `copy_trading`。
|
||
|
||
## Locked v1 Decisions
|
||
|
||
- v1 不持久化“总试验预算”。页面只展示默认 50 的试验预算提示和估算最坏暴露,不新增账户资金模型。
|
||
- v1 UI 不提供“创建后立即启用”开关。池子创建的小额试跟配置默认 `enabled=false`;`enableImmediately` 与 `confirm` 只作为后端防御和后续扩展预留。
|
||
- v1 UI 只暴露“候选、观察、小额试跟、冷却、淘汰”。`PAPER` 和 `ACTIVE` 保留在后端枚举中,第一版不进入筛选标签和主操作流。
|