31 KiB
策略工厂与信号引擎验证清单(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'的每一行,当成一个“策略实例”; - 对每个实例,组合:
- 多窗口 CVD(5m / 15m / 30m / 1h / 4h);
- ALT 四层评分(Direction / Crowding / Environment / Auxiliary);
- 5 个否决 Gate(vol / 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、期现背离等;- 实时 WebSocket:OBI 实时值、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)。
- 当前实现:冷启动预载 4h(WINDOW_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_ms,30m 用新 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。 - ✅ TR(True 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 length)时,ATR 返回 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>=10;ALT: 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)做 Gate;ALT 使用recent_large_trades中的对立/同向大单判断。 - 备注:鲸鱼 Gate 的“占比阈值”在 C2 中有一个缩放 bug(
whale_flow_pct被多除以 100),已在 Gate 小节标记为问题点。
- 实现:BTC 用
A6. market_indicators 中的 JSON 指标
需要逐个核对字段名称与意义(对照实际 JSON):
- ✅
long_short_ratio:- 实现:从
market_indicators读取 JSONvalue,使用 keylongShortRatio转为 float;默认 1.0。
- 实现:从
- ✅
top_trader_position:- 实现:使用 key
longAccount,代表 top trader 多头占比(0~1)。
- 实现:使用 key
- ✅
open_interest_hist:- 实现:使用 key
sumOpenInterestValue作为 OI 数值,后续只用来计算相对变化率。
- 实现:使用 key
- ✅
coinbase_premium:- 实现:使用 key
premium_pct,并统一除以 100 转成小数,避免量纲混乱。
- 实现:使用 key
- ✅
funding_rate:- 实现:优先使用
fundingRate,否则 fallbacklastFundingRate。
- 实现:优先使用
- ✅
obi_depth_10:- 实现:使用 key
obi,作为 [-1,1] 区间内的订单簿不平衡(正=买压,负=卖压),与 Gate4 逻辑一致。
- 实现:使用 key
- ✅
spot_perp_divergence:- 实现:使用 key
divergence,注释中定义为(spot - mark) / mark,与 Gate5 使用方式一致。
- 实现:使用 key
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_score;V5.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 aggTradeT(毫秒),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_mid;slow 总是用 win_mid,与 full-spec 描述一致。
B5. 四层权重与 entry/flip
- ✅
weights是否已真实参与 scoring(direction/env/aux/momentum 四层权重)。
- 现状:V5.4strategies中的权重字段被映射到 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 时不会产生 signal),signal_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(...)",并且后续直接视为不通过。
- ✅
-
Gate2:CVD 共振门
- ✅ 快慢 CVD 同向时方向判断是否为:
fast>0 且 mid>0 → LONG;fast<0 且 mid<0 → SHORT; - ✅ 不同向时,若 gate_cvd_enabled=True,则设置
no_direction=True且gate_block="no_direction_consensus"; - ✅ 当 Gate2 关闭(enabled=False)时,是否允许仅根据 fast CVD 决定方向。
- ✅ 快慢 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。
- ✅
- BTC:
-
Gate4:OBI 否决门
- ✅ OBI 采样顺序是否为:先用实时
rt_obi,否则用 DB 中obi_depth_10; - ✅ 条件:
- LONG 且 obi_raw < -obi_veto → Gate 拒绝;
- SHORT 且 obi_raw > obi_veto → Gate 拒绝。
- ✅ OBI 采样顺序是否为:先用实时
-
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_bonus:cvd_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 → 不开仓。
- total_score >= flip_threshold →
- ✅ 冷却:
- COOLDOWN_MS = 10 分钟;
- 以
strategy_name为 key 的 last_signal_ts 是否正确记录; - 冷却期内不再生成新 signal。
- ✅
direction限制:- 策略 direction=long/short/both 是否在最终生成 signal 和 signal_flip 时被应用:
- 只多策略:不会生成空头 signal,也不会因为空头评估方向触发 signal_flip 平仓;
- 只空策略:反之同理;
- both:行为与原来一致。
- 策略 direction=long/short/both 是否在最终生成 signal 和 signal_flip 时被应用:
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/ 重新计算的一致。
- ✅ factors(JSON):
- 是否包含 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 计目标”的定义一致。
- LONG:
- ✅ 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(必须最先验证的)
- A2:CVD 多窗口计算(5m/15m/30m/1h/4h 全链路)。
- A3+A9:ATR / 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 参数、方向限制。 - C1:per-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,再看这份验证清单,
确保“为什么这么算”和“有没有算对”都在一个有记录的地方。