问题描述
def calc_beta_r2(highs, lows, volumes):
# type: (np.ndarray, np.ndarray, np.ndarray) -> (float, float)
# 成交量加权的权重列表
weights = volumes / volumes.sum()
# 加权最小二乘法 #sm.WLS(y, xx, weights)
xx = sm.add_constant(lows) # 为模型增加常数项,即回归线在y轴上的截距(括号里面写上自变量)
model = sm.WLS(highs, xx, weights).fit() # 加权最小二乘法
beta = model.params[1] # model.params.low
r2 = model.rsquared
return beta, r2
语句model = sm.WLS(highs, xx, weights).fit()从昨天开始发现问题。
会在编译运行的时候出现不同结果
通常正确结果如下:
Img
但也会出现另一种情况,如下:
Img
代码完全一样,但编译后的结果不一样。
解决方案
RSRS 策略中 sm.WLS 结果不一致的原因分析
在 JoinQuant 平台上运行 RSRS(阻力支撑相对强度)策略时,如果发现使用 statsmodels.api.WLS(加权最小二乘法)在相同代码下编译运行出现不同结果,通常不是 WLS 算法本身出了问题,而是传入的数据或权重处理方式导致了底层数值计算的不稳定。
根据您提供的代码,主要问题出在权重的归一化处理上:
# 您的原始代码
weights = volumes / volumes.sum()
1. 浮点数精度与数值不稳定
当您使用 volumes / volumes.sum() 时,由于成交量(volumes)通常是一个非常大的数值(例如几百万、几千万),归一化后的 weights 会变成一组非常小的浮点数。在底层线性代数库(如 BLAS/LAPACK)进行矩阵求逆和乘法运算时,极小的权重会导致条件数过大(Ill-conditioned),从而引发严重的浮点数精度丢失。这种精度丢失在多线程并发计算时,可能会因为计算顺序的微小差异而导致最终的 Beta 和 R-squared 产生肉眼可见的随机波动。
2. 极端行情导致除零或 NaN
如果某段时间内股票停牌或极端缩量,volumes.sum() 可能为 0,导致 weights 变成 NaN。或者某些天的 volume 为 0,导致该点权重为 0,这在某些底层实现中也会引发异常或不稳定。
解决方案与代码优化
在 statsmodels 的 WLS 模型中,权重参数 weights 只需要反映相对大小,不需要归一化到和为 1。直接传入原始成交量即可避免极小浮点数带来的精度问题。同时,建议加上一个极小的常数以防止权重为 0。
优化后的代码:
import statsmodels.api as sm
import numpy as np
def calc_beta_r2(highs, lows, volumes):
# type: (np.ndarray, np.ndarray, np.ndarray) -> (float, float)
# 1. 数据清洗:剔除包含 NaN 的无效数据(可选,视上游数据质量而定)
valid_idx = ~np.isnan(highs) & ~np.isnan(lows) & ~np.isnan(volumes)
highs = highs[valid_idx]
lows = lows[valid_idx]
volumes = volumes[valid_idx]
if len(highs) < 2:
return 0.0, 0.0
# 2. 核心修改:取消归一化,直接使用成交量作为权重
# 加上 1e-8 防止某天成交量为 0 导致权重为 0 报错
weights = volumes + 1e-8
# 3. 加权最小二乘法回归
xx = sm.add_constant(lows) # 增加常数项
try:
model = sm.WLS(highs, xx, weights=weights).fit()
beta = model.params[1] # 斜率
r2 = model.rsquared # R方
except Exception as e:
# 捕获奇异矩阵等极端情况
beta = 0.0
r2 = 0.0
return beta, r2
优化点解析:
- 移除
volumes / volumes.sum():直接使用volumes,保持数值在正常范围内,彻底解决底层矩阵运算的浮点数精度漂移问题,保证每次运行结果绝对一致。 - 增加
1e-8平滑项:防止一字跌停/涨停或停牌导致的单日成交量为 0,从而引发 WLS 内部除零警告。 - 增加
try-except保护:在量化回测中,极端数据(如 lows 全部相等导致共线性)可能会让fit()抛出异常,捕获异常可以防止整个回测中断。