arbitrage-engine/docs/STRATEGY_FACTORY_VALIDATION.md

31 KiB
Raw Permalink Blame History

策略工厂与信号引擎验证清单Strategy Factory & Signal Engine Validation

目的:把需要“逐项验证”的点全部列清楚,变成可打勾的 checklist。
范围:backend/signal_engine.pysingle-engine 数据发射源) + V5.4 策略工厂(strategies 表 → 信号 / 模拟盘)。
权威规格仍以 docs/arbitrage-engine-full-spec.md 为准,本文件只列“需要检验的点”。


1. 背景与角色划分(人话版)

  • single-enginebackend/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_indicatorsOI、多空比、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 0CVD 里 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_midtrades 列表按 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_fastcvd_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_000100k USD阈值可配置或硬编码
    - 实现:usd_val = price * qtyusd_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 中有一个缩放 bugwhale_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_prevoi_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_msmarket_indicators.timestamp_mssignal_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_iduuid是否被转为 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_thresholdwhale_usd_thresholdwhale_flow_pctvol_atr_pct_minspot_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/momentumevaluate_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_enabledgates["vol"]["enabled"]
    • vol_atr_pct_mingates["vol"]["vol_atr_pct_min"],默认值与 full-spec 一致。
  • Gate cvd
    • gate_cvd_enabledgates["cvd"]["enabled"]
    • 默认是否为 True并与 full-spec 一致。
  • Gate whale
    • gate_whale_enabledgates["whale"]["enabled"]
    • whale_usd_thresholdgates["whale"]["whale_usd_threshold"]
    • whale_flow_pctgates["whale"]["whale_flow_pct"](注意是否有 /100 的处理)。
      • 更新:evaluate_factory_strategy() 中已去掉多余的 /100 缩放,现使用 0~1 的比例值与 DB/迁移脚本一致,鲸鱼 Gate 灵敏度恢复到设计水平。
  • Gate obi
    • gate_obi_enabledgates["obi"]["enabled"]
    • obi_thresholdgates["obi"]["threshold"]
  • Gate spot_perp
    • gate_spot_perp_enabledgates["spot_perp"]["enabled"]
    • spot_perp_thresholdgates["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_volgate_block = "low_vol(...)",并且后续直接视为不通过。
  • Gate2CVD 共振门

    • 快慢 CVD 同向时方向判断是否为:
      fast>0 且 mid>0 → LONGfast<0 且 mid<0 → SHORT
    • 不同向时,若 gate_cvd_enabled=True则设置 no_direction=Truegate_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 价格计算:
    • LONGSL = price - risk_distanceTP1 = price + tp1_ratio × risk_distanceTP2 = price + tp2_ratio × risk_distance
    • SHORTSL = price + risk_distanceTP1 = price - tp1_ratio × risk_distanceTP2 = 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_signalpayload 格式是否是:
    "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+C9signal_indicators / paper_trades 落库字段,特别是 strategy_id / strategy_name_snapshot 与 SL/TP/risk_distance。

P1中优先级

  • B2~B7strategies 表字段映射完整性、CVD 窗口、Gate 参数、方向限制。
  • C1per-symbol 遍历逻辑与 symbol 过滤。
  • C8signal_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再看这份验证清单
确保“为什么这么算”和“有没有算对”都在一个有记录的地方。