双程序 AZ 值差异分析报告
双程序 AZ 值差异分析报告
分析日期:2026-03-10
涉及配对:PURR/USDC:USDC | HYPE/USDC:USDC
涉及程序:全币种通用版 (RealtimeKlineService) vs HYPE-PURR 专用版 (RealtimeKlineServiceHypePurr)
问题描述
同一配对 PURR|HYPE,在同一时刻、同样的价格下,两个独立运行的程序产出的 AZ(adaptive_z)值不一致。
日志示例:
2026-03-10 05:16:32 - 📉 移动止损触发 (峰值: 1.4% → 当前: 0.6%, 回撤: 0.8%):
PURR/USDC:USDC|HYPE/USDC:USDC | 当前价=$0.080539 | 浮动盈亏=$+0.21
2026-03-10 04:59:42 - ⚡ 近阈值 | PURR/USDC:USDC|HYPE/USDC:USDC |
az=+1.6205 (81%thresh) | z4h=+0.3761 | raw_std=0.2810 reg_std=0.3761 (x1.3)
AZ 计算链路
K线数据 (WS → cache)
↓
OLS 回归 (dual-window)
↓
z4h = (current_spread - spread_mean) / spread_std ← 第一层:analysis_core.py
↓
EMA 增量更新: ema = α * z4h + (1-α) * ema
Welford 增量标准差: effective_std = √(raw_var + floor²)
↓
az = (z4h - ema) / effective_std ← 第二层:strategy.py
关键代码位置:
- z4h 计算:
src/utils/analysis/analysis_core.py→calculate_zscore_ols() - az 计算:
src/trading/strategy.py:419-454→_update_adaptive_z() - 历史灌入:
src/trading/orchestrator.py:174-227→prime_buffer()
差异来源分析
第一层:z4h 本身不一致(已验证)
DB 实证:对同一 kline_time,两个程序在 <1 秒内写入的 z4h 存在差异。
SELECT kline_time, analysis_time, zscore_4h
FROM analysis_results
WHERE symbol = 'PURR/USDC:USDC' AND base_symbol = 'HYPE/USDC:USDC'
AND kline_time >= NOW() - INTERVAL '1 day'
ORDER BY kline_time DESC, analysis_time DESC;
实测数据(最近 24 小时采样):
| kline_time | time_a | time_b | z4h_a | z4h_b | diff | gap(s) |
|---|---|---|---|---|---|---|
| 01:20:00 | 01:21:42.490 | 01:21:42.493 | +0.3875 | +0.3870 | -0.0005 | 0.003 |
| 01:00:00 | 01:04:12.872 | 01:04:12.874 | +0.3053 | +0.3094 | +0.0041 | 0.002 |
| 01:00:00 | 01:01:12.429 | 01:01:12.994 | +0.3433 | +0.3369 | -0.0064 | 0.566 |
| 00:25:00 | 00:29:12.374 | 00:29:12.981 | +0.1599 | +0.1692 | +0.0093 | 0.607 |
| 00:20:00 | 00:20:26.829 | 00:20:27.364 | -0.0022 | -0.0103 | -0.0080 | 0.535 |
| 23:10:00 | 23:11:53.638 | 23:11:53.944 | +0.1335 | +0.1245 | -0.0090 | 0.306 |
统计:
- 差异频率:几乎每个 5min candle 都至少出现 1 次 z4h 不一致
- 差异幅度:0.0003 ~ 0.0093
- 写入间隔:0.002s ~ 0.6s
原因:两个程序各自维护独立的 K 线内存缓存 (_klines_cache)。WS tick 到达后,_refresh_kline_in_cache() 更新当前 forming candle 的 close 价格。两个程序的分析 worker 在不同瞬间取到的 close 价格不同 → OLS 回归结果不同 → z4h 不同。
第二层:az 的 EMA/std 状态分化(主因)
即使 z4h 只差 0.001~0.009,经过以下内存状态的累积,az 差异会被放大:
# strategy.py — 每个程序独立维护
bl.ema = alpha * z4h + (1 - alpha) * bl.ema # EMA 累积偏差
bl.welford_m2 += delta * delta2 # Welford 方差累积偏差
az = (z4h - ema) / effective_std # 除以 std 放大差异
放大机制:
- EMA 累积:每次 z4h 的微小差异通过
α * z4h注入 EMA,永不衰减到零 - std 分化:Welford 增量方差对输入序列的差异敏感,两套 std 独立演化
- 除法放大:
az = (z4h - ema) / std,分子分母都在分化,结果差异非线性增长 - 正向反馈:不同的 az → 不同的开仓决策 → 不同的后续行为
第三层:启动时历史灌入的交叉污染
# orchestrator.py:177-190 — 启动时从 analysis_results 灌入历史 z4h
SELECT symbol, base_symbol, kline_time, zscore_4h
FROM (
SELECT ..., ROW_NUMBER() OVER (
PARTITION BY symbol, base_symbol, kline_time
ORDER BY analysis_time DESC -- 取最新写入的那条
) AS rn
FROM analysis_results
WHERE zscore_4h IS NOT NULL AND kline_time >= NOW() - INTERVAL '30 days'
) sub WHERE rn = 1
两个程序写入同一张表,ORDER BY analysis_time DESC 去重后选到的可能是任一程序写入的 z4h。不同启动时刻 → prime_buffer 灌入不同的 z4h 序列 → EMA/std 初始状态不同。
结论
| 层级 | 差异来源 | 影响 | 是否 Bug |
|---|---|---|---|
| 第一层 | z4h 计算差异(K线缓存时差) | 每 tick ±0.001~0.009 | 否(并发时序的固有特性) |
| 第二层 | EMA/std 内存状态累积分化 | az 随时间越跑越远 | 否(有状态计算的数学特性) |
| 第三层 | 启动灌入交叉污染 | 初始状态即不同 | 否(架构设计层面的问题) |
本质:不是代码 Bug,而是 同一配对不应由两个独立进程同时计算 的架构问题。
建议方案
方案 A:配对互斥(推荐)
让同一配对只由一个程序分析和交易,避免状态分化。
实现方式:在全币种程序中跳过 HYPE-PURR 专用程序已覆盖的配对,或反之。
# realtime_kline_service.py — 全币种程序跳过专用配对
EXCLUSIVE_PAIRS = {('PURR/USDC:USDC', 'HYPE/USDC:USDC')}
# 在分析任务入队时过滤
if (symbol, base_symbol) in EXCLUSIVE_PAIRS:
continue
方案 B:analysis_results 去重写入
对同一 (symbol, base_symbol, kline_time) 在短时间内只保留第一次写入:
-- 写入时加 ON CONFLICT 跳过
INSERT INTO analysis_results (symbol, base_symbol, kline_time, ...)
VALUES (...)
ON CONFLICT (symbol, base_symbol, kline_time) DO NOTHING;
此方案仅解决第三层问题(灌入交叉污染),不解决第二层的内存状态分化。
方案 C:共享策略状态(复杂度高)
通过 Redis 等共享存储同步 EMA/std 状态,使两个程序对同一配对维护一致的策略引擎状态。复杂度高,不推荐。