市场水温算法设计方案:多因子热力累积模型v2
市场水温算法设计方案:多因子热力累积模型 v2
Market Temperature — Heat Accumulation Index (HAI)
一、背景与动机
1.1 核心观察
在加密市场中存在一个"水温"现象:
- 当锚定资产(BTC)价格持续运行在高位时,ALT 资产的 Beta 系数、相关性、跟随性会发生相变
- 高温持续 → ALT 表现暴躁:Beta 跳变、跟随性增强、波动幅度夸张
- 低温持续 → ALT 表现迟钝:Beta 低迷、跟随性差、情绪摆烂
关键特征:不是瞬时温度决定行为,而是温度在高位维持足够长时间后,行情才发生质变。
1.2 在量化金融中的对应
这个现象在学术界对应 Market Regime Detection(市场状态识别):
| 方法 | 优点 | 缺点 | 适用性 |
|---|---|---|---|
| HMM(隐马尔可夫) | 学术主流,概率输出,可在线化(Particle Filter HMM) | 状态数需预设,标准实现需离线训练 | 可作为长期升级路径,但当前架构不需要 |
| Markov Switching | 经济学标准,Hamilton (1989) | 计算较重,宏观频率,非 5m 级别 | 不适合:时间尺度不匹配 |
| Hawkes Process(自激点过程) | 天然建模"热量累积→爆发",金融传染领域前沿 | 实现复杂,参数估计需 MLE | 可作为 P3 升级路径 |
| Absorption Ratio (Kritzman 2010) | PCA 测量系统性风险集中度,前沿 | 需多币种收益率矩阵 | 纳入因子 6 |
| 深度学习(VAE/Transformer) | 理论上限高 | 需训练数据/GPU,黑箱 | 不适合:过度工程 |
| 多因子 Leaky Integrator | 增量计算,可解释,零训练,自然均衡 | 权重需调优(本方案用自适应权重解决) | 最适合:完美匹配现有架构 |
1.3 选择理由
本系统已有多个可复用的构建组件:
- Rogers-Satchell 波动率估计(
momentum_filter.py) - BOCPD 趋势概率(
momentum_filter.py) - Huber-CUSUM 急动检测(
momentum_filter.py) - Efficiency Ratio 趋势强度(
momentum_filter.py) - Volume EMA 量能放大(
momentum_filter.py) - 多币种协整分析(
analysis_core.py— 可用于提取 Realized Correlation)
无需引入新的外部依赖或数据源,只需将已有信号聚合为市场级温度指标。
1.4 与 v1 方案的关键差异
| 维度 | v1 方案 | v2 方案(本文) |
|---|---|---|
| 因子权重 | 固定硬编码 0.25/0.25/0.20/0.15/0.15 | Inverse-Vol 自适应权重 |
| 归一化 | 混合(部分百分位,部分直接映射) | 全部统一滚动百分位 |
| 热力累积 | 二元模式(只累积或只衰减) | Leaky Integrator(始终衰减 + 高温注入) |
| 累积器归一化 | Sigmoid(参数敏感) | 累积值滚动百分位(自适应) |
| Regime 输出 | 硬三态分类 | 模糊隶属度(概率分布 → 平滑调参) |
| 因子数 | 5 个(纯 BTC 价量) | 6 个(新增 Realized Correlation) |
| HMM 评估 | 完全否定 | 客观评估,列为长期升级路径 |
二、算法架构
2.1 整体结构
┌───────────────────────────────────────────────────┐
│ BTC 5m K 线数据(WebSocket) │
│ + ALT 收益率相关性数据(已有分析结果) │
└──────────────────────┬────────────────────────────┘
│
┌────────────▼────────────────┐
│ 第一层:瞬时温度计 │
│ (6 因子自适应加权 → 0~100) │
│ │
│ RS波动率 × w₁(自适应) │
│ BOCPD趋势概率 × w₂(自适应) │
│ CUSUM急动幅度 × w₃(自适应) │
│ ER趋势强度 × w₄(自适应) │
│ 量能放大比 × w₅(自适应) │
│ Realized Corr × w₆(自适应) │
│ │
│ 权重 = 1/σᵢ 归一化 │
└────────────┬────────────────┘
│ instant_temp (0~100)
┌────────────▼────────────────┐
│ 第二层:Leaky Integrator │
│ (始终衰减 + 高温注入) │
│ (自然均衡点,无需硬上限) │
│ │
│ h(t) = λ·h(t-1) + g·Δ(t) │
│ 半衰期 ≈ 11.5 小时 │
└────────────┬────────────────┘
│ heat_percentile (0~100)
┌────────────▼────────────────┐
│ 第三层:模糊 Regime 输出 │
│ │
│ p_cold + p_warm + p_hot = 1 │
│ 策略参数 = Σ pᵢ × paramᵢ │
│ (平滑过渡,无硬切换) │
└─────────────────────────────┘
2.2 与现有系统的关系
┌─ Beta 体制过滤器(配对级,已实现)──────────────────┐
│ β_divergence = max(0, β_short - β_long) │
│ → 检测单个配对的 β 结构性变化 │
│ → 拦截协整失效期的入场 │
└──────────────────────────────────────────────────┘
┌─ momentum_filter(配对级过滤)─────────────────────┐
│ Layer 0: BOCPD → 单个配对的机制切换 │
│ Layer 1: ER → 单个配对的趋势强度 │
│ Layer 2: CUSUM → 单个配对的急动检测 │
│ Layer 3: 平稳性 → 单个配对的均值回复能力 │
└──────────────────────────────────────────────────┘
┌─ market_temperature(市场级过滤)── 本方案 ─────────┐
│ 聚焦 BTC + 跨币种相关性,输出全局温度概率分布 │
│ 影响所有 ALT 配对的入场/出场参数 │
└──────────────────────────────────────────────────┘
决策流程:
should_enter()
├─ ① market_temperature.regime_probs → 市场级参数调整
├─ ② beta_regime.check() → 配对级 β 拦截
└─ ③ momentum_filter.check() → 配对级动量过滤
三、第一层:瞬时温度计
3.1 六因子定义
因子 1:Rogers-Satchell 波动率
已有实现位置:momentum_filter.py 中 _update_rs_vol()
# Rogers-Satchell 波动率(OHLC 估计,不受漂移影响)
rs_var = log(H/C) * log(H/O) + log(L/C) * log(L/O)
rs_vol = sqrt(EMA(rs_var, span=rs_span))
归一化:滚动百分位排名(过去 7 天 = 2016 根 5m bar)
rs_score = rolling_percentile(rs_vol, window=2016) # 输出 0~100
选择理由:
- 基础风险度量,最稳定的温度底色
- Rogers-Satchell 不受漂移影响(比 Parkinson 更适合有趋势的市场)
因子 2:BOCPD 趋势概率
已有实现位置:momentum_filter.py 中 _bocpd_trend_probability()
# Bayesian Online Changepoint Detection (Adams & MacKay, 2007)
# 输出:P(|μ| > drift_threshold | data)
trend_prob = bocpd.trend_probability # 0.0 ~ 1.0
归一化:滚动百分位排名
bocpd_score = rolling_percentile(trend_prob, window=2016) # 输出 0~100
选择理由:
- 直接测量 regime shift 概率
- 在线贝叶斯推断,无滞后
- 已在 momentum_filter Layer 0 中验证可靠
v1→v2 变更:v1 直接映射 trend_prob * 100,导致分布偏斜,与百分位归一化因子的有效权重不一致。v2 统一用百分位。
因子 3:CUSUM 急动幅度
已有实现位置:momentum_filter.py 中 _huber_cusum_check()
# 取双向 CUSUM 最大值,反映最大偏离幅度
cusum_max = max(S_pos, S_neg)
归一化:滚动百分位排名
cusum_score = rolling_percentile(cusum_max, window=2016) # 输出 0~100
选择理由:
- 捕捉急剧价格变动(暴涨/暴跌)
- Huber 裁剪已内置,对插针鲁棒
- 与波动率互补:波动率是"震幅",CUSUM 是"方向性冲击"
因子 4:Efficiency Ratio
已有实现位置:momentum_filter.py 中 _er_check()
# Kaufman ER = 位移 / 路径长度
displacement = abs(close[-1] - close[-er_window])
volatility = sum(abs(close[i] - close[i-1]) for i in range(er_window))
er = displacement / volatility # 0.0 ~ 1.0
归一化:滚动百分位排名
er_score = rolling_percentile(er, window=2016) # 输出 0~100
选择理由:
- 测量趋势的"纯度"——ER 高说明价格单向运动
- 与水温概念对应:高 ER = 市场有方向 = 温度升高
v1→v2 变更:v1 直接映射 er * 100。ER 分布通常集中在 0.05~0.30,直接映射时 score 永远在 5~30 之间,实际贡献远低于标称权重。v2 统一用百分位消除此偏差。
因子 5:量能放大比
已有实现位置:momentum_filter.py 中成交量 EMA
# 当前成交量 / EMA 成交量
vol_ratio = current_volume / ema_volume
归一化:滚动百分位排名
volume_score = rolling_percentile(vol_ratio, window=2016) # 输出 0~100
选择理由:
- 量价验证:无量上涨是虚火,放量上涨才是真热
- 与其他因子互补:唯一的非价格维度
因子 6:Realized Correlation(新增)
数据来源:analysis_core.py 中已有多币种收益率数据
# 计算 top-N ALT 与 BTC 的滚动相关系数均值
# 当所有 ALT 相关性趋向 1.0 → 市场系统性风险上升 → 配对策略最危险
def _compute_realized_correlation(self, btc_returns: deque, alt_returns_dict: dict) -> float:
"""计算 BTC 与活跃 ALT 的平均相关性
Args:
btc_returns: BTC 5m 收益率序列 (至少 60 根)
alt_returns_dict: {symbol: deque[returns]} 各 ALT 的 5m 收益率
Returns:
mean_corr: 平均相关系数 (0.0 ~ 1.0)
"""
if len(btc_returns) < 60 or len(alt_returns_dict) < 3:
return 0.5 # 数据不足时返回中性值
btc = np.array(btc_returns)
correlations = []
for sym, alt_ret in alt_returns_dict.items():
if len(alt_ret) < 60:
continue
alt = np.array(alt_ret)[-len(btc):]
if len(alt) < 60:
continue
corr = np.corrcoef(btc[-60:], alt[-60:])[0, 1]
if not np.isnan(corr):
correlations.append(abs(corr))
if len(correlations) < 3:
return 0.5
return float(np.mean(correlations))
归一化:滚动百分位排名
corr_score = rolling_percentile(mean_corr, window=2016) # 输出 0~100
选择理由:
- 最直接的配对交易风险信号:当所有 ALT 相关性 → 1.0 时,配对交易的均值回复假设最容易失效
- 这是 Absorption Ratio (Kritzman et al., 2010) 的简化版,计算成本极低
- 其他 5 个因子都是 BTC 自身的价量指标,Realized Correlation 是唯一的跨资产结构指标
- 直接预测配对策略表现,而不是间接通过 BTC 波动推断
3.2 自适应加权:Inverse-Volatility Weighting
为什么不用固定权重?
固定权重的核心问题:
- 不同市场周期下各因子预测力差异巨大
- 因子之间存在非线性相关性(RS vol 和 CUSUM 高度相关),固定权重会双重计算
- 真正"最优权重"是时变的
Inverse-Vol Weighting 原理:
各因子的有效贡献应当均衡。如果某因子近期波动很大(贡献不稳定),就降低其权重;波动小(信号稳定)的因子权重提高。
class AdaptiveWeights:
"""Inverse-Volatility 自适应因子权重
原理:
- 波动大的因子 → 信号不稳定 → 降权
- 波动小的因子 → 信号稳定 → 加权
- 每个因子的权重 = 1/σᵢ,然后归一化使 Σwᵢ = 1
"""
def __init__(self, n_factors: int = 6, vol_window: int = 288):
"""
Args:
n_factors: 因子数量
vol_window: 计算因子波动率的窗口 (288 = 1天的 5m bar)
"""
self.n_factors = n_factors
self.vol_window = vol_window
self._buffers: list[deque] = [deque(maxlen=vol_window) for _ in range(n_factors)]
self._default_weights = [1.0 / n_factors] * n_factors # 预热期等权
def update(self, scores: list[float]) -> list[float]:
"""输入各因子百分位得分,返回自适应权重
Args:
scores: 各因子百分位得分 [f1, f2, ..., f6],每个 0~100
Returns:
weights: 归一化权重 [w1, w2, ..., w6],Σ = 1.0
"""
assert len(scores) == self.n_factors
for i, s in enumerate(scores):
self._buffers[i].append(s)
# 预热期:数据不足时用等权
if any(len(buf) < 30 for buf in self._buffers):
return self._default_weights.copy()
# 计算各因子近期标准差
stds = []
for buf in self._buffers:
data = list(buf)
n = len(data)
mean = sum(data) / n
var = sum((x - mean) ** 2 for x in data) / max(n - 1, 1)
stds.append(max(var ** 0.5, 1e-6)) # 防除零
# Inverse-Vol: weight_i = 1/σ_i
inv_vols = [1.0 / s for s in stds]
total = sum(inv_vols)
weights = [iv / total for iv in inv_vols]
return weights
特性:
- 预热期(<30 根 bar)自动使用等权
1/6 ≈ 0.167 - 正常运行后动态调整,贡献均衡化
- 无需人工调参,完全自适应
- 计算量极低:每次 update 仅 O(n_factors) 操作
与 v1 固定权重的对比:
| 场景 | v1 固定权重 | v2 Inverse-Vol |
|---|---|---|
| ER 长期低波 | 权重 0.15(标称),实际贡献偏低 | 自动加权(低波 = 稳定信号) |
| CUSUM 和 RS 高相关 | 双重计算,有效权重 0.45 | 相关因子同波 → 自然降权 |
| 某因子在当前市场周期失效 | 仍保持固定权重 | 失效因子波动大 → 自动降权 |
3.3 加权合成
def compute_instant_temp(self, scores: list[float]) -> float:
"""计算瞬时温度
Args:
scores: 6 个因子的百分位得分,各 0~100
[rs, bocpd, cusum, er, volume, corr]
Returns:
instant_temp: 加权温度 0~100
"""
weights = self._adaptive_weights.update(scores)
instant_temp = sum(w * s for w, s in zip(weights, scores))
return instant_temp # 0~100
3.4 归一化策略:统一滚动百分位
核心改进:所有 6 个因子统一使用滚动百分位归一化
v1 的问题:
RS vol → 滚动百分位 → 分布均匀 [0, 100]
BOCPD → 直接 ×100 → 分布偏斜 (大部分时间 10~30)
CUSUM → 滚动百分位 → 分布均匀 [0, 100]
ER → 直接 ×100 → 分布偏斜 (大部分时间 5~30)
Volume → 滚动百分位 → 分布均匀 [0, 100]
⇒ BOCPD 和 ER 的"有效权重"远低于标称值
⇒ 实际温度主要由 RS/CUSUM/Volume 三个因子驱动
⇒ 标称权重 0.25/0.25/0.20/0.15/0.15 名不副实
v2 的解决:
所有因子 → 滚动百分位 → 分布均匀 [0, 100]
⇒ 每个因子的贡献与权重一致
⇒ Inverse-Vol 自适应权重才能真正发挥作用
滚动窗口选择:7 天(2016 根 5m bar)
- 太短(1 天):百分位波动剧烈,温度抖动
- 太长(30 天):对近期变化不敏感
- 7 天:平衡稳定性与敏感性,覆盖一个完整的周周期
实现方式:增量排序缓冲区(deque + bisect),O(log n) 插入 + O(log n) 查询
from collections import deque
import bisect
class RollingPercentile:
"""增量滚动百分位计算器"""
def __init__(self, window: int = 2016):
self.window = window
self.values = deque(maxlen=window) # FIFO 原始值
self.sorted_vals = [] # 排序副本
def update(self, value: float) -> float:
"""添加新值,返回当前百分位 (0~100)"""
# 移除最旧值
if len(self.values) == self.window:
old = self.values[0]
idx = bisect.bisect_left(self.sorted_vals, old)
self.sorted_vals.pop(idx)
# 添加新值
self.values.append(value)
bisect.insort(self.sorted_vals, value)
# 计算百分位
rank = bisect.bisect_right(self.sorted_vals, value)
return (rank / len(self.sorted_vals)) * 100
四、第二层:Leaky Integrator 热力累积器
4.1 核心思想
瞬时温度只告诉你"现在热不热",但水温模型的关键洞察是:
持续高温一段时间后,市场性格才会改变
4.2 v1 模型的物理缺陷
v1 的热力累积器:
# v1(有缺陷)
if temp > base_temp:
heat += delta # 只加不减
else:
heat *= decay # 只减不加
问题:
- 当 temp = 80 时,热量只增不衰,会线性无限增长直到碰上 max_heat 天花板
- 现实中应该是始终衰减 + 高温时额外注入
- 需要 max_heat 硬上限 + sigmoid 归一化来补丁,但 sigmoid 参数敏感
4.3 v2 模型:Leaky Integrator(漏积分器)
class LeakyHeatAccumulator:
"""Leaky Integrator 热力累积器
物理模型:
- 每个时间步热量都在衰减(散热)
- 高温时额外注入热量(加热)
- 当注入 = 衰减时达到自然均衡点
- 无需硬上限,系统自然收敛
公式:
h(t) = λ × h(t-1) + g × max(0, T(t) - T_base)
均衡点(ΔT = T - T_base 恒定时):
h_eq = g × ΔT / (1 - λ)
示例(λ=0.995, g=0.15, ΔT=30):
h_eq = 0.15 × 30 / (1 - 0.995) = 900
"""
def __init__(
self,
base_temp: float = 50.0, # 基准温度(低于此不注入)
decay_factor: float = 0.995, # 每根 5m bar 的衰减系数
gain: float = 0.15, # 注入增益
):
self.base_temp = base_temp
self.decay_factor = decay_factor
self.gain = gain
self.heat = 0.0
def update(self, instant_temp: float) -> float:
"""每根 5m K 线调用一次
Args:
instant_temp: 瞬时温度 (0~100)
Returns:
heat: 当前累积热量(无上限,由滚动百分位归一化)
"""
excess = max(0.0, instant_temp - self.base_temp)
self.heat = self.decay_factor * self.heat + self.gain * excess
return self.heat
4.4 参数设计
基准温度 base_temp = 50
- 百分位 50 = 中位数 = "正常"水平
- 低于 50 的温度不会注入热量,但热量仍在衰减
- 含义:只有"高于正常"的状态才会推动 regime 变化
衰减系数 decay_factor = 0.995
半衰期计算:
ln(0.5) / ln(0.995) = 138.3 根 5m bar
138.3 × 5min = 691.5 min ≈ 11.5 小时
含义:
- BTC 从 80° 突然降到 40°
- ALT 的"暴躁状态"还会持续约 11.5 小时才衰减一半
- 这符合观察:ALT 的反应是迟缓的,不会立刻冷却
注入增益 gain = 0.15
与 v1 的 accumulation_rate = 1.0 不同,v2 的 gain 更小,因为 Leaky Integrator 同时在衰减和注入。
均衡分析:
当 instant_temp 恒定为 T 时,均衡热量:
h_eq = gain × max(0, T - base_temp) / (1 - decay_factor)
示例:
T=50 (中位温度): h_eq = 0.15 × 0 / 0.005 = 0 (无热量)
T=60 (温和偏高): h_eq = 0.15 × 10 / 0.005 = 300
T=70 (明显偏高): h_eq = 0.15 × 20 / 0.005 = 600
T=80 (高温): h_eq = 0.15 × 30 / 0.005 = 900
T=90 (极高温): h_eq = 0.15 × 40 / 0.005 = 1200
T=100 (最高): h_eq = 0.15 × 50 / 0.005 = 1500
热量有自然上界(不需要 max_heat 硬上限),且上界由物理参数决定而非人为钳位。
4.5 热量归一化:滚动百分位(替代 Sigmoid)
v1 使用 Sigmoid 归一化,但存在参数敏感问题(midpoint 设在 30% 处导致过早饱和)。
v2 使用滚动百分位归一化热量值,与第一层因子归一化策略一致:
self._heat_percentile = RollingPercentile(window=2016)
def normalized_heat(self) -> float:
"""将累积热量归一化到 0~100"""
return self._heat_percentile.update(self.heat)
优势:
- 与第一层归一化策略一致,无额外参数
- 自动适应不同市场环境下的热量分布
- 不存在 Sigmoid 的参数敏感问题(midpoint、k 值)
- 低温区和高温区分辨率自然均衡
4.6 Leaky Integrator 动力学示意
瞬时温度 (instant_temp)
100│ ╱╲
│ ╱ ╲ ╱╲
80│────────╱────╲────╱──╲──── 高温线
│ ╱ ╲ ╱ ╲
50│──────╱────────╲╱──────── 基准线(注入阈值)
│ ╱ ← 低于基准:无注入,仅衰减
0└─────────────────────────── 时间
累积热量 (heat) — 注意:始终在衰减
100│ ╱──── ← 持续高温:注入 > 衰减,稳步上升
│ ╱
70│──────────────────╱────── 高隶属度区
│ ╱╲ ╱
│ ╱ ╲ ╱ ← 第一次高温不够持久:
30│─────────╱────╲╱───────── 注入停止后,衰减占主导
│ ╱ ↘ 热量自然下降(无硬切换)
│ ╱
0└─────────────────────────── 时间
↑ ↑
第一波高温 第二波持续高温
(注入后衰减) (注入持续 > 衰减
热量稳步累积)
4.7 v1 vs v2 累积器对比
| 特性 | v1 Binary Accumulator | v2 Leaky Integrator |
|---|---|---|
| 高温时 | 只加不减(线性增长) | 加减同时(收敛到均衡) |
| 低温时 | 只减不加(指数衰减) | 只减不加(指数衰减) |
| 上界 | 需要 max_heat 硬上限 | 自然上界 = g×ΔT/(1-λ) |
| 归一化 | Sigmoid(参数敏感) | 滚动百分位(自适应) |
| 物理含义 | 不符合热力学 | 标准散热+加热模型 |
| 均衡分析 | 无法解析 | h_eq = g×ΔT/(1-λ) |
五、第三层:模糊 Regime 分类与平滑策略调整
5.1 v1 硬分类的问题
v1 使用硬阈值 30/70 + 滞后:
heat=69 → WARM (正常参数)
heat=71 → HOT (仓位减半,阈值×1.5)
问题:
- heat 从 69→71 的微小变化导致策略参数跳变
- 阈值 30/70 缺乏统计依据
- heat=31 和 heat=69 都是 WARM,但风险差异巨大
5.2 v2 方案:模糊隶属度
用 softmax 输出各 regime 的概率分布,策略参数连续平滑调整:
import math
class FuzzyRegimeClassifier:
"""模糊 Regime 分类器
输出 (p_cold, p_warm, p_hot) 概率分布,
策略参数为各 regime 参数的加权平均。
核心公式 (log-linear softmax):
score_cold = -(heat / tau)
score_warm = -((heat - 50)² / (2 × sigma²))
score_hot = (heat - 100) / tau
p_i = exp(score_i) / Σ exp(score_j)
参数语义:
tau: 控制 cold/hot 端的灵敏度(越小越尖锐)
sigma: 控制 warm 区间的宽度(越大越宽)
"""
def __init__(self, tau: float = 15.0, sigma: float = 18.0):
self.tau = tau
self.sigma = sigma
def classify(self, heat_percentile: float) -> dict[str, float]:
"""返回各 regime 的隶属度
Args:
heat_percentile: 归一化热量 (0~100)
Returns:
{"cold": p, "warm": p, "hot": p},Σ = 1.0
"""
h = heat_percentile
# 各 regime 的 log 得分
score_cold = -(h / self.tau)
score_warm = -((h - 50.0) ** 2) / (2.0 * self.sigma ** 2)
score_hot = (h - 100.0) / self.tau
# Softmax(数值稳定版)
scores = [score_cold, score_warm, score_hot]
max_score = max(scores)
exps = [math.exp(s - max_score) for s in scores]
total = sum(exps)
return {
"cold": exps[0] / total,
"warm": exps[1] / total,
"hot": exps[2] / total,
}
参数含义:
tau = 15.0:
heat=0 → p_cold ≈ 0.95, p_warm ≈ 0.05, p_hot ≈ 0.00
heat=20 → p_cold ≈ 0.65, p_warm ≈ 0.34, p_hot ≈ 0.01
heat=50 → p_cold ≈ 0.06, p_warm ≈ 0.88, p_hot ≈ 0.06 (warm 主导)
heat=80 → p_cold ≈ 0.00, p_warm ≈ 0.34, p_hot ≈ 0.66
heat=100→ p_cold ≈ 0.00, p_warm ≈ 0.05, p_hot ≈ 0.95
sigma = 18.0:
warm 概率在 heat=32~68 之间 > 0.5
自然对应 v1 的 30~70 区间,但过渡平滑
5.3 平滑策略参数调整
# ── 各 Regime 的策略参数 ──
_REGIME_PARAMS = {
"cold": {
"zscore_entry_multiplier": 1.3, # 入场阈值 ×1.3
"cointegration_min_passed": 4, # 协整要求提高
"position_size_multiplier": 0.7, # 仓位缩小 30%
"max_concurrent_positions": 3, # 最大持仓数
},
"warm": {
"zscore_entry_multiplier": 1.0, # 正常参数
"cointegration_min_passed": 3,
"position_size_multiplier": 1.0,
"max_concurrent_positions": 3,
},
"hot": {
"zscore_entry_multiplier": 1.5, # 入场阈值 ×1.5
"cointegration_min_passed": 5, # 协整要求大幅提高
"position_size_multiplier": 0.5, # 仓位减半
"max_concurrent_positions": 2, # 最大持仓数减少
},
}
def get_blended_adjustments(regime_probs: dict[str, float]) -> dict:
"""根据 regime 概率分布计算混合策略参数
Args:
regime_probs: {"cold": p, "warm": p, "hot": p}
Returns:
混合后的策略参数字典
Example:
regime_probs = {"cold": 0.0, "warm": 0.34, "hot": 0.66}
→ zscore_entry_multiplier = 0.34 × 1.0 + 0.66 × 1.5 = 1.33
→ position_size_multiplier = 0.34 × 1.0 + 0.66 × 0.5 = 0.67
"""
result = {}
for param_key in _REGIME_PARAMS["warm"]:
blended = sum(
regime_probs[regime] * _REGIME_PARAMS[regime][param_key]
for regime in ("cold", "warm", "hot")
)
result[param_key] = blended
# 主导 regime(用于日志和告警)
dominant = max(regime_probs, key=regime_probs.get)
result["dominant_regime"] = dominant
result["dominant_prob"] = regime_probs[dominant]
return result
关键优势:
v1 (硬分类):
heat=69 → WARM → multiplier=1.0
heat=71 → HOT → multiplier=1.5 (跳变!)
v2 (模糊):
heat=69 → p_warm=0.42, p_hot=0.56 → multiplier = 0.42×1.0 + 0.56×1.5 = 1.26
heat=71 → p_warm=0.38, p_hot=0.60 → multiplier = 0.38×1.0 + 0.60×1.5 = 1.28
→ 平滑过渡,无跳变
5.4 防抖机制
模糊分类已内在平滑,但仍需防止高频微小振荡导致参数频繁变化:
class TemperatureOutputSmoother:
"""温度输出平滑器
对模糊分类结果做 EMA 平滑,防止因瞬时温度微小波动
导致策略参数频繁变化。
"""
def __init__(self, alpha: float = 0.1):
"""
Args:
alpha: EMA 衰减因子 (0.1 = 约 10 根 bar 的平滑窗口)
"""
self.alpha = alpha
self._smoothed: dict[str, float] | None = None
def smooth(self, regime_probs: dict[str, float]) -> dict[str, float]:
if self._smoothed is None:
self._smoothed = regime_probs.copy()
else:
for key in regime_probs:
self._smoothed[key] = (
self.alpha * regime_probs[key]
+ (1.0 - self.alpha) * self._smoothed[key]
)
# 重新归一化(EMA 可能导致微小数值偏差)
total = sum(self._smoothed.values())
return {k: v / total for k, v in self._smoothed.items()}
5.5 日志中的 Regime 标签
虽然策略参数用模糊值,但日志和告警仍使用主导 regime 标签:
dominant = max(regime_probs, key=regime_probs.get)
# 日志: regime=HOT(0.66) warm=0.34 cold=0.00
Regime 切换告警的触发条件改为:主导 regime 发生变化。
六、集成设计
6.1 新增文件
src/trading/market_temperature.py # ~250 行,核心实现
├─ RollingPercentile # 滚动百分位计算器
├─ AdaptiveWeights # Inverse-Vol 自适应权重
├─ LeakyHeatAccumulator # Leaky Integrator 热力累积
├─ FuzzyRegimeClassifier # 模糊 Regime 分类
├─ TemperatureOutputSmoother # 输出平滑
└─ MarketTemperature # 顶层聚合类
6.2 修改文件
src/trading/config.py # ~20 行,新增配置参数
src/trading/strategy.py # ~40 行,集成温度判断
src/services/realtime_kline_service_base.py # ~25 行,驱动温度更新
6.3 配置参数
# trading/config.py → StrategyParams 新增
# --- 市场水温 ---
market_temperature_enabled: bool = True
market_temperature_base_temp: float = 50.0 # 基准温度
market_temperature_decay: float = 0.995 # 衰减系数(半衰期≈11.5h)
market_temperature_gain: float = 0.15 # Leaky Integrator 注入增益
market_temperature_percentile_window: int = 2016 # 百分位窗口(7天×288根/天)
market_temperature_vol_window: int = 288 # 因子权重波动率窗口(1天)
market_temperature_regime_tau: float = 15.0 # Regime softmax 温度参数
market_temperature_regime_sigma: float = 18.0 # Regime warm 区间宽度
market_temperature_smooth_alpha: float = 0.1 # 输出 EMA 平滑系数
设计要点:
- 不再需要
cold_threshold、hot_threshold、hysteresis— 模糊分类自动处理 - 不再需要
max_heat— Leaky Integrator 有自然上界 - 不再需要
weightsdict — Inverse-Vol 自适应计算 - 新增
gain、regime_tau、regime_sigma、smooth_alpha
6.4 数据流集成
realtime_kline_service_base.py
│
├─ _on_candle_update(symbol="BTC/USDC:USDC", timeframe="5m")
│ │
│ ├─ [已有] momentum_filter._update_rs_vol(btc_candle)
│ ├─ [已有] momentum_filter._bocpd_trend_probability(btc_data)
│ ├─ [已有] momentum_filter._huber_cusum_check(btc_data)
│ ├─ [已有] momentum_filter._er_check(btc_data)
│ │
│ └─ [新增] market_temperature.update(
│ rs_vol=..., bocpd_prob=...,
│ cusum_max=..., er=..., vol_ratio=...,
│ realized_corr=...,
│ )
│ → 内部自动:归一化 → 自适应加权 → Leaky Integrate → 模糊分类 → 平滑
│
├─ _process_analysis_result(symbol="ALT/USDC:USDC")
│ └─ strategy.should_enter()
│ ├─ [新增] market_temperature.get_adjustments() → 混合参数
│ ├─ [已有] beta_regime.check() → 配对级 β 拦截
│ └─ [已有] momentum_filter.check() → 配对级过滤
6.5 strategy.py 集成点
# strategy.py :: AdaptiveBollingerStrategy._check_entry()
def _check_entry(self, key, z4h, adaptive_z, timestamp, current_above, params, latest_price=None):
# === 第一关:市场水温(市场级) ===
if self._market_temp and self._market_temp.enabled:
adjustments = self._market_temp.get_adjustments()
# 平滑混合后的阈值倍数
effective_zscore_threshold = (
params.adaptive_threshold * adjustments["zscore_entry_multiplier"]
)
# 协整通过数要求(取整)
min_passed = round(adjustments["cointegration_min_passed"])
# 仓位大小倍数(传递给 executor)
position_multiplier = adjustments["position_size_multiplier"]
# 日志
dominant = adjustments["dominant_regime"]
dominant_p = adjustments["dominant_prob"]
logger.info(
f"🌡️ 水温调整 | {pair_label} | "
f"regime={dominant}({dominant_p:.0%}) | "
f"阈值×{adjustments['zscore_entry_multiplier']:.2f} "
f"仓位×{adjustments['position_size_multiplier']:.2f}"
)
else:
effective_zscore_threshold = params.adaptive_threshold
position_multiplier = 1.0
# === 第二关:Beta 体制过滤(配对级,已有) ===
if params.beta_regime_enabled:
beta_state = self._beta_regime.check(key, ...)
if beta_state.hard_block:
return None
effective_zscore_threshold *= beta_state.threshold_scale
# === 后续:原有逻辑不变 ===
...
七、监控与告警
7.1 飞书推送
主导 Regime 切换时推送告警:
🌡️ 市场水温变化
Regime 主导: WARM → HOT (p=0.72)
概率分布: cold=0.02 warm=0.26 hot=0.72
瞬时温度: 82.3
累积热量百分位: 78.1
因子得分 (百分位):
RS波动率: 91.2 (w=0.18)
BOCPD趋势: 78.5 (w=0.15)
CUSUM急动: 85.3 (w=0.14)
ER趋势: 67.2 (w=0.17)
量能放大: 72.8 (w=0.16)
Realized Corr: 88.4 (w=0.20) ← 相关性飙升
策略调整 (混合):
入场阈值: ×1.38
仓位: ×0.63
协整通过: 5 (取整)
7.2 日志记录
每根 5m bar 记录温度快照(DEBUG 级别):
[MarketTemp] instant=72.3 heat=847.5 heat_pct=78.1 regime=HOT(0.72) | w=[0.18,0.15,0.14,0.17,0.16,0.20]
主导 Regime 切换记录(INFO 级别):
[MarketTemp] REGIME SHIFT: WARM → HOT (p=0.72, duration_in_warm=4.2h) | heat_pct=78.1
7.3 数据库存储(可选)
可将温度数据写入 analysis_results 表的扩展字段,用于回测分析:
ALTER TABLE analysis_results ADD COLUMN IF NOT EXISTS market_temp DOUBLE PRECISION;
ALTER TABLE analysis_results ADD COLUMN IF NOT EXISTS market_regime VARCHAR(10);
ALTER TABLE analysis_results ADD COLUMN IF NOT EXISTS market_regime_prob DOUBLE PRECISION;
八、回测验证方案
8.1 验证目标
- Regime 识别是否与主观判断一致:对照 BTC 历史行情,验证 HOT/COLD 主导概率划分是否合理
- 对策略收益的影响:在 HOT regime 收紧参数 vs 不收紧,对比胜率和盈亏比
- 参数敏感性:衰减系数、gain、tau、sigma 的变化对结果的影响
- v2 vs v1 对比:统一归一化 + Leaky Integrator 的改进是否体现在回测结果中
8.2 验证方法
# 使用历史 klines 数据回放
for candle in historical_btc_5m_candles:
temp.update(candle)
probs = temp.regime_probs
adjustments = temp.get_adjustments()
# 对比:
# A 组:不使用水温过滤的原始策略
# B 组:v1 水温过滤(硬分类)
# C 组:v2 水温过滤(模糊分类 + Leaky Integrator)
# 统计:胜率、盈亏比、最大回撤、夏普比率
8.3 关键检验指标
| 指标 | 期望效果 |
|---|---|
| HOT 主导期 C 组回撤 | 显著低于 A 组,与 B 组持平或更优 |
| WARM 主导期 ABC 三组表现 | 基本一致(水温不干预正常交易) |
| COLD 主导期 C 组假信号 | 少于 A 组(收紧入场过滤了弱信号) |
| 整体夏普比率 | C ≥ B ≥ A |
| Regime 切换时参数跳变 | C 组无跳变(模糊平滑),B 组有跳变 |
| 相关性飙升期拦截率 | C 组 > B 组(Realized Corr 因子贡献) |
8.4 因子贡献度分析
回测中记录各因子权重随时间的变化,验证 Inverse-Vol 自适应权重是否合理:
# 预期行为:
# - 在趋势行情中:ER 波动小 → ER 权重上升(信号稳定)
# - 在震荡行情中:ER 波动大 → ER 权重下降(信号不稳定)
# - CUSUM 和 RS 高相关时:两者同波 → 各自权重被压制 → 避免双重计算
九、实现优先级
| 优先级 | 组件 | 预估代码量 |
|---|---|---|
| P0 | MarketTemperature 核心类(RollingPercentile + AdaptiveWeights + LeakyHeatAccumulator + FuzzyRegimeClassifier + Smoother) |
~200 行 |
| P0 | 配置参数 (StrategyParams 新增字段) |
~20 行 |
| P0 | strategy.py 集成 | ~40 行 |
| P0 | service_base.py 驱动 | ~25 行 |
| P1 | Realized Correlation 计算 | ~40 行 |
| P1 | 飞书告警推送 | ~35 行 |
| P1 | 日志输出 | ~15 行 |
| P2 | 数据库存储(回测用) | ~20 行 |
| P2 | 回测验证脚本 | ~150 行 |
总计核心实现:~285 行新代码 + ~65 行修改
十、风险与局限
10.1 已知局限
| 局限 | 影响 | 缓解措施 |
|---|---|---|
| 百分位窗口冷启动 | 启动前 7 天百分位不准 | 使用历史数据预填充 |
| Inverse-Vol 权重冷启动 | 前 30 根 bar 使用等权 | 30 根 = 2.5 小时,影响极小 |
| Realized Corr 需要多币种 | 活跃 ALT < 3 时退化 | 数据不足时返回中性值 0.5 |
| 仅基于 BTC + 跨币种相关性 | 部分 ALT 有独立行情 | 可扩展为 BTC + ETH 双锚定 |
| 无 Funding Rate 数据 | 温度计缺少杠杆/情绪维度 | 后续补充为第 7 个因子 |
10.2 后续演进方向
| 优先级 | 方向 | 方法 | 预期效果 |
|---|---|---|---|
| P1 | 资金费率因子 | 接入 Hyperliquid funding rate API,作为第 7 个温度因子 | 补充市场杠杆和情绪维度 |
| P1 | OI 变化率因子 | 接入 Hyperliquid open interest API,作为第 8 个温度因子 | 补充资金流入/流出速度 |
| P2 | Exponential Gradient 调权 | 替代 Inverse-Vol,用在线凸优化动态学习最优权重 (Cesa-Bianchi & Lugosi, 2006) | 权重不仅均衡,还能追踪最优 |
| P2 | 多锚定资产 | BTC + ETH 双温度计,取加权平均 | 覆盖独立于 BTC 的 ETH 生态行情 |
| P2 | 配对级温度修正 | 特定 ALT 对 BTC 温度的敏感度不同(β 调节) | 个性化温度调整 |
| P3 | Hawkes Process | 用自激点过程替代 Leaky Integrator,建模"高温事件提升未来高温概率" | 学术最优的热力累积模型 |
| P3 | Online HMM | 用 Particle Filter HMM 替代整体三层架构 | 统一概率框架,自动学习状态数和转移概率 |
| P3 | 回测自动调参 | 基于历史数据的贝叶斯优化(而非网格搜索) | 高效参数空间探索 |
附录 A:学术参考
| 方法 | 参考文献 | 本方案的应用 |
|---|---|---|
| Rogers-Satchell Volatility | Rogers & Satchell (1991) | 因子 1:OHLC 波动率估计 |
| BOCPD | Adams & MacKay (2007) "Bayesian Online Changepoint Detection" | 因子 2:在线变点检测 |
| Huber-CUSUM | Page (1954) + Huber (1964) | 因子 3:鲁棒急动检测 |
| Kaufman Efficiency Ratio | Kaufman (1995) "Smarter Trading" | 因子 4:趋势纯度 |
| Absorption Ratio | Kritzman et al. (2010) "Principal Components as a Measure of Systemic Risk" | 因子 6 的理论基础 |
| Inverse-Volatility Weighting | Risk Parity 文献 (Qian, 2005) | 自适应因子权重 |
| Leaky Integrator | 信号处理标准模型 | 热力累积器 |
| Softmax Fuzzy Classification | 模糊逻辑 + 统计学习 | 模糊 Regime 输出 |
| Exponential Gradient | Cesa-Bianchi & Lugosi (2006) "Prediction, Learning, and Games" | P2 升级路径 |
| Hawkes Process | Bacry et al. (2015) "Hawkes processes in finance" | P3 升级路径 |
| Online HMM | Cappé (2011) "Online EM Algorithm for HMMs" | P3 升级路径 |
附录 B:参数速查表
| 参数 | 默认值 | 含义 | 调优方向 |
|---|---|---|---|
base_temp |
50.0 | 注入阈值(百分位中位数) | 一般不需调整 |
decay_factor |
0.995 | 衰减系数(半衰期≈11.5h) | ↓ 更快冷却 ↑ 更慢冷却 |
gain |
0.15 | 注入增益 | ↑ 升温更快 ↓ 升温更慢 |
percentile_window |
2016 | 百分位窗口(7天) | ↓ 更敏感 ↑ 更稳定 |
vol_window |
288 | 权重波动率窗口(1天) | ↓ 权重更敏感 ↑ 权重更稳定 |
regime_tau |
15.0 | Softmax 温度(cold/hot 灵敏度) | ↓ 更尖锐 ↑ 更平滑 |
regime_sigma |
18.0 | Warm 区间宽度 | ↓ warm 更窄 ↑ warm 更宽 |
smooth_alpha |
0.1 | 输出 EMA 平滑 | ↑ 更快响应 ↓ 更平滑 |
均衡分析快查:
h_eq = gain × (T - base_temp) / (1 - decay_factor)
gain=0.15, decay=0.995:
T=60 → h_eq=300, T=70 → h_eq=600, T=80 → h_eq=900
T=90 → h_eq=1200, T=100 → h_eq=1500
gain=0.20, decay=0.995 (更快升温):
T=60 → h_eq=400, T=70 → h_eq=800, T=80 → h_eq=1200
gain=0.15, decay=0.990 (更快冷却, 半衰期≈5.8h):
T=60 → h_eq=150, T=70 → h_eq=300, T=80 → h_eq=450