Python中的虚假回归(伪回归)(基础篇)
📘

Python中的虚假回归(伪回归)(基础篇)

第 1/3 篇

Python里那层看似相关的假象

在 Python 里做时间序列配对或跨品种相关性分析时,伪回归(spurious regression)是最容易踩的坑。两个各自带随机游走特征的序列,哪怕生成过程完全独立,直接跑 OLS 也常给出极高的 R² 和显著的 t 值,看起来像强相关,实则只是趋势巧合。 以外汇与贵金属为例:EURUSD 与 XAUUSD 的日线收盘价都带明显单位根,若不加处理直接回归,2020—2023 年样本上 R² 可能冲到 0.6 以上,但这种“关系”在样本外基本失效,实盘跟单会连续吃止损。外汇与贵金属本身杠杆高、跳空频繁,伪回归信号放大的是破产概率而非胜率。 验证方法很直接:对两序列先做 ADF 检验,若存在单位根则做一阶差分后再回归,或者改跑协整检验(Engle-Granger / Johansen)。MT5 的 Python API 能直接拉历史柱,用 statsmodels 三行就能复现下面的判定逻辑。

「先确认关系再谈模型」

做机器学习交易之前,得先确认输入特征和你要预测的变量之间真有统计关系,而不是自己脑补出来的相关性。否则模型在样本内误差再低,也只是拟合了噪声。 用没真实关系的数据也能训出误差指标漂亮的模型,这种虚假控制感最危险——业内叫“虚假回归”(spurious regression)。它常在非平稳时间序列直接回归时冒头,回测曲线好看,实盘大概率崩。 本篇先建立对虚假回归的直觉,再用合成时间序列模拟它、看特征效应;随后讲识别方法,并拿一套 Python 训练的模型做验证。若验证通过,会导出 ONNX 在 MT5 里落地成策略。外汇与贵金属杠杆高,虚假回归模型实盘可能引发快速回撤,验证前勿直接上资金。

◍ 洗手率与死亡率:被邪灵遮蔽的真实因果

19世纪维也纳一家医院里,健康产妇分娩时因感染发热死亡的占比高达20%。医生们归咎于‘坏空气’里的邪灵,直到塞梅尔维斯发现停尸房做尸检的人不洗手就去接生。推行手部卫生后,该死亡率从20%掉到1%。但他想把结论往外推时,被主流医学界当成异类,46岁死在精神病院。 这故事放到量化里就是典型警示:你可以用完全不相关的数据凑出误差很小的模型,它偶然‘预测对’了,却证伪不了根本不存在的关系——这就是虚假回归。就像当年医生念叨‘今天邪灵多,明天死人多’,结果真死人了,但因果是错的。 做机器学习建模时,模型也可能因错误原因给出正确输出。如果你明确知道输入输出间有真实联系,那不必慌;可要是你从没检验、只是假设‘肯定有关系’,就得小心了。 最通用的破法是对残差做单位根检验:残差里能揪出单位根,回归就倾向是虚假的。不过现实有物质限制——单位根在,你没查出来(第一类错误);或者没单位根你却查出来了(第二类错误)。ADF和KPSS是常用测试,各有失效场景。后面我们会自己造两组独立时间序列,看用不相关数据训模型会跑出什么。

用随机游走拆穿虚假回归

虚假回归最常见的根源,是把两条互相独立且非平稳的序列拿去做拟合。非平稳指的是均值、方差、自相关随时间漂移;平稳则相反。两条独立随机游走偶尔会在局部走出相似趋势,模型却可能误读成因果关系。 下面这段 Python 代码(MT5 之外用 Jupyter 跑即可)造了两个毫无关系的非平稳序列:x 用均值0方差1的正态步长累加,y 用均值2方差4的正态步长累加,各 1000 点。两者生成逻辑完全独立。 [CODE] import pandas as pd import numpy as np import matplotlib.pyplot as plt import statsmodels.api as sm from statsmodels.tsa.stattools import adfuller , kpss from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error size = 1000 mu , sigma = 0 , 1 mu_y , sigma_y = 2 , 4 steps = np.random.normal(mu,sigma,size) steps_y = np.random.normal(mu_y,sigma_y,size) x_non_stationary = pd.DataFrame(100 + np.cumsum(steps),index= np.arange(0,1000)) x_non_stationary_lagged = x_non_stationary.shift(1) x_non_stationary_lagged.dropna(axis=0,inplace=True) y_non_stationary = pd.DataFrame(100 + np.cumsum(steps_y),index= np.arange(0,1000)) plt.plot(x_non_stationary) plt.plot(y_non_stationary) ols = sm.OLS(y_non_stationary,x_non_stationary) lm = ols.fit() print(lm.summary()) [/CODE] 代码逐行拆解:

  • size = 1000 设定序列长度;
  • mu, sigma = 0, 1mu_y, sigma_y = 2, 4 分别定义两组正态步长的均值和标准差;
  • steps / steps_ynp.random.normal 抽正态随机数,是随机游走的“增量”;
  • x_non_stationary = 100 + np.cumsum(steps) 对步长累加,得到依赖前值的非平稳序列,起点100;
  • x_non_stationary_lagged 做了一阶滞后并去空,本段后续未用于 OLS;
  • y_non_stationary 同理用另一组参数累加;
  • plt.plot 两条线画出随机游走;
  • sm.OLS(y, x).fit() 直接拿独立 y 对独立 x 回归,打印摘要。

跑完摘要里调整后的 R 方接近 1,模型声称 y 约 90% 变异可由 x 解释,P 值极小且置信区间不含 0。但我们明知两者无关——这是典型的虚假回归:独立非平稳序列的随机趋势短暂同向,就足以把 R 方顶到虚高。外汇与贵金属价格多为非平稳序列,直接拿两个品种收盘价回归大概率踩这个坑,属高风险误用。 别把正态当圣经 正态步长只是方便造数,真实 tick 序列有厚尾和波动聚集;用 numpy 模拟完,建议换 EURUSD 与 XAUUSD 的 M5 收盘亲自跑一遍 OLS,看 R 方是否也莫名偏高。

MQL5 / C++
class="kw">import pandas as pd
class="kw">import numpy as np
class="kw">import matplotlib.pyplot as plt
class="kw">import statsmodels.api as sm
from statsmodels.tsa.stattools class="kw">import adfuller , kpss
from sklearn.linear_model class="kw">import LinearRegression
from sklearn.metrics class="kw">import mean_squared_error
size = class="num">1000
mu , sigma = class="num">0 , class="num">1
mu_y , sigma_y = class="num">2 , class="num">4
steps = np.random.normal(mu,sigma,size)
steps_y = np.random.normal(mu_y,sigma_y,size)
x_non_stationary = pd.DataFrame(class="num">100 + np.cumsum(steps),index= np.arange(class="num">0,class="num">1000))
x_non_stationary_lagged = x_non_stationary.shift(class="num">1)
x_non_stationary_lagged.dropna(axis=class="num">0,inplace=True)
y_non_stationary = pd.DataFrame(class="num">100 + np.cumsum(steps_y),index= np.arange(class="num">0,class="num">1000))
plt.plot(x_non_stationary)
plt.plot(y_non_stationary)
ols = sm.OLS(y_non_stationary,x_non_stationary)
lm = ols.fit()
print(lm.summary())

「滞后变量暴露的伪回归信号」

把一个预测变量的滞后项塞进回归后,原本显著的系数常常瞬间塌成不显著,这种断崖式变化本身就是伪回归的明显痕迹。读者在 MT5 用 Python 工具链复现时应盯住这一点:系数失稳往往比漂亮 R 平方更值得警惕。 R 平方越过 Durbin-Watson 值,是另一层谨慎信号。Durbin-Watson 专用于捕捉残差自相关——时间序列里观测值依赖前值,残差若互相关联,模型结论就可能建立在虚假联系上。DW 低于约 1 或高于约 3 都该引起怀疑。 下面这段代码把当期与滞后预测变量一起跑 OLS,输出摘要。注意 y 取前 998 行、x 取同区间两列,样本截断一致才能比对滞后前后的系数变化。外汇与贵金属为高杠杆高风险品种,此类统计结论只提示概率倾向,不构成方向保证。

MQL5 / C++
ols = sm.OLS(y_non_stationary.iloc[class="num">0:class="num">998,class="num">0],x_matrix.loc[class="num">0:class="num">998,[&class="macro">#x27;current_x&class="macro">#x27;,&class="macro">#x27;lagged_x&class="macro">#x27;]])
lm = ols.fit()
print(lm.summary())

◍ 用 Durbin-Watson 看残差自相关

Durbin-Watson 统计量取值被锁死在 0 到 4 的区间内,它本身不预测价格,只诊断你那套回归残差干不干净。 接近 2 说明残差基本无自相关,模型没在吃自己尾巴;明显低于 2 是残差正自相关,高于 2 则是负自相关。MT5 里跑完线性回归,顺手核对这个值比盯 R² 更管用。 高 R² 配低 DW 容易让人怀疑碰上虚假回归,但这俩指标 alone 定不了罪。外汇与贵金属属高风险品种,时间序列上要坐实虚假回归,还得靠额外诊断检验和品种门道,别拿两个数字就下单。

常见问题

高相关系数可能来自两个序列共同的随机游走趋势,属于虚假回归。先对序列做平稳性检验(如ADF)再谈模型。
那是把无因果的时间趋势当关系。用协整检验确认长期均衡,而非直接拟合,才能拆穿被趋势遮蔽的假象。
可以。小布能直接对品种做平稳性和残差自相关诊断,标出伪回归信号,省去你手跑Python脚本。
滞后项高自相关常暴露伪回归。看Durbin-Watson值,接近2才较安全,远低于2说明残差有自相关。
DW值接近0表示强正自相关,接近2为无自相关,低于1.5通常要警惕伪回归,别只看R平方。