在MQL5中构建自适应的自动化交易系统(EA)·进阶篇
📘

在MQL5中构建自适应的自动化交易系统(EA)·进阶篇

第 2/3 篇

线性回归模型的内部状态字段

在 MT5 里用 MQL5 搭一个监督学习式的行情模型,第一步是把训练与验证阶段要用的状态量先声明清楚。下面这段类内成员定义,把学习率搜索、训练轮次、输入输出矩阵和权重偏置全部摊开,方便后续在 OnInit 或独立训练函数里直接读写。 mae_array 长度固定为 30,对应我们在验证集上测试 30 个不同学习率幂次时的平均绝对误差读数;learning_rate_power 与 lr_error_index 配合,记录当前正在评估的学习率 10 的负幂次位置。epochs_power 则决定总训练轮数取 10 的几次方,比如设为 3 即最多约 1000 轮。 x_train / y_train 与 x_validation / y_validation 是成对的向量,分别装训练与验证的样本输入和真实输出;y_hat_train / y_hat_validation 存模型预测值,用来算 mae_train 和 mae_validation 两个误差向量。权重 m 和偏置 b 都是 vector 类型,forecast 是 Double 标量,代表模型对下一根 K 线的点预测。 n 这个 ulong 存的是数据行数(即抓取样本量),在梯度下降的循环里控制边界。外汇与贵金属波动大、跳空频繁,这类线性外推模型在实盘只可作辅助参考,信号失效概率不低,务必先在策略测试器跑通再谈仓位。

MQL5 / C++
class="type">class="kw">double mae_array[class="num">30];
class="type">bool trained;
class="type">int epochs_power;
vector mae_train,mae_validation;
vector x_validation,x_train;
vector y_validation,y_train;
vector y_hat_validation,y_hat_train;
vector m;
vector b;
class="type">class="kw">double forecast;
class="type">class="kw">ulong learning_rate_power;
class="type">int lr_error_index;
class="type">class="kw">double learning_rate;
class="type">class="kw">double epochs;
class="type">class="kw">ulong n;

「回归模型类的私有字段与接口声明」

在 MT5 里自建线性回归器时,先得把时间和索引绑定清楚。下面这组私有成员用 output_start / output_end 与 input_start / input_end 四个 datetime 变量锁定输入输出样本区间,再用对应的 index_ 前缀整型记录柱线偏移位置,避免回测时取错 K 线。 first_reading 这个 double 承担数据缩放基准,allowed_to_evaluate 则是防止样本不足时硬跑拟合的开关。外汇与贵金属行情的高波动特性,意味着缩放因子若取在异常极值上,后续系数可能严重偏离,实盘前应在策略测试器里核对首根取值。 公开接口暴露了模型生命周期:LinearRegression() 构造函数、Init(_fetch,_look_ahead) 设定回看与前瞻窗、GetCurrentValidationData() 拉验证集。私有侧则给出 Fit() 算系数、Evaluate() 按指定 epoch 幂次评估、ScaleInputs() 做输入归一,以及 learning rate 与 epochs 的更新/重置系列方法。 把这段声明直接贴进 .mqh 头文件就能编译,下一步该填的是 Fit() 里的梯度下降循环——那是下一段要拆的。

MQL5 / C++
class="type">class="kw">datetime output_end,output_start,input_end,input_start;
class=class="str">"cmt">//These are the index times for our class="kw">input and output data
class="type">int index_output_end,index_output_start,index_input_end,index_input_start;
class=class="str">"cmt">//This is the value we will use to scale our data
class="type">class="kw">double first_reading;
class="type">bool allowed_to_evaluate;
class=class="str">"cmt">//Update the learning rate
class="type">bool UpdateLearningRate(class="type">void);
class=class="str">"cmt">//Update the number of epochs
class="type">bool UpdateEpochs(class="type">void);
class=class="str">"cmt">//Set the number of epochs
class="type">bool SetEpochs(class="type">int _epochs_power);
class=class="str">"cmt">//Reset the number of epochs 
class="type">bool ResetEpochs(class="type">void);
class=class="str">"cmt">//Reset the learning rate
class="type">bool ResetLearningRate(class="type">void);
class=class="str">"cmt">//This function will fit the coeffeicients
class="type">bool Fit(class="type">void);
class=class="str">"cmt">//This function evaluates the current settings
class="type">bool Evaluate(class="type">class="kw">ulong _index,class="type">int _epochs_power);
class=class="str">"cmt">//This function will scale the class="kw">input data
class="type">bool ScaleInputs(class="type">void);
class=class="str">"cmt">//This function sets the learning rate
class="type">bool SetLearningRate(class="type">class="kw">ulong _learning_rate_power);
class="kw">public:
class=class="str">"cmt">//Constructor 
LinearRegression();
class=class="str">"cmt">//Fetch Current Validation Data
class="type">bool GetCurrentValidationData(class="type">void);
class=class="str">"cmt">//Initialise the LinearRegressor Model
class="type">void Init(class="type">int _fetch,class="type">int _look_ahead);

◍ 线性回归模型的初始化与训练准备

在 MT5 里用 CLinearRegression 类做价格预测时,构造函数只做一件事:用 Print 把当前交易品种打进日志,方便你确认上下文没错。 真正的准备逻辑都在 Init(int _fetch, int _look_ahead) 里。它会先 ObjectsDeleteAll(0) 清掉图表上所有对象,再把 allowed_to_evaluate 置 true 允许后续评估;epochs_power 写死为 4,epochs 算出来是 5 * 10^4 = 50000 次迭代,这是训练轮数上限。 trained 初始为 false,max_learning_rate_power 给到 30,意味着学习率幂次最多搜到 0.1^30。start 取当前周期第 1 根 K 线的时间戳 iTime(_Symbol, PERIOD_CURRENT, 1),作为验证数据末端。 fetch 和 look_ahead 在入参上各减/加 1 存入 this,梯度 m 和偏置 b 都用 vector::Zeros(1) 清零,forecast 先设 0。learning_rate_power 从 0 起跳,lr_error_index 为 0,mae_train 初始化成 10^100 的 1 维向量,mae_validation 则是 30 维、每维填 10^10000——这就是后续找最优学习率时的误差上界占位。 初始化收尾把 learning_rate 算成 0.1^0 = 1,n 等于 fetch 作为样本行数。若 GetCurrentValidationData() 返回真,就接着进数据缩放分支。外汇和贵金属波动剧烈,这种缩放和幂次搜索直接决定过拟合风险,建议你在策略测试器里改 epochs_power 看回测稳定性。

MQL5 / C++
class=class="str">"cmt">//Function to determine if the model has been trained and is ready for use.
class="type">bool Trained(class="type">void);
class=class="str">"cmt">//A function to train the model class="kw">using the best learning rate and the most recent prices
class="type">bool Train(class="type">void);
class=class="str">"cmt">//A function to predict future price class="kw">using the current price.
class="type">class="kw">double Predict(class="type">void);
class=class="str">"cmt">//Destructor
~LinearRegression();
LinearRegression::LinearRegression()
  {
    Print("Current Symbol: ",_Symbol);
  }
class="type">void LinearRegression::Init(class="type">int _fetch,class="type">int _look_ahead)
   {
     class=class="str">"cmt">//Clear The Chart
     ObjectsDeleteAll(class="num">0);
     class=class="str">"cmt">//Allow evaluations
     allowed_to_evaluate = true;
     class=class="str">"cmt">//Epochs power
     epochs_power =class="num">4;
     class=class="str">"cmt">//Set the number of epochs
     epochs = class="num">5 * MathPow(class="num">10,epochs_power);
     class=class="str">"cmt">//Has the model been trained?
     trained = false;
     class=class="str">"cmt">//Set the maximum learning rate power
     max_learning_rate_power = class="num">30;
     class=class="str">"cmt">//Set the end of our validation data
     start = iTime(_Symbol,PERIOD_CURRENT,class="num">1);
     class=class="str">"cmt">//This is how much data we&class="macro">#x27;re going to fetch
     this.fetch = _fetch - class="num">1;
     class=class="str">"cmt">//This is how far into the future we want to forecast
     this.look_ahead = _look_ahead + class="num">1;
     class=class="str">"cmt">//Set the gradient coefficient to a random value
     m = vector::Zeros(class="num">1);
     class=class="str">"cmt">//Set the bias to a random value
     b = vector::Zeros(class="num">1);
     class=class="str">"cmt">//Set the forecast to class="num">0
     forecast = class="num">0;
     class=class="str">"cmt">//Our model&class="macro">#x27;s learning rate will start at class="num">0
     learning_rate_power = class="num">0;
     class=class="str">"cmt">//This is the learning rate we are evaluting
     lr_error_index = class="num">0;
     mae_train = vector::Full(class="num">1,MathPow(class="num">10,class="num">100));
     mae_validation = vector::Full(class="num">30,MathPow(class="num">10,class="num">10000));
     class=class="str">"cmt">//Set the initial learning rate
     learning_rate = MathPow(class="num">0.1,(learning_rate_power));
     class=class="str">"cmt">//Set the number of rows
     n = fetch;
     if(GetCurrentValidationData())
       {
         class=class="str">"cmt">//Scale the data

回归预测与验证数据的落地写法

线性回归模型训练完后,真正产生交易信号的是 Predict() 这一环。它先取当前收盘价 _current_reading = iClose(_Symbol,PERIOD_CURRENT,0),再用已算出的斜率 m[0] 和截距 b[0] 做一步 y = m[0]*x + b[0] 的外推。 预测值大于现价时在终端提示 Buy,小于则提示 Sell,同时用 ObjectCreate 画一条竖线标时间、一条水平线标预测价位。注意这里只是方向倾向,外汇与贵金属杠杆高,信号失效可能很快。 验证数据切分靠 GetCurrentValidationData() 里的索引偏移:index_output_end 固定为 1,index_output_start = 1 + fetch,输入侧再叠加 look_ahead。也就是说模型看的是未来偏移 look_ahead 根 K 线后的收盘价做对照。 复制下面代码到 MT5 的 LinearRegression 类里,把 look_ahead 设成 5、fetch 设成 200,能在 EURUSD 的 M15 上直接看到预测水平线漂在现价上方或下方。

MQL5 / C++
      ScaleInputs();
      class=class="str">"cmt">//Fit the model
      Fit();      
   }       
 }
class="type">class="kw">double LinearRegression::Predict(class="type">void)
  {
    if(Trained())
     {
       class="type">class="kw">double _current_reading = iClose(_Symbol,PERIOD_CURRENT,class="num">0);
       predict = iTime(_Symbol,PERIOD_CURRENT,class="num">0);
       
       class="type">class="kw">double prediction = (m[class="num">0]*_current_reading)+b[class="num">0];
       if(prediction > _current_reading)
        {
          Comment("Buy, forecast: ",prediction);
        }
       else if(prediction < _current_reading)
        {
          Comment("Sell, forecast: ",prediction);
        }
       
       ObjectCreate(class="num">0,"prediction point",OBJ_VLINE,class="num">0,predict,class="num">0);
       ObjectCreate(class="num">0,"forecast",OBJ_HLINE,class="num">0,predict,prediction);
       class="kw">return(prediction);
     }
    
    class="kw">return(class="num">0);
  }
class="type">bool LinearRegression::GetCurrentValidationData(class="type">void)
  {
    class=class="str">"cmt">//Indexes
    index_output_end = class="num">1;
    index_output_start = index_output_end + fetch;
    index_input_end = index_output_end + look_ahead;
    index_input_start = index_output_start + look_ahead;
    
    class=class="str">"cmt">//Assigning time stamps
    output_end =  iTime(Symbol(),PERIOD_CURRENT,index_output_end);
    output_start = iTime(Symbol(),PERIOD_CURRENT,index_output_start);
    input_end = iTime(Symbol(),PERIOD_CURRENT,index_input_end);
    input_start = iTime(Symbol(),PERIOD_CURRENT,index_input_start);
    
    class=class="str">"cmt">//Get the output data
    if(!y_validation.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,output_end,fetch))
     {
       Print("Failed to get market data: ",GetLastError());
       class="kw">return(false);
     }
    class=class="str">"cmt">//Get the class="kw">input data

「验证集与训练集的切片对齐」

这段逻辑干的事很直白:先抓验证集的收盘价向量,再检查 x、y 两个向量长度是否一致,不一致直接返 false,避免后面矩阵维度炸掉。 若 CopyRates 拉不到数据,终端会打印错误码并退出;成功的话会在图表上画四条垂直参考线,标记 validation 与 training 各自的起止时间,肉眼就能核对窗口有没有错位。 训练集的时间戳不是直接传参,而是用索引推算:index_output_end = index_input_start + look_ahead*2,再借 iTime 反查真实时间。look_ahead 翻倍这段是滑动窗口的关键,调小它会让输入输出重叠度上升,过拟合概率可能变大。 最后分别用 y_train、x_train 的 CopyRates 按 output_end / input_end 拉取训练数据,这里若返回 false 仅打印提示而不中断,实盘跑之前建议自己补个 return 保护。

MQL5 / C++
if(!x_validation.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,input_end,fetch))
      {
         Print("Failed to get market data: ",GetLastError());
         class="kw">return(false);
      }
class=class="str">"cmt">//Print the vectors we have
if(x_validation.Size() != y_validation.Size())
   {
      Print("Failed to get market data: Our vectors aren&class="macro">#x27;t the same length.");
      class="kw">return(false);
   }
   
   class=class="str">"cmt">//Print the vectors and plot the data points
   Print("X validation: ",x_validation);
   ObjectCreate(class="num">0,"X validation end",OBJ_VLINE,class="num">0,input_end,class="num">0);
   ObjectCreate(class="num">0,"X validation start",OBJ_VLINE,class="num">0,input_start,class="num">0);
   
   class=class="str">"cmt">//Print the vectors and plot the data points
   Print("y validation: ",y_validation);
   ObjectCreate(class="num">0,"y validation end",OBJ_VLINE,class="num">0,output_end,class="num">0);
   ObjectCreate(class="num">0,"y validation start",OBJ_VLINE,class="num">0,output_start,class="num">0);
   
   class=class="str">"cmt">//Set the training data
   index_output_end = index_input_start + (look_ahead * class="num">2);
   index_output_start = index_output_end + fetch;
   index_input_end = index_output_end + look_ahead;
   index_input_start = index_output_start + look_ahead;
   
   class=class="str">"cmt">//Assigning time stamps
   output_end =  iTime(Symbol(),PERIOD_CURRENT,index_output_end);
   output_start = iTime(Symbol(),PERIOD_CURRENT,index_output_start);
   input_end = iTime(Symbol(),PERIOD_CURRENT,index_input_end);
   input_start = iTime(Symbol(),PERIOD_CURRENT,index_input_start);
   
   class=class="str">"cmt">//Copy the training data  
   if(!y_train.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,output_end,fetch))
      {
         Print("Error fetching training data ",GetLastError());
      }
   
   class=class="str">"cmt">//Copy the training data  
   if(!x_train.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,input_end,fetch))

◍ 训练数据对齐与梯度下降拟合的实现细节

取数阶段先校验 x_train 与 y_train 的 Size() 是否一致,若维度不匹配直接 Print 报错退出,避免后续矩阵运算越界。这一步在 MT5 脚本里常被忽略,但实盘回测中样本错位会让回归系数完全失真。 数据就绪后在图表上用 OBJ_VLINE 标出 input_start / input_end 与 output_start / output_end,四条竖线把训练区间和标签区间可视化,方便肉眼核对特征与目标的偏移关系。 Fit() 里跑的是标准批量梯度下降:每个 epoch 先算 y_hat_train = m[0]*x_train + b[0],再用 (-2.0/n) 乘残差求和得到导数。learning_rate 乘以导数后更新 m[0] 与 b[0],循环 epochs 次逼近最优斜率与截距。 mae_train 用 MathAbs(y_train - y_hat_train).Mean() 记录平均绝对误差,外汇与贵金属市场波动剧烈、高风险,该值仅反映训练集拟合程度,对未知行情的解释力可能随品种与周期变化而下降。

MQL5 / C++
{
   Print("Error fetching training data ",GetLastError());
}

class=class="str">"cmt">//Check if the data matches
if(x_train.Size() != y_train.Size())
  {
   Print("Error fetching training dataL: The x and y vectors are not the same size");
  }

class=class="str">"cmt">//Print the vectors and plot the data points 
Print("X training: ",x_train);
ObjectCreate(class="num">0,"X training end",OBJ_VLINE,class="num">0,input_end,class="num">0);
ObjectCreate(class="num">0,"X training start",OBJ_VLINE,class="num">0,input_start,class="num">0);

Print("y training: ",y_train);
ObjectCreate(class="num">0,"y training end",OBJ_VLINE,class="num">0,output_end,class="num">0);
ObjectCreate(class="num">0,"y training start",OBJ_VLINE,class="num">0,output_start,class="num">0);
class="kw">return(true);
 }
class="type">bool LinearRegression::Fit()
  {
   Print("Fitting a linear regression on the training set with learning rate ",learning_rate_power);
   Print("Evalutaions: ",allowed_to_evaluate);
   for(class="type">int i =class="num">0; i < epochs;i++)
     {
      class=class="str">"cmt">//Measure error
      y_hat_train = (m[class="num">0]*x_train) + b[class="num">0];
      vector y_minus_y_hat = (y_train - y_hat_train);
      vector y_minus_y_hat_sqaured = MathAbs((y_train - y_hat_train));
mae_train.Set(class="num">0,( y_minus_y_hat_sqaured.Mean()));
      vector x_times_y_minus_y_hat = (x_train*(y_train -y_hat_train));
      
      class=class="str">"cmt">//Aproximate the derivatives
      class="type">class="kw">double derivative_m = (-class="num">2.0/n) * x_times_y_minus_y_hat.Sum();
      class="type">class="kw">double derivative_b = (-class="num">2.0/n) * y_minus_y_hat.Sum();
      
      class=class="str">"cmt">//Update the linear parameters
      m[class="num">0] = m[class="num">0] - (learning_rate * derivative_m);
      b[class="num">0] = b[class="num">0] - (learning_rate * derivative_b);
     }
     
     class=class="str">"cmt">//Finished fitting the coefficients

常见问题

主要存权重向量、偏置项、学习率、迭代次数和损失值等私有字段,通过公开接口读写,避免外部直接改状态。
先声明私有字段并给默认初值,再分配样本容器,最后做特征与标签的维度对齐,确保训练前数据非空且形状一致。
可以。小布能读取你的策略逻辑,指出验证集切片与训练集未对齐、梯度下降步长异常等问题,并给出修正建议。
用同一时间轴偏移量切分,训练取t-N到t-1,验证取t到t+M,并在拟合前断言两者时间戳连续,断开即报错。
常见是学习率过大、特征未归一化或标签错位。先调小学习率并把输入缩放到同量纲,再查标签偏移一行的情况。