Python中的虚假回归(伪回归)·进阶篇
📉

Python中的虚假回归(伪回归)·进阶篇

(2/3)· 只用低误差指标判断模型有效?你可能正被伪回归骗得团团转

实战向 第 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 上下无趋势。

MQL5 / C++
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回测报告实在。

MQL5 / C++
对残差进行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 值,比盯一个统计量靠谱得多。

MQL5 / C++
对残差进行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 跑一遍再说话。

MQL5 / C++
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 里剔除。 下面这段代码保留了原写法,可直接对照改路径验证:

MQL5 / C++
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 值再决定要不要进参数层。

MQL5 / C++
pp = PhillipsPerron(residuals)
原假设:该过程包含一个单位根。
备择假设:该过程是弱平稳的。
adf = ADF(residuals)
零假设:该过程包含一个单位根。
备择假设:该过程是弱平稳的。
把残差诊断交给小布盯盘
这些单位根与KPSS诊断小布盯盘的AIGC已内置,打开对应品种页即可看到残差平稳性提示,你只需判断策略逻辑是否成立。

常见问题

虚假回归指模型用无真实关系的数据拟合出看似有效的输出。输入与预测变量本无关联,但偶然低误差让人误以为可控,样本外常失效。
ADF原假设含单位根,KPSS原假设平稳。两者矛盾常出现在趋势附近,需结合Phillip-Perron检测与经济意义综合判断,倾向认为序列性质模糊。
PP检验对异方差和自相关更鲁棒,不额外加滞后项,适合存在结构断点或波动聚集的金融时间序列,可能更稳。
目前小布盯盘对导入的Python模型残差做平稳性扫描,若ADF与KPSS同时异常会提示关系存疑,但决策仍由你做。
分别生成两个独立随机游走,再做线性回归,会发现t统计显著但关系为零,借此直观理解虚假回归特征效应。