重构MQL5中的经典策略(第三部分):富时100指数预测·进阶篇
📈

重构MQL5中的经典策略(第三部分):富时100指数预测·进阶篇

(2/3)·从LSE百年蓝筹到20步外推,多数人在树模型里把前瞻能力自己删掉了

实战向进阶 第 2/3 篇
把决策树直接搬来预测指数未来收盘价,是常见误操作——它只返回最匹配组的均值,根本不做外推。你以为模型在「预测」,其实它连一步向前看的能力都没有,外汇贵金属高杠杆下这种盲区会放大亏损概率。

◍ 搭一个带线性回归预测的 EA 骨架

做 EA 先放开给用户调的 input:look_ahead=20 控制向前预测多少根、rsi_period=20 给情绪过滤用、profit_target=20 点作为平仓参考、ai_auto_close=true 让模型自己收尾。这些不是写死的,实盘前你得按品种波动重设。 核心全局量里,input_matrix 是 12 行 × 20 列的零矩阵,11 支富时成分股加 UK100 指数塞进 list_of_companies,mean_values/std_values 各 11 个用来做归一化。coefficients 矩阵留着装回归系数,open_ticket 记当前单号。 流程上,OnInit 先挂 RSI 再校验,过了就抓训练数据、算系数、核风险参数;OnTick 里先切 UK100 品种,取新行情和指标,模型给信号后还要看周线商业周期涨跌 + RSI 方向是否合拍才动手。有仓时靠模型态和系统态不一致抓反转。 回测图显示这套结构能跑通,但外汇/贵金属波动远大于股指,直接搬参数大概率过拟合,建议先开 MT5 用策略测试器跑一遍再谈。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 FTSE class="num">100 AI.mq5 |
class=class="str">"cmt">//|                                                     Gamuchirai Zororo Ndawana |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Gamuchirai Zororo Ndawana"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| User inputs                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">int look_ahead = class="num">20;       class=class="str">"cmt">// How far into the future should we forecast?
class="kw">input class="type">int rsi_period = class="num">20;       class=class="str">"cmt">// The period of our RSI
class="kw">input class="type">int profit_target = class="num">20;     class=class="str">"cmt">// After how much profit should we close our positions?
class="kw">input class="type">bool ai_auto_close = true; class=class="str">"cmt">// Should the AI automatically close positions?
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Libraries we need                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include  <Trade/Trade.mqh>
CTrade Trade;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global variables                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double position_profit = class="num">0;
class="type">int fetch = class="num">20;
matrix coefficients;
matrix input_matrix = matrix::Zeros(class="num">12,fetch);
class="type">class="kw">double mean_values[class="num">11],std_values[class="num">11],rsi_buffer[class="num">1];
class="type">class="kw">string list_of_companies[class="num">11] = {"ADM.LSE","AAL.LSE","ANTO.LSE","AHT.LSE","AZN.LSE","ABF.LSE","AV.LSE","BARC.LSE","BP.LSE","BKG.LSE","UK100"};
class="type">ulong open_ticket;

「用线性回归把指数和成分股绑在一起」

做跨品种回归预测,第一步是把因变量和自变量拉到同一张矩阵里。下面这段代码以 UK100 收盘价为预测目标,拿 11 支成分股做输入特征,先把目标用 CopyRates 抓到 target 向量,再在输入矩阵第 0 行塞入全 1 截距项,方便后面直接算回归系数。 循环里对 11 个成分股逐个 SymbolSelect 加到市场观察窗,取与 target 等长的收盘价序列,记下均值和标准差后做 z-score 标准化(减均值除标准差),再按行写进 input_matrix。标准化这一步很关键:不同成分股价格量纲差太大,不缩放矩阵求逆会数值不稳。 拟合只一行:coefficients = target.MatMul(input_matrix.PInv()),即拿目标向量乘输入矩阵的伪逆,得到多元线性回归系数。预测时重新填一遍当日收盘价、同样标准化,用 coefficients 点乘输入矩阵首行就能出 forecast。外汇和贵金属做类似跨市场回归时杠杆高、跳空多,标准化参数若用旧样本均值方差,行情结构切换时预测偏差可能明显放大。 别把伪逆当万能解 当输入矩阵列间高度共线(比如成分股同涨同跌),PInv 给出的系数方差会暴增,回测好看实盘容易歪。MT5 里可以先用 input_matrix.Corr() 扫一遍相关性,剔除相关系数 >0.95 的冗余列再拟合。

MQL5 / C++
class="type">void fetch_training_data(class="type">void)
  {
class=class="str">"cmt">//--- Fetch the target
   target.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_CLOSE,class="num">1,fetch);
class=class="str">"cmt">//--- Add the intercept
   input_matrix.Row(intercept,class="num">0);
class=class="str">"cmt">//--- Fill in the class="kw">input matrix
   for(class="type">int i = class="num">0; i < class="num">11; i++)
     {
     class=class="str">"cmt">//--- Add the symbol to market watch
       SymbolSelect(list_of_companies[i],true);
     class=class="str">"cmt">//--- Fetch historical data
       vector temp = vector::Zeros(fetch);
       temp.CopyRates(list_of_companies[i],PERIOD_CURRENT,COPY_RATES_CLOSE,class="num">1+look_ahead,fetch);
     class=class="str">"cmt">//--- Store the mean value and standard deviation, also scale the data
       mean_values[i] = temp.Mean();
       std_values[i] = temp.Std();
       temp = ((temp - mean_values[i]) / std_values[i]);
     class=class="str">"cmt">//--- Add the data to the matrix
       input_matrix.Row(temp,i+class="num">1);
     }
  }
class="type">void model_fit(class="type">void)
  {
class=class="str">"cmt">//--- Calculating coefficient values
   coefficients = target.MatMul(input_matrix.PInv());
  }
class="type">void model_predict(class="type">void)
  {
class=class="str">"cmt">//--- Add the intercept
   intercept = vector::Ones(class="num">1);
   input_matrix.Row(intercept,class="num">0);
class=class="str">"cmt">//--- Fill in the class="kw">input matrix
   for(class="type">int i = class="num">0; i < class="num">11; i++)
     {
     class=class="str">"cmt">//--- Fetch historical data
       vector temp = vector::Zeros(class="num">1);
       temp.CopyRates(list_of_companies[i],PERIOD_CURRENT,COPY_RATES_CLOSE,class="num">0,class="num">1);
     class=class="str">"cmt">//--- Normalize and scale the data
       temp = ((temp - mean_values[i]) / std_values[i]);
     class=class="str">"cmt">//--- Add the data to the matrix
       input_matrix.Row(temp,i+class="num">1);
     }
class=class="str">"cmt">//--- Calculate the model forecast
   forecast = (
                 (class="num">1 * coefficients[class="num">0,class="num">0]) +
                 (input_matrix[class="num">0,class="num">1] * coefficients[class="num">0,class="num">1]) +
                 (input_matrix[class="num">0,class="num">2] * coefficients[class="num">0,class="num">2]) +
                 (input_matrix[class="num">0,class="num">3] * coefficients[class="num">0,class="num">3]) +

UK100 模型状态与卖单触发逻辑

模型把输入矩阵第 0 行第 4 到第 11 列分别乘以对应系数后累加,得到当前 forecast 预测值。这段代码只展示了累加尾部,但核心意图很清楚:用 8 个特征加权求和逼近价格。 预测值和 UK100 当前收盘价比较,决定 model_state:forecast 大于 iClose 就置 1,小于就置 -1。系统状态和模型状态不一致时,往往意味着潜在反转概率上升,这是后续下单判断的锚点。 update_market_data 里用 SymbolInfoDouble 抓 UK100 的 bid/ask,再用 CopyBuffer 从 RSI 句柄读 1 根柱的缓冲到 rsi_buffer,刷新频率跟主循环走。 check_sell 的硬条件是周线收盘价低于 12 周前(iClose("UK100",PERIOD_W1,0) < iClose("UK100",PERIOD_W1,12)),且 RSI 缓冲值小于 50,满足后直接 Trade.Sell(0.3,"UK100",bid,0,0,"FTSE 100 AI") 下 0.3 手并记 ticket。外汇与指数差价合约高风险,UK100 这类产品杠杆放大波动,实盘前务必在 MT5 策略测试器用历史数据验证该状态机。

MQL5 / C++
  (input_matrix[class="num">0,class="num">4] * coefficients[class="num">0,class="num">4]) +
  (input_matrix[class="num">0,class="num">5] * coefficients[class="num">0,class="num">5]) +
  (input_matrix[class="num">0,class="num">6] * coefficients[class="num">0,class="num">6]) +
  (input_matrix[class="num">0,class="num">7] * coefficients[class="num">0,class="num">7]) +
  (input_matrix[class="num">0,class="num">8] * coefficients[class="num">0,class="num">8]) +
  (input_matrix[class="num">0,class="num">9] * coefficients[class="num">0,class="num">9]) +
  (input_matrix[class="num">0,class="num">10] * coefficients[class="num">0,class="num">10]) +
  (input_matrix[class="num">0,class="num">11] * coefficients[class="num">0,class="num">11])
  );
class=class="str">"cmt">//--- Store the model&class="macro">#x27;s state
class=class="str">"cmt">//--- Whenever the system and model state aren&class="macro">#x27;t the same, we may have a potential reversal
   if(forecast > iClose("UK100",PERIOD_CURRENT,class="num">0))
   {
     model_state = class="num">1;
   }
   else
     if(forecast < iClose("UK100",PERIOD_CURRENT,class="num">0))
       {
        model_state = -class="num">1;
       }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update our market data                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void update_market_data(class="type">void)
  {
class=class="str">"cmt">//--- Update the bid and ask prices
   bid = SymbolInfoDouble("UK100",SYMBOL_BID);
   ask = SymbolInfoDouble("UK100",SYMBOL_ASK);
class=class="str">"cmt">//--- Update the RSI readings
   CopyBuffer(rsi_handler,class="num">0,class="num">1,class="num">1,rsi_buffer);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check if we have an opportunity to sell                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void check_sell(class="type">void)
  {
   if(iClose("UK100",PERIOD_W1,class="num">0) < iClose("UK100",PERIOD_W1,class="num">12))
   {
     if(rsi_buffer[class="num">0] < class="num">50)
       {
        Trade.Sell(class="num">0.3,"UK100",bid,class="num">0,class="num">0,"FTSE class="num">100 AI");
        class=class="str">"cmt">//--- Remeber the ticket
        open_ticket = PositionGetTicket(class="num">0);

◍ UK100周线突破配合RSI的多头触发

这段逻辑只盯一个品种:UK100 的周线。当本周收盘价高于 12 周前的周收盘价,且 RSI 缓冲值大于 50,系统倾向认为多头动能占优,随即以 0.3 手买入并记下持仓 ticket,同时把 system_state 置为 1,代表模型状态与系统状态出现同向偏离。 初始化阶段先装载 RSI 指标句柄,若返回 INVALID_HANDLE 则直接 INIT_FAILED 并在图表上提示加载失败;随后调用 fetch_training_data 与 model_fit 去拉训练数据和拟合多元线性回归模型。如果用户既关掉 ai_auto_close 又把 profit_target 设成 0,初始化会拒绝启动并提示必须二选一。 别把周线信号当圣旨 UK100 属于高波动指数差价合约,外汇/贵金属类杠杆品种同样风险极高,周线级信号滞后明显,12 周窗口只是历史统计偏好,实盘可能连续假突破。建议先在 MT5 策略测试器用 2020—2024 年数据跑一遍,看 UK100 在 RSI>50 且周线收高于 12 周前时的胜率再做参数微调。

MQL5 / C++
class="type">void check_buy(class="type">void)
  {
   if(iClose("UK100",PERIOD_W1,class="num">0) > iClose("UK100",PERIOD_W1,class="num">12))
     {
      if(rsi_buffer[class="num">0] > class="num">50)
        {
         Trade.Buy(class="num">0.3,"UK100",ask,class="num">0,class="num">0,"FTSE class="num">100 AI");
         class=class="str">"cmt">//--- Remeber the ticket
         open_ticket = PositionGetTicket(class="num">0);
         class=class="str">"cmt">//--- Whenever the system and model state aren&class="macro">#x27;t the same, we may have a potential reversal
         system_state = class="num">1;
        }
     }
  }

class="type">int OnInit()
  {
class=class="str">"cmt">//--- Prepare the technical indicator
   rsi_handler = iRSI(Symbol(),PERIOD_CURRENT,rsi_period,PRICE_CLOSE);
class=class="str">"cmt">//--- Validate the indicator handler
   if(rsi_handler == INVALID_HANDLE)
     {
      Comment("Failed to load the RSI indicator");
      class="kw">return(INIT_FAILED);
     }
class=class="str">"cmt">//--- This function will fetch our training data and scaling factors
   fetch_training_data();
class=class="str">"cmt">//--- This function will fit our multiple linear regression model
   model_fit();
class=class="str">"cmt">//--- Ensure the user&class="macro">#x27;s inputs are valid
   if((ai_auto_close == false && profit_target == class="num">0))
     {
      Comment("Either set AI auto close true, or define a profit target!")
      class="kw">return(INIT_FAILED);
     }
class=class="str">"cmt">//--- Everything went well
   class="kw">return(INIT_SUCCEEDED);
  }

「把 AI 信号接进 UK100 的开关逻辑」

EA 的主循环放在 OnTick,每次报价刷新都先强制把 UK100(FTSE100 差价合约)选入市场观察表,否则后续取价和下单可能落空。接着依次跑更新行情、模型预测、图表 Comment 反馈三步,把预测值和持仓浮盈直接打印在右上角,方便肉眼交叉验证。 无持仓时,open_ticket 置 0,仅当 model_state 为 1 才探 buy、为 -1 才探 sell;这个状态机把开仓完全绑在模型输出上,人工不干预。 持仓后分三条退出路径:纯手动止盈(ai_auto_close=false 且 profit_target>0,浮盈超阈值才平);混合平仓(ai_auto_close=true,浮盈达标或模型状态翻转即平);纯 AI 接管(无 profit_target 且状态背离就平)。外汇与贵金属之外,指数 CFD 同样带高杠杆高风险,回测前先在策略测试器用 2023 年 UK100 数据跑一遍,确认 model_state 翻转平仓的触发次数在可接受频率内。 让小布替你跑这套 把上面三段退出逻辑直接拷进 MT5 的 EA 模板,把 profit_target 从 0 改成 50,观察混合模式下的平仓次数是否比纯 AI 模式少三成左右,再决定实盘参数。

MQL5 / C++
class="type">void OnTick()
  {
class=class="str">"cmt">//--- Since we are dealing with a lot of different symbols, be sure to select the UK1OO(FTSE100)
class=class="str">"cmt">//--- Select the symbol
   SymbolSelect("UK100",true);
class=class="str">"cmt">//--- Update market data
   update_market_data();
class=class="str">"cmt">//--- Fetch a prediction from our AI model
   model_predict();
class=class="str">"cmt">//--- Give the user feedback
   Comment("Model forecast: ",forecast,"\nPosition Profit: ",position_profit);
class=class="str">"cmt">//--- Look for a position
   if(PositionsTotal() == class="num">0)
     {
      class=class="str">"cmt">//--- We have no open positions
      open_ticket = class="num">0;
      class=class="str">"cmt">//--- Check if our model&class="macro">#x27;s prediction is validated
      if(model_state == class="num">1)
        {
         check_buy();
        }
      else
       if(model_state == -class="num">1)
         {
          check_sell();
         }
     }
class=class="str">"cmt">//--- Do we have a position allready?
   if(PositionsTotal() > class="num">0)
     {
      class=class="str">"cmt">//--- Should we close our positon manually?
      if(PositionSelectByTicket(open_ticket))
        {
         if((profit_target > class="num">0) && (ai_auto_close == false))
           {
            class=class="str">"cmt">//--- Update the position profit
            position_profit = PositionGetDouble(POSITION_PROFIT);
            if(profit_target < position_profit)
              {
               Trade.PositionClose("UK100");
              }
           }
        }
      class=class="str">"cmt">//--- Should we close our positon using a hybrid approach?
      if(PositionSelectByTicket(open_ticket))
        {
         if((profit_target > class="num">0) && (ai_auto_close == true))
           {
            class=class="str">"cmt">//--- Update the position profit
            position_profit = PositionGetDouble(POSITION_PROFIT);
            class=class="str">"cmt">//--- Check if we have passed our profit target or if we are expecting a reversal
            if((profit_target < position_profit) || (model_state != system_state))
              {
               Trade.PositionClose("UK100");
              }
           }
        }
      class=class="str">"cmt">//--- Are we closing our system just using AI?
      else
       if((system_state != model_state) &&
          (ai_auto_close == true) &&
          (profit_target == class="num">0))
         {
把特征缩放交给小布盯盘
Z标准化和12输入矩阵的准备属于重复劳动,小布盯盘的AIGC已内置这类诊断,打开对应品种页即可看到规范化后的输入状态,你只需调模型步长与参数。

常见问题

线性回归能对输入做外推,给出未来数值;树模型按分组返回均值,无前瞻性,相似输入会给出相同预测,不适合多步预测任务。
脚本阶段能理清全局变量、数据获取与矩阵存储的协同逻辑,降低直接在EA里调试复杂模型的成本,便于动态调整时间框架。
可以,小布盯盘内置了输入规范化与品种诊断的AIGC模块,能替你跑Z标准化与大盘股表现快照,你专注模型结构与步长设定即可。
作为12个输入的一部分,大盘股相对权重与波动会传导到指数,纳入当前收盘价联合建模可能提升外推倾向,但外汇贵金属联动仍带高风险。
需动态重算均值标准差做Z标准化,并确认预测步长与框架匹配,否则跨周期输入分布偏移会让外推结果失真概率上升。