协整检验存在的过拟合的问题(Cursor)- 基于波动率的自适应窗口优化方案
基于波动率的自适应窗口优化方案
核心设计原则
波动率-窗口反向关系:
- 高波动期:使用短窗口(快速响应市场变化)
- 低波动期:使用长窗口(平滑噪声,提高稳定性)
每币种独立优化:
- 每个交易对维护独立的窗口参数
- 基于自身历史波动率特征计算
- 使用LRU缓存提升性能
实现步骤
1. 添加波动率计算模块
在 multi_coins3.py 中添加新的静态方法:
@staticmethod
def _calculate_volatility_metric(prices: pd.Series, lookback: int = 20) -> float:
"""
计算价格序列的波动率指标(使用对数收益率标准差)
Args:
prices: 价格序列
lookback: 回溯窗口(默认20期)
Returns:
年化波动率(百分比)
"""
技术细节:
- 使用对数收益率:
log_returns = np.log(prices / prices.shift(1)) - 滚动标准差:
rolling_std = log_returns.rolling(window=lookback).std() - 年化处理:根据timeframe转换为年化波动率(5m→105120, 1h→8760, 4h→2190)
2. 实现自适应窗口计算器
添加核心方法:
def _calculate_adaptive_windows(
self,
base_prices: pd.Series,
alt_prices: pd.Series,
timeframe: str,
coin: str = None
) -> tuple[int, int]:
"""
基于波动率动态计算最优窗口长度
Returns:
(adaptive_beta_window, adaptive_zscore_window)
"""
自适应规则:
# 1. 计算双币种波动率
base_vol = _calculate_volatility_metric(base_prices)
alt_vol = _calculate_volatility_metric(alt_prices)
combined_vol = (base_vol + alt_vol) / 2 # 加权平均
# 2. 波动率分位数映射(基于历史分布)
vol_percentile = 根据历史波动率计算当前分位数
# 3. 窗口长度映射函数
if vol_percentile >= 80: # 极高波动
beta_window = 60
zscore_window = 20
elif vol_percentile >= 60: # 高波动
beta_window = 80
zscore_window = 25
elif vol_percentile >= 40: # 中等波动
beta_window = 100 # 默认值
zscore_window = 30
elif vol_percentile >= 20: # 低波动
beta_window = 120
zscore_window = 35
else: # 极低波动
beta_window = 150
zscore_window = 40
# 4. 窗口合理性检查
beta_window = max(50, min(200, beta_window)) # 限制范围
zscore_window = max(15, min(50, zscore_window))
zscore_window = min(zscore_window, beta_window // 2) # 保证 ZSCORE < BETA/2
3. 添加币种级缓存机制
在 __init__ 方法中添加:
# 币种窗口参数缓存
self.adaptive_window_cache = {} # {(coin, timeframe, period): (beta_w, zscore_w, timestamp)}
self.window_cache_ttl = 3600 # 缓存有效期1小时(避免过度缓存过时参数)
实现缓存管理方法:
def _get_adaptive_windows_cached(
self,
coin: str,
base_prices: pd.Series,
alt_prices: pd.Series,
timeframe: str,
period: str
) -> tuple[int, int]:
"""
获取自适应窗口(带缓存和TTL)
"""
cache_key = (coin, timeframe, period)
current_time = time.time()
# 检查缓存
if cache_key in self.adaptive_window_cache:
beta_w, zscore_w, timestamp = self.adaptive_window_cache[cache_key]
if current_time - timestamp < self.window_cache_ttl:
logger.debug(f"自适应窗口缓存命中 | 币种: {coin} | Beta={beta_w}, Zscore={zscore_w}")
return beta_w, zscore_w
# 计算新窗口参数
beta_w, zscore_w = self._calculate_adaptive_windows(
base_prices, alt_prices, timeframe, coin
)
# 更新缓存
self.adaptive_window_cache[cache_key] = (beta_w, zscore_w, current_time)
return beta_w, zscore_w
4. 修改现有方法调用链
修改点A:zscore_analysis 方法(1248行附近)
# 修改前
cointegration_status_total_period, cointegration_status_short_period, cointegration_result = self.multiple_cointegration_analysis(
price_data['base_prices'],
price_data['alt_prices'],
coin=coin,
stats_period_key=stats_period_key,
beta_window=self.BETA_WINDOW, # 固定值
zscore_window=self.ZSCORE_WINDOW # 固定值
)
# 修改后
# 获取自适应窗口参数
timeframe, period = stats_period_key
adaptive_beta_w, adaptive_zscore_w = self._get_adaptive_windows_cached(
coin=coin,
base_prices=price_data['base_prices'],
alt_prices=price_data['alt_prices'],
timeframe=timeframe,
period=period
)
logger.info(f"自适应窗口 | 币种: {coin} | 周期: {stats_period_key} | Beta窗口: {adaptive_beta_w} | Zscore窗口: {adaptive_zscore_w}")
cointegration_status_total_period, cointegration_status_short_period, cointegration_result = self.multiple_cointegration_analysis(
price_data['base_prices'],
price_data['alt_prices'],
coin=coin,
stats_period_key=stats_period_key,
beta_window=adaptive_beta_w, # 自适应值
zscore_window=adaptive_zscore_w # 自适应值
)
修改点B:_calculate_zscore 方法(1278行附近)
保持方法签名不变,已经支持动态传入 beta_window 和 window 参数
5. 添加配置开关和回退机制
在类常量区添加:
# ========== 自适应窗口配置 ==========
ENABLE_ADAPTIVE_WINDOWS = True # 是否启用自适应窗口
VOLATILITY_LOOKBACK = 20 # 波动率计算回溯期
ADAPTIVE_WINDOW_CACHE_TTL = 3600 # 缓存有效期(秒)
# 窗口长度约束(防止极端值)
MIN_BETA_WINDOW = 50
MAX_BETA_WINDOW = 200
MIN_ZSCORE_WINDOW = 15
MAX_ZSCORE_WINDOW = 50
# 回退机制:当数据不足或计算失败时使用固定值
FALLBACK_BETA_WINDOW = 100
FALLBACK_ZSCORE_WINDOW = 30
在 _get_adaptive_windows_cached 中添加回退逻辑:
try:
if not self.ENABLE_ADAPTIVE_WINDOWS:
logger.debug("自适应窗口已禁用,使用固定值")
return self.FALLBACK_BETA_WINDOW, self.FALLBACK_ZSCORE_WINDOW
# ... 正常计算逻辑 ...
except Exception as e:
logger.warning(f"自适应窗口计算失败,使用回退值 | 币种: {coin} | 错误: {e}")
return self.FALLBACK_BETA_WINDOW, self.FALLBACK_ZSCORE_WINDOW
6. 添加诊断日志和统计
在分析结束时输出窗口统计信息:
def _output_adaptive_window_statistics(self):
"""输出自适应窗口使用统计"""
if not self.adaptive_window_cache:
return
beta_windows = [v[0] for v in self.adaptive_window_cache.values()]
zscore_windows = [v[1] for v in self.adaptive_window_cache.values()]
logger.info(
f"自适应窗口统计 | "
f"Beta窗口范围: [{min(beta_windows)}, {max(beta_windows)}], 均值: {np.mean(beta_windows):.1f} | "
f"Zscore窗口范围: [{min(zscore_windows)}, {max(zscore_windows)}], 均值: {np.mean(zscore_windows):.1f}"
)
在 run() 方法的末尾调用:
# 在 "分析完成" 日志后添加
self._output_adaptive_window_statistics()
预期效果
性能影响
- 计算开销增加:约20-30%(符合预期)
- 主要来源:每个币种需额外计算波动率(O(n) 复杂度)
- 缓存机制减少重复计算
精度提升
- 高波动币种:响应速度提升,减少滞后
- 低波动币种:信号质量提升,减少假阳性
- 不同市场状态:自动适配,避免参数失效
示例场景
币种A(高波动山寨币,近期波动率90分位):
固定窗口 → Beta=100, Zscore=30
自适应窗口 → Beta=60, Zscore=20(更快响应)
币种B(低波动主流币,近期波动率10分位):
固定窗口 → Beta=100, Zscore=30
自适应窗口 → Beta=150, Zscore=40(更稳定)
测试验证
建议完成后进行以下测试:
- 单元测试:验证波动率计算和窗口映射逻辑
- 对比测试:选择5-10个币种,对比固定窗口vs自适应窗口的信号质量
- 极端场景测试:数据不足、极端波动率时的回退机制
文件修改清单
multi_coins3.py- 新增方法:
_calculate_volatility_metric(约30行) - 新增方法:
_calculate_adaptive_windows(约50行) - 新增方法:
_get_adaptive_windows_cached(约30行) - 新增方法:
_output_adaptive_window_statistics(约15行) - 修改
__init__:添加缓存字典(3行) - 修改
zscore_analysis:替换固定窗口为自适应窗口(15行) - 修改
run:添加统计输出(1行) - 添加类常量:自适应窗口配置(约12行)
- 新增方法:
总计:约156行新增代码,15行修改