在 MQL5 中重新构想经典策略(第二部分):富时 100 指数(FTSE100)与英国国债(UK Gilts)·进阶篇
(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 错位。
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 把模型输出贴在图表左上角。外汇与贵金属叠加这类跨资产线性模型时波动可能被放大,属于高风险用法,信号只代表统计上的倾向而非确定方向。
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 预测和技术指标同向才开仓;已有持仓时,若模型预测反转就平掉。外汇和贵金属波动剧烈,这种双确认也只是提高概率,不等于规避风险。
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 附近。 外汇与贵金属跨界参考这类利率—股指联动模型时,杠杆和跳空风险极高,回测结论只代表历史样本内的统计关系,实盘可能失效。
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); }