数据科学与机器学习(第 01 部分):线性回归·综合运用
📉

数据科学与机器学习(第 01 部分):线性回归·综合运用

(3/3)· 散点里找直线看似简单,错把非线性当线性才是实盘建模的隐性坑

偏理论 第 3/3 篇
拿一堆指标列直接塞进线性公式,是 EA 建模里最常见也最隐蔽的错。关联弱的变量混进去,回测漂亮、实盘崩盘的概率很高。外汇贵金属杠杆高,这类根本性错误会放大成实亏。

「多变量回归在 MT5 类里的硬编码路子」

多元线性回归和单变量最大的区别,是模型里同时塞了多个自变量:Y = M1X1 + M2X2 + M3X3 + … + C。Y 轴截距只有一个,因为整条拟合直线只会穿过 y 轴一次,这点跟简单线性回归一致。 由于要处理的是数组集合,类的私有段先记一个 m_independent_vars 表示自变量个数,公开段则直接重载 MultipleRegressionMain——两个自变量和三个自变量的版本分开写,没有取巧的泛化办法。 推导后的常数公式是 C = Y - M1X1 - M2X2,代码里 y_interceptforMultiple 就是拿各序列均值按这个算的。下面这段是两个自变量版本的类的骨架与核心函数,逐行拆一下能帮助你在 MT5 里直接抄去验证。 class CMultipleLinearRegression: public CSimpleLinearRegression —— 继承简单回归类,复用斜率计算。 private: int m_independent_vars; —— 私有成员,记录自变量数量。 public: 构造析构与多个重载函数声明,包括双变量和三变量两套 MultipleRegressionMain 与 y_interceptforMultiple。 void MultipleRegressionMain(...) 双变量版:先算截距和两个斜率,Print 出形如 Y=0.12A+0.08B+1.35 的模型串,再按数组均长 resize 预测数组,循环填 predicted_y[i] = slope1*A[i]+slope2*B[i]+截距。 DoubleToString(slope1,2) 里的 2 是保留两位小数,跑出来你能在Experts日志里直接看到系数。 double y_interceptforMultiple(...) :return(mean(Y)-coefficient_of_X(A,Y)*mean(A)-coefficient_of_X(B,Y)*mean(B)); 就是 C = Y均值 - M1*A均值 - M2*B均值。 外汇和贵金属行情受新闻冲击大,多变量回归拟合出的系数只在样本区间内有解释力,拿去预测未来价格只是概率倾向,实盘前务必用历史数据回测截距稳定性。

MQL5 / C++
class CMultipleLinearRegression: class="kw">public CSimpleLinearRegression
  {
    class="kw">private:
                      class="type">int m_independent_vars;
    class="kw">public:
                      CMultipleLinearRegression(class="type">void);
                     ~CMultipleLinearRegression(class="type">void);
                      
                      class="type">class="kw">double coefficient_of_X(class="type">class="kw">double& x_arr[],class="type">class="kw">double& y_arr[]);
                      class="type">void  MultipleRegressionMain(class="type">class="kw">double& predicted_y[],class="type">class="kw">double& Y[],class="type">class="kw">double& A[],class="type">class="kw">double& B[]);
                      class="type">class="kw">double y_interceptforMultiple(class="type">class="kw">double& Y[],class="type">class="kw">double& A[],class="type">class="kw">double& B[]);
                      class="type">void  MultipleRegressionMain(class="type">class="kw">double& predicted_y[],class="type">class="kw">double& Y[],class="type">class="kw">double& A[],class="type">class="kw">double& B[],class="type">class="kw">double& C[],class="type">class="kw">double& D[]);
                      class="type">class="kw">double y_interceptforMultiple(class="type">class="kw">double& Y[],class="type">class="kw">double& A[],class="type">class="kw">double& B[],class="type">class="kw">double& C[],class="type">class="kw">double& D[]);
  };
class="type">void CMultipleLinearRegression::MultipleRegressionMain(class="type">class="kw">double &predicted_y[],class="type">class="kw">double &Y[],class="type">class="kw">double &A[],class="type">class="kw">double &B[])
{
class=class="str">"cmt">// Multiple regression formula =  y = M1X1+M2X2+M3X3+...+C
  class="type">class="kw">double constant_y_intercept=y_interceptforMultiple(Y,A,B);
  class="type">class="kw">double slope1 = coefficient_of_X(A,Y);
  class="type">class="kw">double slope2 = coefficient_of_X(B,Y);
  
   Print("Multiple Regression Model is ","Y="+DoubleToString(slope1,class="num">2)+"A+"+DoubleToString(slope2,class="num">2)+"B+"+
        DoubleToString(constant_y_intercept,class="num">2));
        
   class="type">int ArrSize = (ArraySize(A)+ArraySize(B))/class="num">2;
   ArrayResize(predicted_y,ArrSize);
   for (class="type">int i=class="num">0; i<ArrSize; i++)
      predicted_y[i] = slope1*A[i]+slope2*B[i]+constant_y_intercept;
      
}
class="type">class="kw">double CMultipleLinearRegression::y_interceptforMultiple(class="type">class="kw">double &Y[],class="type">class="kw">double &A[],class="type">class="kw">double &B[])
{
  class=class="str">"cmt">//formula c=Y-M1X1-M2X2;
   class="kw">return(mean(Y)-coefficient_of_X(A,Y)*mean(A)-coefficient_of_X(B,Y)*mean(B));
}
class="type">void CMultipleLinearRegression::MultipleRegressionMain(class="type">class="kw">double &predicted_y[],class="type">class="kw">double &Y[],class="type">class="kw">double &A[],class="type">class="kw">double &B[],class="type">class="kw">double &C[],class="type">class="kw">double &D[])
{

把四因子回归塞进预测数组

多元线性回归算完系数后,关键一步是把斜率和截距落进预测序列。下面这段代码先抓出 Y 对 A/B/C/D 各自的斜率,以及整体截距 constant_y_intercept,随后用 Print 把模型式子打到日志里,方便你直接在 MT5 终端核对数值。 ArrSize 取 A、B 数组长度的平均值,假定四组因子长度一致;ArrayResize 给 predicted_y 定容,循环里按 Y = slope1*A + slope2*B + slope3*C + slope4*D + intercept 逐点算预测值。外汇与贵金属市场的高风险意味着,这种拟合只描述历史样本关系,样本外预测可能明显偏离。 截距函数 y_interceptforMultiple 用均值差法:intercept = mean(Y) - Σ(slope_i * mean(因子_i))。复制进 EA 后,把 A/B/C/D 换成你自己的指标序列(比如波动率、动量、价差、成交量),跑一遍看 Print 出来的系数符号和大小是否符合盘感。

MQL5 / C++
  class="type">class="kw">double constant_y_intercept = y_interceptforMultiple(Y,A,B,C,D);
  class="type">class="kw">double slope1 = coefficient_of_X(A,Y);
  class="type">class="kw">double slope2 = coefficient_of_X(B,Y);
  class="type">class="kw">double slope3 = coefficient_of_X(C,Y);
  class="type">class="kw">double slope4 = coefficient_of_X(D,Y);
class=class="str">"cmt">//---
  Print("Multiple Regression Model is ","Y="+DoubleToString(slope1,class="num">2),"A+"+DoubleToString(slope2,class="num">2)+"B+"+
        DoubleToString(slope3,class="num">2)+"C"+DoubleToString(slope4,class="num">2)+"D"+DoubleToString(constant_y_intercept,class="num">2));
class=class="str">"cmt">//---
  class="type">int ArrSize = (ArraySize(A)+ArraySize(B))/class="num">2;
  ArrayResize(predicted_y,ArrSize);
  for (class="type">int i=class="num">0; i<ArrSize; i++)
      predicted_y[i] = slope1*A[i]+slope2*B[i]+slope3*C[i]+slope4*D[i]+constant_y_intercept;
}
class="type">class="kw">double CMultipleLinearRegression::y_interceptforMultiple(class="type">class="kw">double &Y[],class="type">class="kw">double &A[],class="type">class="kw">double &B[],class="type">class="kw">double &C[],class="type">class="kw">double &D[])
{
  class="kw">return (mean(Y)-coefficient_of_X(A,Y)*mean(A)-coefficient_of_X(B,Y)*mean(B)-coefficient_of_X(C,Y)*mean(C)-coefficient_of_X(D,Y)*mean(D));
}

◍ 线性模型背后的几个硬前提

在 MT5 里跑线性回归类指标前,得先认清楚它依赖的几条假设,否则拟合出来的线只是看起来顺眼。最基础的一条是线性关系:你假定因变量和自变量之间确实存在直线型关联,若价格结构本质是分段的或带突变的,硬套线性就会系统性偏误。 误差项要满足正态性,并且围绕模型均值随机分布。实操中可把实际值与预测值做散点图,理想状态下点云应在整条回归线上下均匀铺开;若呈现喇叭口或明显弧带,说明异方差或非线性残留,线性模型在该样本上可能失效。 这类模型实现简单、系数易读,但外汇与贵金属属高杠杆高风险品种,假设不满足时模型给出的方向倾向仅作参考,不可直接当作进出场依据。

「线性回归用在行情里的几处硬伤」

把价格序列塞进线性回归,第一个隐含前提是自变量和因变量之间真有一条直线能连起来。实际 K 线里多空切换常是折线甚至突变,硬套直线会系统性低估拐点附近的误差。 极端值对回归系数的破坏力极大。一根秒级插针就能把整条拟合线拽偏,使后续基于斜率的判断全部失真,这种脆弱性在贵金属跳空夜尤为明显。 模型还默认各输入特征彼此独立。但像收盘价和均线这类量本就同源,强行当独立变量处理,会得到统计上虚胖的显著性。 回归刻画的只是均值层面的关系,就像平均数掩盖了分布尾部一样,它给不出极端行情下变量如何脱钩。边界被限定为线性,也意味着非线性套利结构一律看不见。 外汇与贵金属杠杆高、跳空频繁,直接拿裸回归信号下单风险很大,建议先在小布里做残差诊断再决定是否采信。

函数库还差训练与测试这一步

眼下这套线性回归函数库只是搭了骨架,模型的训练与测试模块还没接进去,想直接拿来跑配对相关性策略还差最后一段路。作者把 Python 参考实现丢在 GitHub,目前附件里的 GetDataToFile.mq5(2.85 KB)、LinearRegressionLib.mqh(16.86 KB)、TestScript.mq5(2.32 KB)是能跑通取数和基础拟合的最小集合。 如果你手上有 MT5 环境,先把 TestScript.mq5 挂上实盘模拟账户,看 LinearRegressionLib 吐出的斜率和残差是否符合你盯的贵金属跨期价差;外汇与贵金属杠杆交易风险极高,回测漂亮不等于实盘能活。 下一篇才会补上样本外测试和参数寻优,在那之前别把未训练的函数当信号源。

◍ 把工具请下神坛

回看这一节里读者在评论区挑出的几个刺:有人指出 CSV 必须先转 UTF-8 才能被 Python 读,其实直接在 MQL 里写 UTF-8 就省掉这道工序;还有人纠正图上标错的点——那不是 y-截距,坐标应是 (-5,0) 而非 (0,-5)。这些都不是大道理,只是实盘前顺手该核一下的细节。 另有读者提到 MT4 上做过带综合 R 平方的线性回归指标,也有人说 Pearson 系数分母有缺陷,说明同一套数学工具在不同人手里误差点完全不同。外汇和贵金属杠杆高、滑点凶,模型只是 y=mx+c 这种后缀符号,别把它当圣旨。 开 MT5 把脚本跑一遍,自己导一份 CSV 看编码、对一遍图上的截距点,比盲信任何现成指标都实在。工具用熟了,它只是你验证想法的锤子,不是替你下单的神。

把关联筛选交给小布
小布盯盘的 AIGC 已内置品种关联诊断,打开对应页就能先看清哪些因子和价格强相关,再决定要不要进线性模型,省掉手工画散点图的重复劳动。

常见问题

简单只有一个自变量,多元同时放多个自变量求系数;代码上要从单向量换成矩阵运算,偏理论层面更吃线性代数底子。
先看散点是否近似直线走向,再做相关性过滤,把弱关联列删掉;非线性相关数据硬套线性公式属于根本性错误。
误差项独立同分布且方差恒定这条常被跳过,实盘里价格聚类波动会破功,导致置信区间失真。
可以,品种页的 AIGC 关联诊断能先标出强关联因子,你再拿去写 MQL5 训练脚本,少走弯路。
逻辑回归基于线性组合再做概率映射,SVM 的线性核本质也是找最优分隔直线,核心都脱胎于 y=mx+c 的思路。