在 MQL5 中重新构想经典策略(第二部分):富时 100 指数(FTSE100)与英国国债(UK Gilts)·进阶篇
📊

在 MQL5 中重新构想经典策略(第二部分):富时 100 指数(FTSE100)与英国国债(UK Gilts)·进阶篇

(2/3)· 当股市与国债的跷跷板遇上机器学习,散户最缺的不是策略而是验证框架

偏理论 第 2/3 篇

不少交易者把 FTSE100 和英国国债当成简单的避险切换标的,却说不清两者收益率反向关系的临界点在哪里。靠肉眼翻财经新闻做股债轮动,往往踩错节奏还以为是运气差。这一篇先把两个市场的底层逻辑拆开,再看算法怎么从数据里长出自己的规则。

「把跨品种数据塞进回归矩阵并做标准化」

做跨资产回归预测时,第一步是把截距项和各品种的历史行向量拼成输入矩阵。下面这段逻辑把英国金边债券(UKGB)和英国100指数(UK100)各取4行,分别写入 input_matrix 的第1到第8行,第0行留给截距,矩阵共9列、行数随样本走。 拼完矩阵别急着算系数,先用 Mean(1) 和 Std(1) 按行求均值与标准差,再遍历前8行做 (x-mean)/std 的 z-score 标准化。外汇与贵金属跨品种建模同理,标准化能压住量纲差异,否则英镑波动和金价波动直接相加会扭曲权重。 标准化后系数用 target.MatMul(input_matrix.PInv()) 拿伪逆求解,相当于跑了一版闭式多元线性回归。实盘前建议把 Print 输出的 mean_values、std_values、coefficients 贴到 MT5 专家日志里核对,确认没有 NaN 或数量级异常。 预测阶段要实时拉新棒:gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,0,1) 只取1根,再手动用训练期的 mean/std 缩放新数据。注意这里硬编码了索引 1~4 对应债券四维,换品种或加特征必须同步改下标,否则预测会 silently 错位。

MQL5 / C++
class=class="str">"cmt">//--- Fill in the input matrix
  input_matrix.Row(intercept,class="num">0);
  input_matrix.Row(gilts_data.Row(class="num">0),class="num">1);
  input_matrix.Row(gilts_data.Row(class="num">1),class="num">2);
  input_matrix.Row(gilts_data.Row(class="num">2),class="num">3);
  input_matrix.Row(gilts_data.Row(class="num">3),class="num">4);
  input_matrix.Row(uk100_data.Row(class="num">0),class="num">5);
  input_matrix.Row(uk100_data.Row(class="num">1),class="num">6);
  input_matrix.Row(uk100_data.Row(class="num">2),class="num">7);
  input_matrix.Row(uk100_data.Row(class="num">3),class="num">8);
class=class="str">"cmt">//--- Display the data fetched
  Print("Input matrix: ");
  Print("Rows: ",input_matrix.Rows()," Columns: ",input_matrix.Cols());
  Print(input_matrix);
  Print("Target: ");
  Print("Rows: ",target.Rows()," Columns: ",target.Cols());
  Print(target);
  Print("UK100: ");
  Print("Rows: ",uk100_data.Rows()," Columns: ",uk100_data.Cols());
  Print(uk100_data);
  Print("GILTS: ");
  Print("Rows: ",gilts_data.Rows()," Columns: ",gilts_data.Cols());
  Print(gilts_data);
class=class="str">"cmt">//--- Calculate the scaling values
  mean_values = input_matrix.Mean(class="num">1);
  std_values = input_matrix.Std(class="num">1);
  Print("Mean values: ");
  Print(mean_values);
  Print("Std values: ");
  Print(std_values);
class=class="str">"cmt">//--- Normalizing and scaling our input data
  for(class="type">int i = class="num">0; i < class="num">8; i++)
    {
    class=class="str">"cmt">//--- Extract the vector
      vector temp = input_matrix.Row(i + class="num">1);
    class=class="str">"cmt">//--- Scale the data
      temp = ((temp - mean_values[i+class="num">1]) / std_values[i+class="num">1]);
    class=class="str">"cmt">//--- Write the data back
      input_matrix.Row(temp,i+class="num">1);
    }
class=class="str">"cmt">//--- Finished normalizing the data
  Print("Finished normalizing the data.");
  Print(input_matrix);
class=class="str">"cmt">//--- Now we can calculate our coefficient values
  coefficients = target.MatMul(input_matrix.PInv());
  Print("Coefficient values");
  Print(coefficients);
class=class="str">"cmt">//--- Now we can obtain a forecast from our model
  gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,class="num">0,class="num">1);
  uk100_data.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_OHLC,class="num">0,class="num">1);
class=class="str">"cmt">//--- Scale our inputs
  gilts_data[class="num">0,class="num">0] = ((gilts_data[class="num">0,class="num">0] - mean_values[class="num">1]) / std_values[class="num">1]);
  gilts_data[class="num">1,class="num">0] = ((gilts_data[class="num">1,class="num">0] - mean_values[class="num">2]) / std_values[class="num">2]);
  gilts_data[class="num">2,class="num">0] = ((gilts_data[class="num">2,class="num">0] - mean_values[class="num">3]) / std_values[class="num">3]);
  gilts_data[class="num">3,class="num">0] = ((gilts_data[class="num">3,class="num">0] - mean_values[class="num">4]) / std_values[class="num">4]);

◍ 把原始序列压成标准分再喂进线性模型

这段逻辑干的事很直白:先把 UK100 的四个滞后项各自减去对应均值再除以标准差,转成 z-score。索引 0~3 分别对应 mean_values[5]~[8] 和 std_values[5]~[8],说明前面已经算好了至少 9 组均值与标准差,UK100 占后 4 组。 归一化之后用 Print 把 gilts_data 和 uk100_data 整个矩阵打到日志,方便你核对标准化有没有把量纲拉爆。这一步在 MT5 策略测试器里能直接看,不用等实盘。 forecast 是一个手写线性组合:截距系数 coefficients[0,0] 乘 1,其余项用 gilts 的 0~3 滞后、uk100 的第 0 滞后、以及 gilts 的 1~3 滞后再乘各自系数累加。注意 uk100 只用了滞后 0,而 gilts 重复用了 0/1/2/3 多次,权重结构明显偏向债市变量。 最后 Comment 把模型输出贴在图表左上角。外汇与贵金属叠加这类跨资产线性模型时波动可能被放大,属于高风险用法,信号只代表统计上的倾向而非确定方向。

MQL5 / C++
  uk100_data[class="num">0,class="num">0] = ((uk100_data[class="num">0,class="num">0] - mean_values[class="num">5]) / std_values[class="num">5]);
  uk100_data[class="num">1,class="num">0] = ((uk100_data[class="num">1,class="num">0] - mean_values[class="num">6]) / std_values[class="num">6]);
  uk100_data[class="num">2,class="num">0] = ((uk100_data[class="num">2,class="num">0] - mean_values[class="num">7]) / std_values[class="num">7]);
  uk100_data[class="num">3,class="num">0] = ((uk100_data[class="num">3,class="num">0] - mean_values[class="num">8]) / std_values[class="num">8]);
  Print("Normalized inputs: ");
  Print(gilts_data);
  Print(uk100_data);
  class="type">class="kw">double forecast = (
                    (class="num">1 * coefficients[class="num">0,class="num">0]) +
                    (gilts_data[class="num">0,class="num">0] * coefficients[class="num">0,class="num">1]) +
                    (gilts_data[class="num">1,class="num">0] * coefficients[class="num">0,class="num">2]) +
                    (gilts_data[class="num">2,class="num">0] * coefficients[class="num">0,class="num">3]) +
                    (gilts_data[class="num">3,class="num">0] * coefficients[class="num">0,class="num">4]) +
                    (uk100_data[class="num">0,class="num">0] * coefficients[class="num">0,class="num">5]) +
                    (gilts_data[class="num">1,class="num">0] * coefficients[class="num">0,class="num">6]) +
                    (gilts_data[class="num">2,class="num">0] * coefficients[class="num">0,class="num">7]) +
                    (gilts_data[class="num">3,class="num">0] * coefficients[class="num">0,class="num">8])
                     );
class=class="str">"cmt">//--- Give our predictions
  Comment("Model forecast: ",forecast);

把线性回归塞进EA骨架

搭EA第一步不是写交易逻辑,而是把交易库挂进来。引入 <Trade/Trade.mqh> 并实例化 CTrade 后,你才有办法在后续函数里直接下单、平仓、查持仓。没有这层,后面所有预测都只是纸上数字。 输入参数只留两个就够用:fetch 控制抓取多少根历史数据(默认20),look_ahead 控制模型往前预测多少根(默认20)。这两个值直接决定训练样本量和预测 horizon,调参时先动它们比动模型结构更直观。 全局变量里有个细节容易忽略:coefficients 是 1×9 矩阵,mean_values 和 std_values 都是长度8的向量。这说明标准化针对8个特征,而模型系数额外含了截距项。用 matrix/vector 而不是裸数组,是因为后面要直接调 MQL5 的矩阵代数函数做线性回归,别手写循环。 初始化流程是串起来的:抓训练数据 → 缩放标准化 → 算系数 → 挂 Williams %R 和 RSI 并验证句柄有效。EA 从图表卸载时,必须释放指标句柄并自行去初始化,否则 MT5 会留垃圾资源。 每来一个新报价,先刷新市场数据,再让模型出预测。无持仓时,只有当 AI 预测和技术指标同向才开仓;已有持仓时,若模型预测反转就平掉。外汇和贵金属波动剧烈,这种双确认也只是提高概率,不等于规避风险。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 UK100 Gilts.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">//|Libraries we need                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade/Trade.mqh>           class=class="str">"cmt">//Trade class
CTrade Trade;                        class=class="str">"cmt">//Initialize the class
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Inputs                                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">int fetch = class="num">20;                class=class="str">"cmt">//How much data should we fetch?
input class="type">int look_ahead = class="num">20;           class=class="str">"cmt">//How far into the future should we forecast?
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global vairables                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
matrix coefficients = matrix::Zeros(class="num">1,class="num">9);
vector mean_values = vector::Zeros(class="num">8);
vector std_values = vector::Zeros(class="num">8);
vector intercept = vector::Ones(fetch);
matrix input_matrix = matrix::Zeros(class="num">9,fetch);
matrix gilts_data,uk100_data,target;
class="type">class="kw">double willr_buffer[],rsi_buffer[];
class="type">int willr_handler,rsi_handler;
class="type">class="kw">double forecast,bid,ask;
class="type">int model_forecast = class="num">0;
class="type">int state = class="num">0;

「把金边债与UK100的行情喂进训练矩阵」

做跨品种回归前,先得把英国金边债(UKGB_Z4)和英国100指数(UK100)的 OHLC 拉进内存。下面这段函数用 CopyRates 取数,look_ahead 控制向前看几根 K 线,fetch 是已定义的抓取深度;目标列只取 UK100 的收盘价,行数为 1+look_ahead,和输入矩阵对齐。 输入矩阵按行排布:第 0 行放截距项,第 1~4 行放金边债最近 4 根 K 线,第 5~8 行放 UK100 最近 4 根 K 线,共 9 行。Print 会把矩阵维度(Rows/Cols)和原始数值打到日志,开 MT5 按 F4 编译后跑一遍,能在专家日志里直接核对数据有没有串列。 光取数不够,不同品种量纲差太大,必须标准化。scale_training_data 里对第 1~8 行做 (x - mean)/std 处理,mean_values 和 std_values 由 input_matrix.Mean(1) 和 Std(1) 按列算出;循环写死 i<8,对应那 8 个特征行。标准化后日志会再打印一次矩阵,你可以对比处理前后数值,确认均值倾向收敛到 0 附近。 外汇与贵金属跨界参考这类利率—股指联动模型时,杠杆和跳空风险极高,回测结论只代表历史样本内的统计关系,实盘可能失效。

MQL5 / C++
class="type">void fetch_training_data(class="type">void)
  {
class=class="str">"cmt">//--- First we will fetch the market data
   gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,class="num">1+look_ahead,fetch);
   uk100_data.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_OHLC,class="num">1+look_ahead,fetch);
   target.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_CLOSE,class="num">1,fetch);
class=class="str">"cmt">//--- Fill in the input matrix
   input_matrix.Row(intercept,class="num">0);
   input_matrix.Row(gilts_data.Row(class="num">0),class="num">1);
   input_matrix.Row(gilts_data.Row(class="num">1),class="num">2);
   input_matrix.Row(gilts_data.Row(class="num">2),class="num">3);
   input_matrix.Row(gilts_data.Row(class="num">3),class="num">4);
   input_matrix.Row(uk100_data.Row(class="num">0),class="num">5);
   input_matrix.Row(uk100_data.Row(class="num">1),class="num">6);
   input_matrix.Row(uk100_data.Row(class="num">2),class="num">7);
   input_matrix.Row(uk100_data.Row(class="num">3),class="num">8);
class=class="str">"cmt">//--- Display the data fetched
   Print("Input matrix: ");
   Print("Rows: ",input_matrix.Rows()," Columns: ",input_matrix.Cols());
   Print(input_matrix);
   Print("Target: ");
   Print("Rows: ",target.Rows()," Columns: ",target.Cols());
   Print(target);
   Print("UK100: ");
   Print("Rows: ",uk100_data.Rows()," Columns: ",uk100_data.Cols());
   Print(uk100_data);
   Print("GILTS: ");
   Print("Rows: ",gilts_data.Rows()," Columns: ",gilts_data.Cols());
   Print(gilts_data);
class=class="str">"cmt">//--- Calculate the scaling values
   mean_values = input_matrix.Mean(class="num">1);
   std_values = input_matrix.Std(class="num">1);
   Print("Mean values: ");
   Print(mean_values);
   Print("Std values: ");
   Print(std_values);
  }
class="type">void scale_training_data(class="type">void)
  {
class=class="str">"cmt">//--- Normalizing and scaling our input data
   for(class="type">int i = class="num">0; i < class="num">8; i++)
     {
      class=class="str">"cmt">//--- Extract the vector
      vector temp = input_matrix.Row(i + class="num">1);
      class=class="str">"cmt">//--- Scale the data
      temp = ((temp - mean_values[i+class="num">1]) / std_values[i+class="num">1]);
      class=class="str">"cmt">//--- Write the data back
      input_matrix.Row(temp,i+class="num">1);
     }
class=class="str">"cmt">//--- Finished normalizing the data
   Print("Finished normalizing the data.");
   Print(input_matrix);
  }
让小布替你跑这套对照
这两个市场的股债资金流向诊断,小布盯盘已内置对应品种页,打开即可看到 AI 对 FTSE100 与 UK Gilts 相对强弱的实时标注,把重复劳动交给小布,你专注决策。

常见问题

大公司按市值占更高权重,少量龙头的利空就会拖累整体指数,小公司异动常被稀释,做均值回归时要先看清权重结构。
其票息随通胀浮动导致现金流不可预测,固定收益投资者更难做久期匹配,因此流动性弱于传统国债。
小布盯盘的品种页会呈现两类资产的相对强弱与资金偏好标注,但是否切换仓位仍由你结合风险承受力判断,外汇贵金属类联动同样属高风险。
价格与收益率反向,买盘推高债券价格后,固定票息对应的到期收益率自然被压缩,这是供需定价而非违约预警。