arbitrage-engine/docs/STRATEGY_FACTORY_VALIDATION.md

498 lines
31 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 策略工厂与信号引擎验证清单Strategy Factory & Signal Engine Validation
> 目的:把需要“逐项验证”的点全部列清楚,变成可打勾的 checklist。
> 范围:`backend/signal_engine.py`single-engine 数据发射源) + V5.4 策略工厂(`strategies` 表 → 信号 / 模拟盘)。
> 权威规格仍以 `docs/arbitrage-engine-full-spec.md` 为准,本文件只列“需要检验的点”。
---
## 1. 背景与角色划分(人话版)
- single-engine`backend/signal_engine.py`)在本系统里扮演的是:
-`agg_trades` + `market_indicators` 等数据源,滚动算出各种指标;
- 每 15 秒左右,对每个运行中策略打分、判断是否开仓;
- 把结果写入:
- `signal_indicators`(信号与指标快照);
- `paper_trades`(模拟盘开仓记录);
- 并通过 NOTIFY 推送给其他进程。
- V5.4 策略工厂的职责是:
-`strategies` 表中 `status='running'` 的每一行,当成一个“策略实例”;
- 对每个实例,组合:
- 多窗口 CVD5m / 15m / 30m / 1h / 4h
- ALT 四层评分Direction / Crowding / Environment / Auxiliary
- 5 个否决 Gatevol / cvd / whale / obi / spot_perp的阈值与开关
- 在统一的信号模型上,跑出不同风格的策略。
当前假设:
- 你只对 **aggTrades 数据流** 有信心,其他所有东西(指标/门控/评分/策略映射/落库)都需要重新审计;
- 我们需要一个尽可能完整的“验证清单”,后续可以按照这份清单逐条打勾。
下面把需要检查的点分成 3 层:
- A. 数据与指标层:所有输入信号是不是对的(基础指标本身)
- B. 策略配置与工厂层DB 字段是否正确映射成策略参数
- C. 决策与落库层:评分 / 门控 / 开仓 / 写库 / 冷却 / 方向限制等是否符合规则
---
## 2. 总体结构视图(先有个地图)
- **数据来源**
- `agg_trades`:逐笔成交 → 多窗口 CVD / ATR / VWAP / 大单 P95/P99 / 巨鲸成交;
- `market_indicators`OI、多空比、Top Trader、资金费率、Coinbase Premium、OBI、期现背离等
- 实时 WebSocketOBI 实时值、spot-perp divergence 实时值(如果有)。
- **策略配置**
- `strategies`策略实例的参数symbol、方向、CVD 窗口、四层权重、五门阈值、TP/SL、timeout、flip_threshold 等);
- `load_strategy_configs_from_db()`:把这些行映射成内部配置 dict。
- **决策 / 落库**
- `evaluate_factory_strategy()`(原 `_evaluate_v53`):统一的评分与 Gates 逻辑;
- `save_indicator()`:写 `signal_indicators`
- `paper_open_trade()`:写 `paper_trades`
- `NOTIFY new_signal`:推送给 live_executor 等其他进程。
这 3 层每一处的小错误,都可能直接导致“赚钱 / 亏钱”的行为偏差,所以需要逐项验证。
---
## 3. A 层数据与指标验证single-engine 基础指标)
### A1. 时间线与窗口基础
-`agg_trades.time_ms` 的语义是否统一为 **毫秒时间戳**,无混用秒/毫秒的情况。
- ✅ 冷启动加载历史时,时间范围是否覆盖至少 `24h+`足够支持所有窗口5m/15m/30m/1h/4h/24h
- 当前实现:冷启动预载 4hWINDOW_MID之后由实时数据自然填充到 24h。此行为已知且暂视为接受的设计不视为 bug。
- ✅ 滚动窗口裁剪规则是否一致(例如统一使用 `t >= now_ms - window_ms`),避免 off-by-one。
- ✅ 历史回补或乱序插入时,是否会导致窗口内时间回跳,进而影响 CVD/ATR 等指标。
- 当前实现agg_trades_collector 通过 REST 补洞 + 连续性巡检,配合按 agg_id/time_ms 有序读取,认为不会产生系统性时间回跳问题。
### A2. CVD 多窗口计算
目标:确认 5m / 15m / 30m / 1h / 4h 所有 CVD 计算都符合“主动买入量 - 主动卖出量”的定义。
- ✅ 对 is_buyer_maker 的解释是否正确:
- 实现:采集时 `is_buyer_maker = 1 if t["m"] else 0`CVD 里 `is_buyer_maker == 0` 计为买量taker 买),`==1` 计为卖量taker 卖);
- 结论:与 Binance aggTrade 语义一致CVD = taker 买入量 - taker 卖出量,符合预期。
- ✅ 基础 30m / 4h 窗口(`win_fast` / `win_mid`)中,新增/裁剪 trade 时 buy/sell 累计是否严格同步更新。
- 实现:所有 trade 通过 `TradeWindow.add()` 进入窗口,`trim()` 时同步回退 buy/sell 累计CVD 始终等于当前窗口内的买减卖。
- ✅ 其他窗口5m/15m/1h的 CVD 是否通过对已有窗口win_fast/win_mid的列表按时间切片重算而不是重新查库。
- 实现:通过 `_window_ms()` 把窗口字符串转毫秒,从 `win_fast`/`win_mid` 的 `trades` 列表按 `t_ms >= now_ms - window_ms` 切片重算,未重复查库。
- ✅ 所有窗口的截止时间点是否都使用当前 `now_ms`不会出现“5m 用旧 now_ms30m 用新 now_ms”的错位。
- 实现:`build_evaluation_snapshot(now_ms)` 与 `evaluate_factory_strategy(now_ms, ...)` 使用同一个 `now_ms`,动态切片和快照在同一时间基准上计算。
- ⚠️ 斜率与加速度:
- ✅ 定义:`cvd_fast_slope = cvd_fast(now) - prev_cvd_fast``cvd_fast_accel = cvd_fast_slope(now) - prev_cvd_fast_slope`,在持续运行时语义正确;
- ⚠️ 冷启动第一帧使用 `prev_* = 0` 计算,未做专门回退为 0 的处理,可能使第一帧 slope/accel 数值偏大;目前视为需要后续评估/是否调整的注意点。
### A3. ATR 与 ATR 百分位
- ✅ 5m K 线 bucket 是否按 `bar_ms = (time_ms // period_ms) * period_ms` 计算,保证时间对齐。
- 实现:`ATRCalculator.update()` 中按 `time_ms // period_ms` 对齐 5m bar符合预期。
- ✅ Candle 的 open/high/low/close 更新逻辑是否正确,所有 trade 都被处理到。
- 实现:新 bar 时写入旧 bar 到队列,当前 bar 逐笔更新 high/low/close。
- ✅ TRTrue Range计算是否符合标准定义
- 实现:`max(high - low, |high - prev_close|, |low - prev_close|)`,与标准 TR 定义一致。
- ✅ ATR 是按何种算法计算(简单平均 / Wilder EMA与 full-spec 中的期望是否一致。
- 实现:先取第一根 TR 作为初始值,再用 `(atr_val*(length-1)+tr)/length` 逐步平滑,为 Wilder 风格 EMA符合设计。
- ✅ 当 candle 数量不足(小于 2 或小于 ATR lengthATR 返回 0 或合理默认值。
- 实现:`len(candles) < 2` 时直接返回 0.0避免未成熟 ATR 参与决策
- `atr_percentile`
- 历史 ATR 样本长度维护方式队列长度是否合理
- 百分位算法<= current / count是否正确
- 样本太少时是否返回 50.0避免无意义的极端值
- 实现`atr_history` 长度上限 288少于 10 条或当前 ATR=0 时返回 50.0其余按 rank 百分位计算
### A4. P95 / P99 大单阈值
- 使用的样本是否来自 24h 窗口`win_day`并且只取 qty不乘 price)。
- 实现`compute_p95_p99()` 直接遍历 `win_day.trades` `t[1]`qty构造样本数组窗口长度即 24h
- 样本数 < 100 时是否使用保底常数BTC: p95>=5, p99>=10ALT: p95>=50, p99>=100是否与策略设计一致。
- 实现:少于 100 条时直接返回 `(5.0, 10.0)`;样本足够时再按 symbol 区分 BTC/ALT 保底。
- ✅ 排序和索引算法:`sorted(qtys)` 后,`int(n * 0.95)` / `int(n * 0.99)` 是否符合预期的分位点定义。
- 实现:对 qtys 排序后按整数下标取 95% / 99% 位置,属于合理的分位点近似。
- ✅ ALT 的分类逻辑是否只是“非 BTC 即 ALT”以及这是否满足未来新增 symbol 的需求。
- 实现:判断条件为 `"BTC" in self.symbol`,否则按 ALT 处理;目前支持的 symbol 集合有限BTC/ETH/XRP/SOL该简化规则可接受如未来扩展再细化。
### A5. 巨鲸成交与 `whale_cvd_ratio`
- ✅ 巨鲸交易过滤条件是否为 `price * qty >= 100_000`100k USD阈值可配置或硬编码
- 实现:`usd_val = price * qty``usd_val >= 100_000` 视为巨鲸成交,阈值目前硬编码为 100k。
-`_whale_trades` 窗口长度是否为 15 分钟,裁剪逻辑是否正确(按 time_ms
- 实现:窗口长度 `WHALE_WINDOW_MS = 15min`,按 trade 的 `time_ms` 滑动裁剪。
-`whale_cvd_ratio` 计算:
- ✅ buy_usd = 所有巨鲸买单金额之和;
- ✅ sell_usd = 所有巨鲸卖单金额之和;
- ✅ ratio = (buy_usd - sell_usd) / (buy_usd + sell_usd)
- ✅ 无数据时返回 0.0 是否符合预期。
- ✅ BTC 与 ALT 对巨鲸数据的使用差异是否符合设计:
- 实现BTC 用 `whale_cvd_ratio`(或 DB 中 `tiered_cvd_whale`)做 GateALT 使用 `recent_large_trades` 中的对立/同向大单判断。
- 备注:鲸鱼 Gate 的“占比阈值”在 C2 中有一个缩放 bug`whale_flow_pct` 被多除以 100已在 Gate 小节标记为问题点。
### A6. `market_indicators` 中的 JSON 指标
需要逐个核对字段名称与意义(对照实际 JSON
-`long_short_ratio`
- 实现:从 `market_indicators` 读取 JSON `value`,使用 key `longShortRatio` 转为 float默认 1.0。
-`top_trader_position`
- 实现:使用 key `longAccount`,代表 top trader 多头占比0~1
-`open_interest_hist`
- 实现:使用 key `sumOpenInterestValue` 作为 OI 数值,后续只用来计算相对变化率。
-`coinbase_premium`
- 实现:使用 key `premium_pct`,并统一除以 100 转成小数,避免量纲混乱。
-`funding_rate`
- 实现:优先使用 `fundingRate`,否则 fallback `lastFundingRate`
-`obi_depth_10`
- 实现:使用 key `obi`,作为 [-1,1] 区间内的订单簿不平衡(正=买压,负=卖压),与 Gate4 逻辑一致。
-`spot_perp_divergence`
- 实现:使用 key `divergence`,注释中定义为 `(spot - mark) / mark`,与 Gate5 使用方式一致。
### A7. OI 环境指标
- ✅ OI 读取:`open_interest_hist` 是否转换为 float
- 实现:通过 `to_float(self.market_indicators.get("open_interest_hist"))` 转为 float。
- ✅ OI 变化率定义:`oi_change = (oi_now - oi_prev) / oi_prev`oi_prev>0 的保护是否到位。
- 实现:仅在 `prev_oi_value > 0` 时计算变化率,否则 oi_change=0.0。
- ✅ environment_score_raw 的区间划分(例如 >=3%、0~3%、<0对应基础分是否与 full-spec 一致
- 实现oi_change >=3% → 15 分;>0 → 10 分;<=0 → 5 分,符合设计。
-`prev_oi_value` 的更新时机:只在 oi_value>0 时更新是否会导致长时间停留在 0。
- 实现oi_value>0 时才覆盖 prev启动后首帧用默认 10 分,后续随 OI 变化更新,行为符合预期。
### A8. 其他环境与拥挤指标
-`long_short_ratio` 的逻辑:>1 多头拥挤,<1 空头拥挤这个假设需要与真实数据验证
- 实现LONG 方向时 lsr 较低<1,<0.7,<0.5给更高分SHORT 方向时 lsr 较高>1,>1.5,>2给更高分符合“多空占比”直觉。
- ✅ crowding 层中对 LONG / SHORT 的条件:
- 实现与上述区间一致,并在 V5.1 路径中用 crowding_score=ls_score+top_trader_scoreV5.3 路径中 crowding_score capped 为 25 分。
-`top_trader_position` 映射:
- 实现LONG>=0.55 给 10 分、<=0.45 给 0 分、其他给 5 分SHORT 反向对应“top trader 站在我们这一边时加分”的逻辑。
-`coinbase_premium` 阈值:
- 实现:>0.0005 / <-0.0005 视为强信号得 5 |premium|<=0.0005 2 其他情况 0 full-spec 描述一致
- 备注精确阈值是否需要微调属于策略优化问题暂不视为实现 bug
### A9. 时间与时区统一
- `agg_trades.time_ms`、`market_indicators.timestamp_ms`、`signal_indicators.ts` 是否全部为 UTC 毫秒
- 实现agg_trades 来自 Binance aggTrade `T`毫秒market_indicators 查询按 `timestamp_ms` 排序signal_indicators.ts `int(time.time()*1000)` 生成语义统一为毫秒
- signal_engine `now_ms` 是否与最新成交的时间差在可接受范围内比如 <5 )。
- 实现每轮循环用当前 `time.time()` 作为 now_ms立刻用 `fetch_new_trades` 拉取自上次 agg_id 之后的 tick 并入窗口逻辑上不会积累大延迟
- 所有窗口冷却时间timeout 等是否都用毫秒表达避免混用秒/分钟
- 实现窗口常量冷却时间timeout 以及 liquidations 窗口等全部以毫秒表示
---
## 4. B 层策略配置与工厂验证DB → 内存策略)
### B1. 行筛选与顺序
- `load_strategy_configs_from_db()` 是否只选择 `status='running'` 的行
- 实现SQL 显式 `WHERE status = 'running'`
- 是否有必要基于 `schema_version` 做过滤未来版本升级时)。
- 实现当前未按 `schema_version` 过滤所有 running 策略视为同一版本如未来引入多版本策略需要补充
- ORDER BY created_at 是否会在行为上产生影响一般无关但需要意识到)。
- 实现仅影响策略评估与写入的顺序对行为影响有限
### B2. 字段映射完整性
逐字段确认 DB cfg 的映射
- `strategy_id`uuid是否被转为 string并赋值到 `cfg["strategy_id"]`
- 实现SQL `strategy_id::text`后续直接写入 cfg
- `display_name` 是否写入 `cfg["strategy_name_snapshot"]`
- `symbol` / `direction` 是否原样保留大小写约定是否固定
- 实现直接从 DB 取值赋入 cfg不做大小写转换
- `cvd_fast_window` / `cvd_slow_window` 是否直接映射到 cfg
- `weight_direction` / `weight_env` / `weight_aux` / `weight_momentum` 是否正确组成 `cfg["weights"]`
- `entry_score` / `flip_threshold` 是否正确映射到 `cfg["threshold"]` / `cfg["flip_threshold"]`
- `gate_obi_enabled` / `gate_whale_enabled` / `gate_vol_enabled` / `gate_spot_perp_enabled` / `gate_cvd_enabled` 是否被正确翻译成 bool
- 实现直接使用 DB 中的 boolean 字段
- 所有 Gate 相关 float 字段`obi_threshold`、`whale_usd_threshold`、`whale_flow_pct`、`vol_atr_pct_min`、`spot_perp_threshold`
None 时有合理默认 full-spec 匹配)。
- 实现在转入 cfg 时对 None `or 默认值` 处理默认值来源于迁移脚本/ symbol_gates
- `sl_atr_multiplier` / `tp1_ratio` / `tp2_ratio` 是否正确映射到 `cfg["tp_sl"]`
- `timeout_minutes` 是否正确映射到 `cfg["timeout_minutes"]`
### B3. legacy 策略兼容逻辑
- [ ] 固定 UUID
- `...000053` `"v53"`
- `...000054` `"v53_middle"`
- `...000055` `"v53_fast"`
是否只对 deprecated legacy 策略生效
- [ ] 其他策略是否统一命名为 `custom_<uuid前8位>`与历史 `strategy` 字段约定一致
- [ ] DB 无策略时是否 fallback JSON 文件配置 DB 有策略时是否会重复加载 JSON
更新
- 固定 UUID 的映射逻辑正确且只通过 `LEGACY_UUID_MAP` 在内存层生效不会修改 DB 中的 strategy_id
- 其他策略统一命名为 `custom_<uuid前8位>`与历史 `strategy` 字段兼容
- main() 中当 DB 读不到策略异常时才 fallback JSON正常情况下不会 DB+JSON 混用
### B4. CVD 窗口参数
- `cvd_fast_window` / `cvd_slow_window` 的合法取值集合是否只支持 {5m,15m,30m,1h,4h}。
- 实现`_window_ms()` 支持任意 m/h策略层目前只用上述几种组合
- 非法值时行为抛出异常还是回退到默认 30m/4h
- 实现未匹配 "m"/"h" 后缀时回退到 30m对异常配置有兜底
- `_window_ms()` 的实现是否覆盖所有这些枚举值
- `evaluate_factory_strategy()` 中选择 trade 源时fast/slow 窗口与 win_fast/win_mid 的映射是否一致
- 实现fast 窗口长度 <=30m win_fast否则用 win_midslow 总是用 win_mid full-spec 描述一致
### B5. 四层权重与 entry/flip
- `weights` 是否已真实参与 scoringdirection/env/aux/momentum 四层权重)。
- 现状V5.4 `strategies` 中的权重字段被映射到 cfg["weights"]direction/env/aux/momentum`evaluate_factory_strategy()` 会将四个权重归一化到 100 并把 direction+momentum 总权重按 55:25 的比例拆分给 direction/crowding再按各层原始得分比例缩放真实影响 total_score
- `entry_score` 是否只用于达到该分数即可考虑开仓”,不会在别处被重载
- 实现 `evaluate_factory_strategy()` 中作为标准开仓阈值total_score < entry_score 时不会产生 signalsignal_engine 通过是否有 signal 决定是否尝试开仓
- `flip_threshold` 是否用于重仓 / 反向强平逻辑阈值关系是否是 `flip_threshold >= entry_score`
- 实现 `evaluate_factory_strategy()` total_score >= flip_threshold 时记为重仓 tier="heavy";在 `signal_engine` 中,当反向信号的 `score >= flip_threshold` 时触发 signal_flip 平仓。代码允许设置任意数值,上层应保证配置关系。
- ⚠️ 当 `flip_threshold < entry_score` 时,系统行为是否有定义或需要禁止。
- 现状:代码未禁止这种配置,会导致实际行为变成“更容易触发 heavy 仓”;需要在策略配置侧约束,暂不视为实现错误。
### B6. Gate 参数与开关
逐 Gate 检查 DB → cfg 的映射:
- [ ] Gate vol
- `gate_vol_enabled``gates["vol"]["enabled"]`
- `vol_atr_pct_min``gates["vol"]["vol_atr_pct_min"]`,默认值与 full-spec 一致。
- [ ] Gate cvd
- `gate_cvd_enabled``gates["cvd"]["enabled"]`
- 默认是否为 True并与 full-spec 一致。
- ✅ Gate whale
- `gate_whale_enabled``gates["whale"]["enabled"]`
- `whale_usd_threshold``gates["whale"]["whale_usd_threshold"]`
-`whale_flow_pct``gates["whale"]["whale_flow_pct"]`(注意是否有 /100 的处理)。
- 更新:`evaluate_factory_strategy()` 中已去掉多余的 `/100` 缩放,现使用 0~1 的比例值与 DB/迁移脚本一致,鲸鱼 Gate 灵敏度恢复到设计水平。
- [ ] Gate obi
- `gate_obi_enabled``gates["obi"]["enabled"]`
- `obi_threshold``gates["obi"]["threshold"]`
- [ ] Gate spot_perp
- `gate_spot_perp_enabled``gates["spot_perp"]["enabled"]`
- `spot_perp_threshold``gates["spot_perp"]["threshold"]`
### B7. 策略方向限制
- ⚠️ DB 中的 `direction` 字段枚举是否固定为 `"long" | "short" | "both"`(大小写统一)。
- 现状:代码层直接使用 DB 文本,不验证枚举/大小写;依赖上游迁移与 API 保证有效值。
-`evaluate_factory_strategy()` 或上层逻辑中是否有对该字段的应用:
- 预期direction=long → 禁止 SHORT 信号开仓direction=short → 禁止 LONG 信号开仓direction=both → 不限制。
- 现状:已在 `evaluate_factory_strategy()` 内增加 per-strategy 方向约束:
- long 策略在方向判定为 SHORT 时不会产生 signal但仍计算评分与指标快照
- short 策略在方向判定为 LONG 时不会产生 signal
- both 不做限制。
- ⚠️ 若当前实现未对 strategy direction 做约束,需要在此记为一个重点检查项。
- 更新:方向约束已在信号生成阶段生效,但反向 signal_flip 平仓仍按“环境方向”判断,是否也需要 direction 约束留待后续决策。
---
## 5. C 层:决策与落库验证(评分 + 门控 + 开仓 + 写库)
### C1. 逐 symbol / 逐策略评估流程
- ✅ 评估循环是否为:先按 symbol 分组,再对每个 symbol 内的所有策略依次评估。
- ⚠️ 对于每个策略,是否在评估前检查 `cfg["symbol"] == state.symbol`,确保不会在错误币种上开仓。
- 现状:评估阶段对所有策略都计算分数,但在开/平仓阶段才按 `strategy_cfg["symbol"]` 过滤,因此不会在错误币种上实际开仓,只是多算了一些“无用分”。
- ✅ 是否存在“一个策略在多个 symbol 上同时运行”的预期场景,如果有,需要明确规则。
- 现状:当前配置一条策略只绑定一个 symbol引擎逻辑允许同一策略在多个 symbol 开仓,但上层 API 不这么用。
### C2. 五个 Gate 的逻辑
对于每一门,需要验证条件与 full-spec 一致:
- Gate1波动率门ATR/price 下限)
-`atr_pct_price = atr / price` 的计算是否正确;
- ✅ 阈值 `min_vol` 是否来自 DB 的 `vol_atr_pct_min` 或 JSON fallback
- ✅ 条件:`atr_pct_price < min_vol` gate_block = `"low_vol(...)"`,并且后续直接视为不通过。
- Gate2CVD 共振门
- 快慢 CVD 同向时方向判断是否为
fast>0 且 mid>0 → LONGfast<0 mid<0 SHORT
- 不同向时 gate_cvd_enabled=True则设置 `no_direction=True` `gate_block="no_direction_consensus"`
- Gate2 关闭enabled=False是否允许仅根据 fast CVD 决定方向
- Gate3鲸鱼否决门
- BTC
- 使用 `whale_cvd_ratio` fallback `market_indicators["tiered_cvd_whale"]`
- 条件
- LONG whale_cvd_ratio < -阈值 否决
- SHORT whale_cvd_ratio > 阈值 → 否决。
- ALT
-`recent_large_trades` 中,对立大单与同向大单的判定逻辑是否正确;
- ✅ 只有“有对立大单且无同向大单”时才否决,且金额阈值是否为 whale_usd_threshold × price。
- Gate4OBI 否决门
- ✅ OBI 采样顺序是否为:先用实时 `rt_obi`,否则用 DB 中 `obi_depth_10`
- ✅ 条件:
- LONG 且 obi_raw < -obi_veto Gate 拒绝
- SHORT obi_raw > obi_veto → Gate 拒绝。
- Gate5期现背离否决门
- ✅ 采样顺序:先用实时 `rt_spot_perp_div`,否则用 DB 中 `spot_perp_divergence`
- ✅ 条件:
- LONG 且 divergence < -spd_veto Gate 拒绝
- SHORT divergence > spd_veto → Gate 拒绝。
### C3. gate_block / gate_passed 对评分和信号的影响
- ✅ 任意一门触发后,`gate_block` 是否被设置为对应字符串,`gate_passed=False`。
- ✅ 评分计算完后,如果 `gate_passed=False`,是否将 `total_score` 强制置为 0。
-`result["signal"]` 在 gate_passed=False 或 no_direction=True 时是否总是 None。
-`factors` 中是否仍然保留原始四层分数和 Gate 细节,方便事后分析。
### C4. 四层评分计算细节
- Direction Layer
- ✅ CVD 共振基础分数(例如 30 分)是否与 full-spec 一致;
- ✅ p99_flow有同向大单 / 无大单 / 有对立大单 三种情况的得分是否符合预期;
- ✅ accel_bonuscvd_fast_accel 与方向同向时报 5 分;
- ✅ v53_fast 的独立加速路径(不要求双线共振)是否正确实现。
- Crowding Layer
- ✅ long_short_ratio 与 top_trader_position 的区间划分与得分是否符合 full-spec
- ✅ crowding_score capped 到 25 分。
- Environment Layer
- ✅ ALT或默认路径只用 OI 变化率映射出 environment_score_raw
- ✅ v53_fast 额外的 OBI bonus强/弱阈值与得分 5/3 是否正确;
- ✅ 总分 capped 到 15。
- Auxiliary Layer
- ✅ coinbase_premium 的区间与得分5/2/0是否正确实施。
- 汇总:
- ✅ total_score = 四层得分之和 capped 到 100并四舍五入到 0.1
- ✅ 如果 `gate_passed=False`,最终 total_score 是否强制为 0。
### C5. 权重的实际使用情况
- ✅ 当前实现已经使用 `weights` 来调整各层最大分/权重direction/env/aux/momentum需要确认缩放规则是否符合预期
- ✅ future如要进一步调整权重逻辑需要设计迁移/验证方案,并评估对历史统计的影响。
### C6. signal 生成与冷却entry / flip / cooldown
- ✅ 条件逻辑:
- total_score >= flip_threshold → `tier="heavy"`
- entry_score <= total_score < flip_threshold `tier="standard"`
- total_score < entry_score 不开仓
- 冷却
- COOLDOWN_MS = 10 分钟
- `strategy_name` key last_signal_ts 是否正确记录
- 冷却期内不再生成新 signal
- `direction` 限制
- 策略 direction=long/short/both 是否在最终生成 signal signal_flip 时被应用
- 只多策略不会生成空头 signal也不会因为空头评估方向触发 signal_flip 平仓
- 只空策略反之同理
- both行为与原来一致
### C7. `signal_indicators` 落库字段映射
- ts是否使用当前 `now_ms`单位为毫秒
- symbol与当前 `SymbolState.symbol` 一致
- strategy写入 `cfg["name"]`legacy 名称)。
- strategy_id写入 `cfg["strategy_id"]`uuid string)。
- strategy_name_snapshot写入 `cfg["strategy_name_snapshot"]`
- 所有指标字段
- cvd_fast / cvd_mid / cvd_day / cvd_fast_slope
- atr_5m / atr_percentile / atr_value
- vwap_30m / price
- p95_qty / p99_qty
- cvd_fast_5m v53_fast 有值
是否与 `snapshot` / 重新计算的一致
- factorsJSON
- 是否包含 gate_passed / gate_block / atr_pct_price / obi_raw / spot_perp_div / whale_cvd_ratio
- direction / crowding / environment / auxiliary 四个子对象的 score/max 等字段是否齐全
### C8. `signal_feature_events`(如仍在使用)
- 只对 v53 系列策略写入策略名以 "v53" 开头)。
- 原始字段cvd_fast_raw 是否与 `result` 中的一致
- score_direction / score_crowding / score_environment / score_aux 是否与 factors 中一致
- gate_passed / block_reason 是否正确记录便于后期分析
### C9. `paper_open_trade` 模拟盘开仓逻辑
- ATR <= 0 时是否直接拒绝开仓避免无意义 SL/TP
- SL/TP 价格计算
- LONG`SL = price - risk_distance``TP1 = price + tp1_ratio × risk_distance``TP2 = price + tp2_ratio × risk_distance`
- SHORT`SL = price + risk_distance``TP1 = price - tp1_ratio × risk_distance``TP2 = price - tp2_ratio × risk_distance`
- 旧版 JSON 策略仍按 `tp*_mult × ATR` 方式计算属于兼容分支
是否与 full-spec R 计目标的定义一致
- risk_distance = sl_multiplier × ATR 是否正确计算并写入
- SL 合理性校验
- 实际 SL 距离是否在 [0.8, 1.2] × risk_distance 之间
- 不满足时是否拒绝开仓并打日志避免奇怪配置)。
- 模拟盘全局开关
- `PAPER_TRADING_ENABLED` / `PAPER_ENABLED_STRATEGIES` / `PAPER_MAX_POSITIONS` 是否生效
- 同一策略是否有 per-strategy 的最大持仓控制如无需要记录)。
- 写库字段
- symbol / direction / score / tier / entry_price / entry_ts
- tp1_price / tp2_price / sl_price / atr_at_entry
- score_factors = factors JSON
- strategy = `cfg["name"]`
- strategy_id / strategy_name_snapshot
- risk_distance
是否全部正确写入对应字段类型与含义与 full-spec 一致
### C10. 重复开仓与持仓交互
- 当前设计是否允许同一策略在同一 symbol 上同时有多笔持仓”(分批进场)。
- 是否存在逻辑限制同一策略同方向仅允许一笔活动持仓”,如果没有需要明确这一点
- `paper_monitor` 的交互
- 反向 signal 是否会触发 signal_flip 平仓
- 平仓后冷却和再开仓的行为是否符合预期
### C11. 通知与其他 side-effect
- `result["signal"]` 非空时是否总是触发 `NOTIFY new_signal`payload 格式是否是
`"symbol:strategy:signal:score"`
- gate_block 不为空gate_passed=False no_direction=True 是否绝不会发 NOTIFY
- 如果 live_executor 等进程依赖该 payload是否已经约定好格式且长期不变
---
## 6. 验证优先级建议
后续可以按下面优先顺序逐项打勾
**P0必须最先验证的**
- A2CVD 多窗口计算5m/15m/30m/1h/4h 全链路)。
- A3+A9ATR / ATR 百分位 / 波动率门
- A5+A6巨鲸OBI期现背离的数据源与符号方向
- C2+C3+C4+C6五个 Gate + 四层评分 + entry/flip + cooldown 的交互
- C7+C9`signal_indicators` / `paper_trades` 落库字段特别是 strategy_id / strategy_name_snapshot SL/TP/risk_distance
**P1中优先级**
- B2~B7`strategies` 表字段映射完整性CVD 窗口Gate 参数方向限制
- C1per-symbol 遍历逻辑与 symbol 过滤
- C8`signal_feature_events` 的字段映射如还在使用)。
**P2相对低优先级**
- 对权重缩放逻辑的长期行为做验证C5确保与预期一致
- 日志/监控是否足够支撑线上排查
- 未来如需长期在线学习可在此基础上加更多验证点
---
## 7. 使用方式(给人类 / AI 的操作建议)
- 把本文件当作 **审计 checklist**
- 每次改动前先在这里找相关条目确认期望行为是什么
- 审计现有代码时对照这些条目逐项比对实现与期望不一致的地方要记录为 bug
- 对于 P0 条目建议配合
- 真实历史数据回放从数据库抽样一段时间
- 针对单一策略 / 单一 symbol 的离线重算脚本对比 signal_indicators / paper_trades)。
- 改动任何 C 评分/开仓/落库逻辑时必须同时
- 更新 `docs/arbitrage-engine-full-spec.md` 中对应章节
- 在本验证清单中勾选/更新受影响的条目
- 附上简短的为什么这样改 + 对历史行为的影响说明
> 一句话:
> 以后任何人(包括 AI想动 signal_engine / 策略工厂,都应该先看 full-spec再看这份验证清单
> 确保“为什么这么算”和“有没有算对”都在一个有记录的地方。