Python中的虚假回归(伪回归)·进阶篇
(2/3)· 只用低误差指标判断模型有效?你可能正被伪回归骗得团团转
「残差不平稳才露馅」
识别虚假回归最硬的证据在残差里:若回归模型的残差本身非平稳,那这条回归关系大概率是虚的。但平稳性肉眼难辨,尤其当序列本就带单位根时,增广迪基-富勒(ADF)检验可能连原假设都推不翻——即便明知是虚假回归,ADF 也可能给不出「非平稳」的结论,这正是该检验的盲区。 所以单靠一个统计检验不够,得把 ADF 数值和品种逻辑放一起看。外汇与贵金属价差序列常有趋势项,直接拿线性回归硬套,残差带漂移的概率偏高,属典型高风险误用。 下面这段用 sklearn 在训练段拟合、再在测试段算残差的代码,可直接丢进 Python 验证残差形态。若残差图明显绕零轴发散,ADF 的 p 值又大于 0.05,就要警惕虚假回归了。 [CODE] lm = LinearRegression() lm.fit(x[train_start:train_end],y[train_start:train_end]) residuals = y[test_start:test_end] - lm.predict(x[test_start:test_end]) residuals.plot() [/CODE] 代码逐行拆:第 1 行建线性回归对象;第 2 行用训练区间的 x、y 拟合斜率与截距;第 3 行拿测试区间真实 y 减模型预测值得到残差;第 4 行把残差时序画出来,目测是否围绕 0 上下无趋势。
lm = LinearRegression() lm.fit(x[train_start:train_end],y[train_start:train_end]) residuals = y[test_start:test_end] - lm.predict(x[test_start:test_end]) residuals.plot()
用ADF揪出残差里的单位根
ADF检验干一件事:判断时间序列有没有单位根。有单位根就是非平稳,均值方差会随日子漂;没有才平稳,统计特性站得住。对EA回归出来的残差跑一遍ADF,是在确认这套拟合到底靠不靠谱——残差平稳,模型才谈得上可信。 零假设是「有单位根(非平稳)」,备择是「没有(平稳)」。决策只看检验统计量和临界值:统计量比临界值小,拒零假设,残差平稳;比临界值大,没理由拒,残差仍可能非平稳。 我们实测过一组模型残差,ADF输出元组里第二个值是 p = 8.4235e-24,几乎贴着0。配合统计量 -12.75 远小于1%临界值 -3.4436,拒零假设没悬念,残差平稳。外汇与贵金属价差序列波动大、高风险,这种平稳结论只针对该段样本,换周期要重跑。 ADF不是万能锤。样本太小效力会塌;滞后阶数乱设,过少或过多都抓不准底层结构;数据里藏着线性或二次趋势却没在模型里去趋势,检验会失灵;差分阶数不够同样拒不动零假设。开MT5把回归残差导出来,用Python statsmodels的adfuller跑一遍,先把滞后阶按AIC定,再对照临界值看p,比盲信EA回测报告实在。
对残差进行ADF
◍ KPSS 怎么判平稳:阈值与误拒陷阱
KPSS 和 ADF 是两套相反的平稳性口径:ADF 原假设是“有单位根”,KPSS 原假设却是“序列趋势平稳”。所以同一段行情,两个测试一起跑能互相印证,单看一个容易误判。 判定规则很直接:把 KPSS 统计量和临界值比。常用 5% 显著性水平,临界值约 0.463;统计量大于它才拒绝原假设(即非趋势平稳)。我们一段样本算出来统计量是 0.016,明显低于 0.463,按 KPSS 口径反而落进“趋势平稳”区——但这和 ADF 同跑时常见背离,说明数据里藏着两种检验都敏感的因子。 别把 KPSS 当圣经。它在四种情况下会假拒绝 H0(第一类错误):强季节性被误读成趋势;结构突变被当成非平稳漂移;异常值拉偏趋势线;非线性趋势超出它的线性假设。外汇和贵金属分钟级序列基本四条全中,单跑 KPSS 极易喊错平稳性,属高风险误用。 实操上,开 MT5 把一段 XAUUSD 的 H1 收盘价先差分再去跑 KPSS,对比 ADF 的 p 值,比盯一个统计量靠谱得多。
对残差进行KPSS检验
「从终端拽真实数据做回归验证」
把前面合成的练习数据扔掉,下一步该碰 MT5 终端里的真实报价了。写个 MQL5 脚本把指标和 OHLC 拉出来存成 CSV,是绕不开的体力劳动:不跑这套,后面用 Python 建的回归模型就是空中楼阁。 脚本先声明四个指标句柄——MA(20)、RSI(60)、CCI(10)、AO,再开四个 double 数组接缓冲区,最后写 3000 根 K 线到 Market Data.csv。下面这段代码就是核心骨架,直接丢进 MT5 脚本编辑器能编译。 拿到 CSV 后,用 Pandas 读进来,目标变量定为收盘价增量,训练集测试集切好,用 statsmodels 跑多元线性回归。R 方看着高,但杜宾-沃森统计量偏低,说明残差可能有自相关——这对外汇、贵金属这种高波动品种尤其要命,模型看着漂亮实际可能漏了序列里的惯性。 剔除不显著特征时别手软:开盘价系数置信区间含 0,RSI 和 CCI 系数都贴在 0 附近,直接砍掉。残差里要查单位根,存在单位根就等于分类器把狗标成猫,知识不稳定、波动会放大。多换几种检验看整体趋势,没有万能测试。 真实市场数据的高风险在于样本外的漂移,回归验证过了也只代表历史窗口内成立,上实盘前先用脚本导出你常用交易对的 CSV 跑一遍再说话。
class=class="str">"cmt">//---Our handlers for our indicators class="type">int ma_handle; class="type">int rsi_handle; class="type">int cci_handle; class="type">int ao_handle; class=class="str">"cmt">//---Data structures to store the readings from our indicators class="type">class="kw">double ma_reading[]; class="type">class="kw">double rsi_reading[]; class="type">class="kw">double cci_reading[]; class="type">class="kw">double ao_reading[]; class=class="str">"cmt">//---File name class="type">class="kw">string file_name = "Market Data.csv"; class=class="str">"cmt">//---Amount of data requested class="type">int size = class="num">3000; class=class="str">"cmt">//---Setup our technical indicators ma_handle = iMA(_Symbol,PERIOD_CURRENT,class="num">20,class="num">0,MODE_EMA,PRICE_CLOSE); rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,class="num">60,PRICE_CLOSE); cci_handle = iCCI(_Symbol,PERIOD_CURRENT,class="num">10,PRICE_CLOSE); ao_handle = iAO(_Symbol,PERIOD_CURRENT); class=class="str">"cmt">//---Set the values as series CopyBuffer(ma_handle,class="num">0,class="num">0,size,ma_reading); ArraySetAsSeries(ma_reading,true); CopyBuffer(rsi_handle,class="num">0,class="num">0,size,rsi_reading); ArraySetAsSeries(rsi_reading,true); CopyBuffer(cci_handle,class="num">0,class="num">0,size,cci_reading); ArraySetAsSeries(cci_reading,true); CopyBuffer(ao_handle,class="num">0,class="num">0,size,ao_reading); ArraySetAsSeries(ao_reading,true); class=class="str">"cmt">//---Write to file class="type">int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(class="type">int i=-class="num">1;i<=size;i++){ if(i == -class="num">1){ FileWrite(file_handle,"Open","High","Low","Close","MA class="num">20","RSI class="num">60","CCI class="num">10","AO"); } else{
把MT5导出的行情拼进Python回归
上面这段落是 MT5 端把每根 K 线的开高低收连同 MA、RSI、CCI、AO 读数写进文件的核心调用,循环里用 FileWrite 把 i 索引下的多个指标值一次性落盘,方便后续脱离终端做统计。 到了 Python 侧,先用 pandas 读入 CSV,并构造 Target:用当前 Close 除以 15 根之后的 Close,相当于在做 15 周期后的相对价位标签;dropna 后训练集取 0~19999 行、测试集取 20020~89983 行,跨度约 7 万行,足够跑出稳定系数。 用 statsmodels 的 OLS 对八个特征做普通最小二乘,print(lm.summary()) 会给出每个变量的 p 值;若某指标 p 值长期大于 0.05,在外汇与贵金属这种高波动市场里,它对该标签的解释力可能偏弱,应考虑从 predictors 里剔除。 下面这段代码保留了原写法,可直接对照改路径验证:
FileWrite(file_handle,iOpen(_Symbol,PERIOD_CURRENT,i), iHigh(_Symbol,PERIOD_CURRENT,i), iLow(_Symbol,PERIOD_CURRENT,i), iClose(_Symbol,PERIOD_CURRENT,i), ma_reading[i], rsi_reading[i], cci_reading[i], ao_reading[i]); } } class="kw">import pandas as pd class="kw">import numpy as np class="kw">import statsmodels.api as sm class="kw">import matplotlib.pyplot as plt from sklearn.linear_model class="kw">import LinearRegression from sklearn.metrics class="kw">import mean_squared_error from arch.unitroot class="kw">import PhillipsPerron , ADF , KPSS class="kw">import onnx from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import DoubleTensorType csv = pd.read_csv("/enter/your/path/here") csv["Target"] = csv["Close"] / csv["Close"].shift(-class="num">15) csv.dropna(axis=class="num">0,inplace=True) train = np.arange(class="num">0,class="num">20000) test = np.arange(class="num">20020,class="num">89984) ols = sm.OLS(csv.loc[:,"Target"],csv.loc,[["Open","High","Low","Close","MA class="num">20","RSI class="num">60","CCI class="num">10","AO"]]) lm = ols.fit() print(lm.summary()) predictors = ["High","Low","Close","MA class="num">20","AO"] residuals = csv.loc[test[class="num">0]:test[-class="num">1],"Target"] - lm.predict(csv.loc[test[class="num">0]:test[-class="num">1],predictors]) plt.plot(residuals)
◍ 用 PP 和 ADF 双重确认序列平稳性
单位根代表序列带随机趋势、非平稳;直接拿非平稳数据做回归容易出虚假关系。Phillips-Perron(PP)检验是 1988 年 Phillips 与 Perron 提出的 DF 变体,核心是对差分后序列以其滞后值为自变量回归,看系数是否显著异于 0,且它允许序列相关与异方差,比原始 DF 更贴实盘数据。 零假设都是「含单位根=非平稳」,备择为弱平稳;当检验统计量小于临界值或 p 值低于 0.05,才倾向拒绝原假设。下面这段 MQL5 侧调用的 Python 封装代码,本质是把残差送进两种检验。 [CODE] pp = PhillipsPerron(residuals) 原假设:该过程包含一个单位根。 备择假设:该过程是弱平稳的。 adf = ADF(residuals) 零假设:该过程包含一个单位根。 备择假设:该过程是弱平稳的。 [/CODE] 实跑中 PP 统计量落到 -73.916、p=0.000,ADF 统计量 -31.300、p=0.000;两个极大负值都指向强烈反对单位根,序列大概率平稳。外汇与贵金属价格序列波动大、高风险,单看一种检验不够稳,两种同指向才更敢用残差做后续均值回复类策略。 不过检验只证平稳、不证能赚钱;真要落地,把你的 EA 残差先导出,在 MT5 外跑一遍这两行,确认 p 值再决定要不要进参数层。
pp = PhillipsPerron(residuals) 原假设:该过程包含一个单位根。 备择假设:该过程是弱平稳的。 adf = ADF(residuals) 零假设:该过程包含一个单位根。 备择假设:该过程是弱平稳的。