数据科学与机器学习(第 01 部分):线性回归·进阶篇
📈

数据科学与机器学习(第 01 部分):线性回归·进阶篇

(2/3)·从斜率截距到 R 平方,把 MQL5 里的线性回归从公式变成可交易的判断依据

新手友好 第 2/3 篇

很多交易者把任意指标和价格往散点图上一扔就拟合直线,却没看相关系数够不够强。用非线性弱相关数据硬套线性模型,得到的斜率和截距只是数字噪音,不会告诉你真实的价格驱动逻辑。

◍ 把均线和RSI落进CSV的收尾写法

这段逻辑处在数据导出函数的末尾,负责把每一根 K 线上的 MA 与 RSI 数值按当前品种小数位精度转成字符串并写入文件。 str3 和 str4 都用 DoubleToString 配合 Digits() 做格式化,Digits() 返回的是当前图表品种报价小数位(多数外汇对为 4 或 5,黄金常见 2 或 3),避免写入时精度错乱。 FileWrite 一次性把 str1、str2、str3、str4 以制表符分隔落盘,循环结束后调用 FileClose 释放句柄;若句柄未关,MT5 会锁住文件且下次打开可能报 5004 错误。 外汇与贵金属波动剧烈、杠杆风险高,导出的历史数据仅用于回测参考,不代表任何方向确定性。

MQL5 / C++
      class="type">class="kw">string str3 = DoubleToString(ma_values[i],Digits());
      class="type">class="kw">string str4 = DoubleToString(rsi_values[i],Digits());
      FileWrite(handle,str1,str2,str3,str4);
     }
   }
 FileClose(handle);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+

「用皮尔逊R筛掉弱相关列」

皮尔逊相关系数 R 衡量两列数据的线性关联强度,取值落在 -1 到 +1。R 接近 1 是强正线性相关,接近 -1 是强负线性相关,贴近 0 代表基本没有线性耦合。做回归前先算一遍 R,能把噪声列剔掉。 在 MT5 里我写了 mean() 求均值、corrcoef() 算 R,核心就是协方差除以各自标准差之积。下面那段代码直接丢进 linearRegressionLib.mqh 就能用,TestScript 里打印三组数据对比。 实测输出:NASDAQ 对 S&P 500 的 R = 0.9807,对 50SMA 的 R = 0.8747,对 RSI 的 R = 0.2425。前两组高度共线,RSI 几乎不线性相关。 外汇和贵金属品种同样适用这套筛法,但价差跳空多、相关性时变,高 R 只代表样本期内线性黏合,换周期可能塌掉,属高风险验证动作。看到 RSI 那列 0.24 就该明白:拿它当自变量喂简单线性回归,大概率是浪费自由度。

MQL5 / C++
class="type">class="kw">double CSimpleLinearRegression::mean(class="type">class="kw">double &data[])
 {
   class="type">class="kw">double x_y__bar=class="num">0;
  
   for (class="type">int i=class="num">0; i<ArraySize(data); i++)
     {
       x_y__bar += data[i]; class=class="str">"cmt">// all values summation
     }
           
     x_y__bar = x_y__bar/ArraySize(data); class=class="str">"cmt">//total value after summation divided by total number of elements
  
   class="kw">return(x_y__bar); 
 }
class="type">class="kw">double CSimpleLinearRegression::corrcoef(class="type">class="kw">double &x[],class="type">class="kw">double &y[])
 {
   class="type">class="kw">double r=class="num">0;
   class="type">class="kw">double numerator =class="num">0, denominator =class="num">0;
   class="type">class="kw">double x__x =class="num">0, y__y=class="num">0;
  
   for(class="type">int i=class="num">0; i<ArraySize(x); i++)
     {
           numerator += (x[i]-mean(x))*(y[i]-mean(y));
           x__x += MathPow((x[i]-mean(x)),class="num">2);   class=class="str">"cmt">//summation of x values minus it&class="macro">#x27;s mean squared 
           y__y += MathPow((y[i]-mean(y)),class="num">2);   class=class="str">"cmt">//summation of y values minus it&class="macro">#x27;s mean squared  
     }
       denominator = MathSqrt(x__x)*MathSqrt(y__y);   class=class="str">"cmt">//left x side of the equation squared times right side of the equation squared
       r = numerator/denominator;  
    class="kw">return(r);
 }
  Print("Correlation Coefficient NASDAQ vs S&P class="num">500 = ",lr.corrcoef(s_p,y_nasdaq));
  Print("Correlation Coefficient NASDAQ vs 50SMA = ",lr.corrcoef(ma,y_nasdaq));
  Print("Correlation Coefficient NASDAQ Vs rsi = ",lr.corrcoef(rsi,y_nasdaq));

斜率 m 在回归类里怎么算

线性回归里 x 的系数就是斜率 m,定义上是 Y 变化量除以 X 变化量,也就是那条拟合直线的坡度。代数表达为 Y = mX + C,C 是截距。 求解 m 的标准做法是拿每个样本点偏离各自均值的乘积求和,再除以 x 偏离均值的平方和。也就是说 m = Σ((xᵢ−x̄)(yᵢ−ȳ)) / Σ((xᵢ−x̄)²),分母恒非负,分子符号决定斜率方向。 下面这段是类里算斜率的函数,y_values 和 x_values 由 Init() 复制进来,两个数组长度不一致时会打印警告但继续跑,可能带来不准确结果。 系数函数先求 x、y 均值,再循环累加分子分母,最后返回比值;循环上限用 (ArraySize(x)+ArraySize(y))/2 隐式假定两数组等长。 Init() 除了拷贝数组,还打开了文件统计行数列数存到 m_rows、m_columns,这部分和斜率计算无直接关系,但说明该类设计上打算从 CSV 类文件读数据。

MQL5 / C++
class="type">class="kw">double CSimpleLinearRegression::coefficient_of_X()
 {
   class="type">class="kw">double m=class="num">0;
   class="type">class="kw">double x_mean=mean(x_values);
   class="type">class="kw">double y_mean=mean(y_values);;
class=class="str">"cmt">//---  
    {
      class="type">class="kw">double x__x=class="num">0, y__y=class="num">0;
      class="type">class="kw">double numerator=class="num">0, denominator=class="num">0; 
      
      for (class="type">int i=class="num">0; i<(ArraySize(x_values)+ArraySize(y_values))/class="num">2; i++)
       {
         x__x = x_values[i] - x_mean; class=class="str">"cmt">//right side of the numerator(x-side)
         y__y = y_values[i] - y_mean; class=class="str">"cmt">//left side of the numerator(y-side)
         
         numerator += x__x * y__y;  class=class="str">"cmt">//summation of the product two sides of the numerator
         denominator += MathPow(x__x,class="num">2); 
       }
      m = numerator/denominator;
    }
   class="kw">return (m);
 }
class="type">void CSimpleLinearRegression::Init(class="type">class="kw">double& x[], class="type">class="kw">double& y[])
 {
   ArrayCopy(x_values,x);
   ArrayCopy(y_values,y);
class=class="str">"cmt">//---
   if (ArraySize(x_values)!=ArraySize(y_values))
    Print(" Two of your Arrays seems to vary In Size, This could lead to inaccurate calculations ",__FUNCTION__);
   
   class="type">int columns=class="num">0, columns_total=class="num">0;
   class="type">int rows=class="num">0;
   
   fileopen();
   class="kw">while (!FileIsEnding(m_handle))
     {
        class="type">class="kw">string data = FileReadString(m_handle);
         if (rows==class="num">0) 
           {
            columns_total++;
           }
         columns++;
         
        if (FileIsLineEnding(m_handle))
         {
          rows++;
          columns=class="num">0;
         }
     }
     
   m_rows = rows;
   m_columns = columns; 
   FileClose(m_handle);
class=class="str">"cmt">//---    
 }

◍ 从方程到代码算出回归截距

线性回归里当自变量 x 取 0 时对应的 y 值就是截距,公式从 Y = M X + C 移项可得 C = Y - M X,其中 Y 和 X 分别是因变量与自变量序列的均值。把这个计算落进 MT5 类方法里,就能直接打印出可用模型。 下面这段 MQL5 是截距与模型主函数的核心:y_intercept() 用均值减斜率乘 x 均值返回 C;LinearRegressionMain() 先取斜率与截距,Print 出形如「Y =4.35x+-4818.55」的字符串,再按 y = m x + c 遍历填充预测数组。某次在 #SP500 H1 上跑脚本,终端输出为「The Linear Regression Model is Y =4.35241x+-4818.54986」,说明当时拟合出的截距约 -4818.55。 double CSimpleLinearRegression::y_intercept() { // c = y - mx return (mean(y_values)-coefficient_of_X()*mean(x_values)); } void CSimpleLinearRegression::LinearRegressionMain(double &predict_y[]) { double slope = coefficient_of_X(); double constant_y_intercept= y_intercept(); Print("The Linear Regression Model is "," Y =",DoubleToString(slope,2),"x+",DoubleToString(constant_y_intercept,2)); ArrayResize(predict_y,ArraySize(y_values)); for (int i=0; i<ArraySize(x_values); i++) predict_y[i] = coefficient_of_X()*x_values[i]+y_intercept(); } lr.LinearRegressionMain(y_nasdaq_predicted); 数据要从 CSV 读入,GetDataToArray() 按列号抽取双精度数组;调用时务必核对列号,第一列若为标题行且转 double 为 0 会被 ArrayRemove 剔掉。外汇与贵金属行情受杠杆影响波动剧烈,用历史数据拟合的截距只反映过去区间,实盘参考价值有限,须以 MT5 回测验证。 别把打印出的截距当预测锚点 截距是样本内均值关系产物,换周期或品种可能大幅漂移。开 MT5 把上面函数挂到自己的 CSV 数据上,改 DoubleToString 的小数位到 5,看不同品种截距数量级差异再决定是否进模型。

MQL5 / C++
class="type">class="kw">double CSimpleLinearRegression::y_intercept()
{
  class=class="str">"cmt">// c = y - mx
  class="kw">return (mean(y_values)-coefficient_of_X()*mean(x_values));
}
class="type">void CSimpleLinearRegression::LinearRegressionMain(class="type">class="kw">double &predict_y[])
{
  class="type">class="kw">double slope = coefficient_of_X();
  class="type">class="kw">double constant_y_intercept= y_intercept();
  Print("The Linear Regression Model is "," Y =",DoubleToString(slope,class="num">2),"x+",DoubleToString(constant_y_intercept,class="num">2));
  ArrayResize(predict_y,ArraySize(y_values));
  for (class="type">int i=class="num">0; i<ArraySize(x_values); i++)
    predict_y[i] = coefficient_of_X()*x_values[i]+y_intercept();
}
lr.LinearRegressionMain(y_nasdaq_predicted);
class="type">void CSimpleLinearRegression::GetDataToArray(class="type">class="kw">double &array[],class="type">class="kw">string file_name,class="type">class="kw">string delimiter,class="type">int column_number)
{
  m_filename = file_name;
  m_delimiter = delimiter;
  class="type">int column=class="num">0, columns_total=class="num">0;
  class="type">int rows=class="num">0;
  fileopen();
  class="kw">while (!FileIsEnding(m_handle))
    {
      class="type">class="kw">string data = FileReadString(m_handle);
      if (rows==class="num">0)
        {
          columns_total++;
        }
      column++;
      class=class="str">"cmt">//Get data by each Column 
      if (column==column_number) class=class="str">"cmt">//if we are on the specific column that we want 
        {
          ArrayResize(array,rows+class="num">1);
          if (rows==class="num">0)
            {
              if ((class="type">class="kw">double(data))!=class="num">0) class=class="str">"cmt">//Just in case the first line of our CSV column has a name of the column 
                {
                  array[rows]= NormalizeDouble((class="type">class="kw">double)data,Digits());
                }
              else { ArrayRemove(array,class="num">0,class="num">1); }
            }
          else
            {

「把 CSV 指标列灌进回归数组」

线性回归类读取外部 CSV 时,靠 GetDataToArray 按列号把数据搬进 double 数组。上面这段把文件名、分隔符和列序号传进去,第 1 列 S&P500 进 s_p,第 2 列 NASDAQ 进 y_nasdaq,后续才能拿这两组序列算斜率。

文件打开用 FileOpen 带 FILE_READFILE_WRITEFILE_CSV 和逗号分隔符;句柄拿到 INVALID_HANDLE 就打印错误函数和 GetLastError,避免静默失败。读行时用 FileIsLineEnding 判断换行,rows 自增、column 归零,逐格 StringToDouble 写进 array[rows-1]。

样本里 NASDAQ_DATA.csv 表头是 S&P500,NASDAQ,50SMA,13RSI,前 8 行 NASDAQ 在 14038.1~14168.6 之间波动,50SMA 始终贴在 14104~14121 附近,说明价差回归特征明显。外汇和贵金属做类似加载时波动更剧烈,高风险,参数列选错会直接带偏拟合。 实跑只需改 OnStart 里的 file_name 和列号,把 LR 初始化换成自己的品种序列即可在 MT5 里印出 coefficient_of_X 看斜率正负。

MQL5 / C++
   array[rows-class="num">1]= StringToDouble(data);
   }
   class=class="str">"cmt">//Print("column ",column," "," Value ",(class="type">class="kw">double)data);
   }
class=class="str">"cmt">//---
   if (FileIsLineEnding(m_handle))
    {
     rows++;
     column=class="num">0;
    }
   } 
   FileClose(m_handle);
}
class="type">void CSimpleLinearRegression::fileopen(class="type">void)
{
  m_handle = FileOpen(m_filename,FILE_READ|FILE_WRITE|FILE_CSV,m_delimiter);     
  if (m_handle==INVALID_HANDLE)
    {     
      Print("Data to work with is nowhere to be found, Error = ",GetLastError()," ", __FUNCTION__);
    }
class=class="str">"cmt">//---
}
   class="type">class="kw">double s_p[]; class=class="str">"cmt">//Array for storing S&P class="num">500 values
   class="type">class="kw">double y_nasdaq[]; class=class="str">"cmt">//Array for storing NASDAQ values
   lr.GetDataToArray(s_p,file_name,",",class="num">1);
   lr.GetDataToArray(y_nasdaq,file_name,",",class="num">2);
 class="type">void   GetDataToArray(class="type">class="kw">double& array[],class="type">class="kw">string filename, class="type">class="kw">string delimiter, class="type">int column_number);
S&P500,NASDAQ,50SMA,13RSI
class="num">4377.5,class="num">14168.6,class="num">14121.1,class="num">59.3
class="num">4351.3,class="num">14053.2,class="num">14118.1,class="num">48.0
class="num">4342.6,class="num">14079.3,class="num">14117.0,class="num">50.9
class="num">4321.2,class="num">14038.1,class="num">14115.6,class="num">46.1
class="num">4331.8,class="num">14092.9,class="num">14114.6,class="num">52.5
class="num">4336.1,class="num">14110.2,class="num">14111.8,class="num">54.7
class="num">4331.5,class="num">14101.4,class="num">14109.4,class="num">53.8
class="num">4336.4,class="num">14096.8,class="num">14104.7,class="num">53.3
.....
class="type">void OnStart()
{
   class="type">class="kw">string file_name = "NASDAQ_DATA.csv";
   class="type">class="kw">double s_p[];
   class="type">class="kw">double y_nasdaq[];
   class="type">class="kw">double y_nasdaq_predicted[];
   
   lr.GetDataToArray(s_p,file_name,",",class="num">1); class=class="str">"cmt">//Data is taken from the first column and gets stored in the s_p Array
   lr.GetDataToArray(y_nasdaq,file_name,",",class="num">2); class=class="str">"cmt">//Data is taken from the second column and gets stored in the y_nasdaq Array
   
class=class="str">"cmt">//---
      lr.Init(s_p,y_nasdaq);        
      {
       lr.LinearRegressionMain(y_nasdaq_predicted);
       Print("slope of a line ",lr.coefficient_of_X());
      }
}

用 R 平方看线性回归拟合力度

R 平方衡量的是模型对 y 总方差的解释比例。计算核心是先求每个样本的误差:实际 y 减预测 y,再平方后求和,除以实际 y 减其均值的平方和,用 1 减这个比值。平方处理既避免正负误差相消,也对大偏差施以更重惩罚,逼出更紧的拟合线。 该系数落在 0 到 1 之间。0 代表模型没解释任何波动,基本不可用;1 代表完美解释,但实盘里接近 1 往往暗示过拟合或未来函数风险。可把它直观读成「模型靠谱百分比」,但外汇与贵金属属高风险品种,历史拟合优度不代表样本外仍成立。 在 MT5 测试脚本里跑纳斯达克 H1 数据,终端打印出 R_SQUARED = 0.9590906984145334,即该简单线性回归解释了标普500约 95.9% 的方差,预测吻合度可能偏高。下面这段代码就是 r_squared() 的实现与调用落点。 别把高 R 平方当圣杯 拟合 95% 以上是回测现象,换周期或遇流动性断裂时解释力可能骤降,验证前先拿近期行情重跑再信。

MQL5 / C++
class="type">class="kw">double CSimpleLinearRegression::r_squared()
 {
   class="type">class="kw">double error=class="num">0;
   class="type">class="kw">double numerator =class="num">0, denominator=class="num">0;
   class="type">class="kw">double y_mean = mean(y_values);
class=class="str">"cmt">//---
   if (ArraySize(m_ypredicted)==class="num">0)
      Print("The Predicted values Array seems to have no values, Call the main Simple Linear Regression Funtion before any use of this function = ",__FUNCTION__);
   else
     {
         for (class="type">int i=class="num">0; i<ArraySize(y_values); i++)
           {
               numerator += MathPow((y_values[i]-m_ypredicted[i]),class="num">2);
               denominator += MathPow((y_values[i]-y_mean),class="num">2);
           }
         error = class="num">1 - (numerator/denominator);
     }
   class="kw">return(error);
 }
      class="kw">private:
                   class="type">int    m_handle;
                  class="type">class="kw">string  m_filename;
                  class="type">class="kw">string  m_delimiter; 
                  class="type">class="kw">double  m_ypredicted[];
                  class="type">class="kw">double  x_values[];
                  class="type">class="kw">double  y_values[];

class=class="str">"cmt">//At the end of the function LinearRegressionMain(class="type">class="kw">double &predict_y[]) I added the following line,
class=class="str">"cmt">// Copy the predicted values to m_ypredicted[], to be Accessed inside the library
   ArrayCopy(m_ypredicted,predict_y);
   Print(" R_SQUARED = ",lr.r_squared());
把关联筛选交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到哪些指标与价格强关联、R 平方落在什么区间,你只需决定要不要据此调整仓位。外汇贵金属波动剧烈,模型输出仅作概率参考。

常见问题

斜率 m 表示 x 每变动一单位 y 的平均变动量;在价格与均线的关系里,它刻画了均线偏移对应价格位移的敏感程度,可能随品种和周期变化。
线性模型假设变量间存在直线关系,若实际是曲线或弱关联,拟合出的系数无解释力,用于交易会系统性误判,倾向产生亏损。
可以,小布盯盘对应品种页内置了关联强度、斜率、截距与 R 平方的自动计算,省去你手算和导数据的重复劳动。
R 平方低说明解释力有限,若相关系数显著说明存在线性方向但噪音大,概率上可参考方向,不宜单独作为入场依据。
本篇聚焦线性模型,弱线性关联列确实该删;非线性因子需在后续算法里单独处理,不在本次线性回归构建范围内。