🚀 AI 一键生成 joinquant 策略代码
立即体验
👉 AI 生成代码 👈

为什么 RSRS 策略中 sm.WLS 加权最小二乘法运行结果不一致?

joinquant | 发布时间: 2026/6/2
以下内容由 EasyQuant 生成。

问题描述

RSRS使用的加权最小二乘法是不是出了问题?

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,这在某些底层实现中也会引发异常或不稳定。


解决方案与代码优化

statsmodelsWLS 模型中,权重参数 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

优化点解析:

  1. 移除 volumes / volumes.sum():直接使用 volumes,保持数值在正常范围内,彻底解决底层矩阵运算的浮点数精度漂移问题,保证每次运行结果绝对一致。
  2. 增加 1e-8 平滑项:防止一字跌停/涨停或停牌导致的单日成交量为 0,从而引发 WLS 内部除零警告。
  3. 增加 try-except 保护:在量化回测中,极端数据(如 lows 全部相等导致共线性)可能会让 fit() 抛出异常,捕获异常可以防止整个回测中断。