重构MQL5中的经典策略(第三部分):富时100指数预测·进阶篇
(2/3)·从LSE百年蓝筹到20步外推,多数人在树模型里把前瞻能力自己删掉了
◍ 搭一个带线性回归预测的 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 用策略测试器跑一遍再谈。
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 的冗余列再拟合。
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 策略测试器用历史数据验证该状态机。
(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 周前时的胜率再做参数微调。
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 模式少三成左右,再决定实盘参数。
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)) {