14 KiB
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 地址库。
建议迁移:
backend/src/main/resources/db/migration/V41__create_copy_trading_leader_pool.sql
建议字段:
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
约束与索引:
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 精简
后端状态枚举预留:
CANDIDATE
WATCH
PAPER
TRIAL
ACTIVE
COOLDOWN
RETIRED
前端 v1 展示:
候选
观察
小额试跟
冷却
淘汰
原因:
- 后端预留可以让后续 radar 或自动规则不需要迁移状态字段。
- 前端不一次暴露太多状态,避免用户每天面对一堆概念。
- 锁定是独立布尔字段
locked,不是状态。这样TRIAL + locked、COOLDOWN + locked都能表达,不会把“交易阶段”和“自动任务是否可改”混在一起。
状态更新规则:
RETIRED不删除 leader 地址,只让池子项退出关注。COOLDOWN可以设置cooldownUntil。locked=true的池子项不允许被后续自动任务改状态。v1 没有自动任务,但字段先保留。
状态流:
+---------+
| RETIRED |
+---------+
^
|
CANDIDATE -> WATCH -> TRIAL -> ACTIVE
| | |
+----------+--------+
|
v
COOLDOWN
locked=true 是覆盖层,不是状态:
任何状态 + locked=true => 后续自动任务不得改状态
3. 创建试跟配置必须复用 CopyTradingService.createCopyTrading
新增 LeaderPoolService.createTrialConfig,内部组装 CopyTradingCreateRequest,然后调用现有 CopyTradingService.createCopyTrading。
原因:
- 现有服务已经处理账户、leader、模板、监听更新、字段校验。
- 不复制跟单创建逻辑,避免两套规则不同步。
默认请求建议:
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 池的一键试跟不应该制造重复配置;真要高级配置,用户应该走 跟单配置 页面手动创建。
建议配置必须在后端校验,不只靠前端输入框:
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 下
新增控制器:
backend/src/main/kotlin/com/wrbug/polymarketbot/controller/copytrading/leaderpool/LeaderPoolController.kt
API:
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 方法,例如:
LeaderRepository.findAllById(...)
CopyTradingRepository.findByLeaderIdIn(...)
CopyTradingRepository.countByLeaderIdInGrouped(...) 或用 findByLeaderIdIn 后内存分组
这保持 v1 足够简单,同时避免池子 50 人时打出 100 多次 SQL。软件会在你最懒得排查的时候提醒你什么叫 N+1。
主要数据流:
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 管理只放轻入口
新增页面:
frontend/src/pages/LeaderPool.tsx
修改路由:
frontend/src/App.tsx
修改菜单:
frontend/src/components/Layout.tsx
菜单位置:
跟单交易
跟单配置
Leader 池
Leader 管理
跟单模板
回测
LeaderList.tsx 只新增“加入 Leader 池”操作。不要把池子状态和预算管理塞进 Leader 管理页。
LeaderPool.tsx 不默认逐行查询 leader 余额。Leader 管理页当前有逐个加载余额的重型交互,但池子页的核心任务是筛选、配仓和创建小额试跟配置;默认列表只展示后端一次返回的聚合字段,profile 和详情可以按需打开。
6. 预算概览只做提示,不做资金托管
页面顶部显示:
池子人数
试跟中人数
估算最坏暴露
待处理风险
估算方式:
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。建议在现有错误码体系中新增:
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
- 新增 Flyway 迁移
V41__create_copy_trading_leader_pool.sql。 - 部署后 Flyway 自动创建新表,不改动现有表数据。
- 新增 API 和前端入口上线后,池子默认为空。
- 用户可以从现有
Leader 管理手动加入 leader。 - 回滚时可隐藏前端菜单和 API;新表保留不会影响现有跟单流程。
- 若必须数据库回滚,可删除
copy_trading_leader_pool表;不会影响copy_trading_leaders和copy_trading。
Locked v1 Decisions
- v1 不持久化“总试验预算”。页面只展示默认 50 的试验预算提示和估算最坏暴露,不新增账户资金模型。
- v1 UI 不提供“创建后立即启用”开关。池子创建的小额试跟配置默认
enabled=false;enableImmediately与confirm只作为后端防御和后续扩展预留。 - v1 UI 只暴露“候选、观察、小额试跟、冷却、淘汰”。
PAPER和ACTIVE保留在后端枚举中,第一版不进入筛选标签和主操作流。