在 MQL5 中重新构想经典策略(第二部分):富时 100 指数(FTSE100)与英国国债(UK Gilts)·综合运用
(3/3)·从指数与国债的跷跷板关系到算法自学规则,收尾篇把前面两篇拆开的零件拼成可跑的系统
◍ 系数求解与实时预测取数
线性回归模型跑完最小二乘之后,核心就是拿系数矩阵去乘输入。下面这段代码先通过伪逆矩阵解出 coefficients,再把金边债与 UK100 的行情拉进来做标准化预测。
calculate_coefficient_values 里只用一行 coefficients = target.MatMul(input_matrix.PInv()) 就完成了系数求解,随后 Print 出矩阵内容供肉眼核对。注意 PInv() 是 Moore-Penrose 伪逆,当输入矩阵列满秩时等价于普通最小二乘解。
fetch_forecast 先 CopyRates 取 UKGB_Z4 和 UK100 各 1 根 K 线的 OHLC,再用 mean_values / std_values 做 z-score 标准化。标准化后每个字段减均值除标准差,索引从 1 到 8 对应 8 个输入特征(4 个金边债 + 4 个 UK100)。
预测值由常数项 coefficients[0,0] 与各标准化特征乘对应系数累加得到,代码里只写到 uk100_data[0,0] 那一项,后面还有 3 个 UK100 特征项未列全。在 MT5 里把这段补完并打印 forecast,能直接看到模型对下一刻目标变量的点估计——外汇与贵金属联动建模同理,但杠杆品种波动剧烈,回测漂移风险高。
class="type">void calculate_coefficient_values(class="type">void) { class=class="str">"cmt">//--- Now we can calculate our coefficient values coefficients = target.MatMul(input_matrix.PInv()); Print("Coefficient values"); Print(coefficients); } class="type">void fetch_forecast(class="type">void) { 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_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=class="str">"cmt">//--- Calculate the model&class="macro">#x27;s prediction 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]) +
「预测落单与指标阈值怎么接」
模型算完 forecast 之后,代码把它和 UK100 当前收盘价比大小:低于收盘价就把 model_forecast 置 -1,高于则置 1,并在图表用 Comment 把数值直接打印出来,方便你肉眼核对信号方向。 update_market_data 负责把盘口和两项技术面拉进缓冲区:用 SymbolInfoDouble 取 UK100 的 bid / ask,再用 CopyBuffer 把 RSI 与 Williams %R 的最新一根柱值分别写进 rsi_buffer、willr_buffer,后续判断全靠这两个数组的 [0] 元素。 check_bullish_sentiment 的触发条件是 willr_buffer[0] > -20 且 rsi_buffer[0] > 70,满足就以 0.2 手买 UK100,止损止盈各挂 5 点,魔术码标为 "UK100 Gilts AI",state 置 1。 卖侧函数 check_bearish_sentiment 里有个明显笔误:rsi_buffer[0] < 370 实际永远成立(RSI 上限 100),原意大概率是 < 30;真要跑这套逻辑,得先把这个常量改对,否则空头信号会被错误放大。外汇与指数 CFD 杠杆高,这类 EA 信号仅作概率参考,实盘前务必在 MT5 策略测试器回测。 OnInit 把流程串起来:先 fetch_training_data 取训练样本,再 scale_training_data 做归一,随后 calculate_coefficient_values 算出回归系数,最后初始化指标句柄,EA 启动即具备预测与下单能力。
(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">//--- Store the model&class="macro">#x27;s prediction if(forecast < iClose("UK100",PERIOD_CURRENT,class="num">0)) { model_forecast = -class="num">1; } if(forecast > iClose("UK100",PERIOD_CURRENT,class="num">0)) { model_forecast = class="num">1; } class=class="str">"cmt">//--- Give the user feedback Comment("Model forecast: ",forecast); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| This function will fetch current market data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void update_market_data(class="type">void) { class=class="str">"cmt">//--- Market prices bid = SymbolInfoDouble("UK100",SYMBOL_BID); ask = SymbolInfoDouble("UK100",SYMBOL_ASK); class=class="str">"cmt">//--- Technical indicators CopyBuffer(rsi_handler,class="num">0,class="num">0,class="num">1,rsi_buffer); CopyBuffer(willr_handler,class="num">0,class="num">0,class="num">1,willr_buffer); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| This function will check if we have oppurtunities to buy | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void check_bullish_sentiment(class="type">void) { if((willr_buffer[class="num">0] > -class="num">20) && (rsi_buffer[class="num">0] > class="num">70)) { Trade.Buy(class="num">0.2,"UK100",ask,ask-class="num">5,ask+class="num">5,"UK100 Gilts AI"); state = class="num">1; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| This function will check if we have oppurtunities to sell | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void check_bearish_sentiment(class="type">void) { if((willr_buffer[class="num">0] < -class="num">80) && (rsi_buffer[class="num">0] <class="num">370)) { Trade.Sell(class="num">0.2,"UK100",ask,ask-class="num">5,ask+class="num">5,"UK100 Gilts AI"); state = -class="num">1; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Fetch the training data fetch_training_data(); class=class="str">"cmt">//--- Scale the training data scale_training_data(); class=class="str">"cmt">//--- Calculate the coefficients calculate_coefficient_values(); class=class="str">"cmt">//--- Setup the indicators
指标句柄校验与 tick 内的模型调度
在 UK100 的 EA 初始化里,RSI 取 20 周期收盘价、WPR 取 30 周期,两个句柄任一返回 INVALID_HANDLE 就直接 Comment 报错并 return(INIT_FAILED),避免后续空句柄调用崩在半路。 OnDeinit 里用 IndicatorRelease 依次释放 willr_handler 与 rsi_handler,再调 ExpertRemove 把 EA 从图表摘掉,防止残留指标占用终端资源。 OnTick 的节奏很清晰:先 update_market_data() 拉最新盘口,再 fetch_forecast() 取模型信号。无持仓时 model_forecast == 1 走 check_bullish_sentiment(),== -1 走 check_bearish_sentiment()。 已有持仓且 model_forecast 不等于 state 时,系统弹 Alert 提示反转并 Trade.PositionClose("UK100") 平掉。这套结构可直接在 MT5 挂 UK100 图表验证:把 RSI 周期从 20 改成 14,观察 INIT_FAILED 触发频率是否下降。外汇与贵金属品种波动剧烈,信号反转可能瞬时扫损,实盘前务必在策略测试器跑历史数据。
rsi_handler = iRSI("UK100",PERIOD_CURRENT,class="num">20,PRICE_CLOSE); willr_handler = iWPR("UK100",PERIOD_CURRENT,class="num">30); class=class="str">"cmt">//--- Validate the technical indicators if((rsi_handler == INVALID_HANDLE) || (willr_handler == INVALID_HANDLE)) { Comment("Failed to load indicators. ",GetLastError()); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- Everything went well class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- Free up the resoruces we don&class="macro">#x27;t need IndicatorRelease(willr_handler); IndicatorRelease(rsi_handler); ExpertRemove(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- Fetch updated market data update_market_data(); class=class="str">"cmt">//--- Fetch a forecast from our model fetch_forecast(); class=class="str">"cmt">//--- Check for a position we can open if(PositionsTotal() == class="num">0) { if(model_forecast == class="num">1) { check_bullish_sentiment(); } else if(model_forecast == -class="num">1) { check_bearish_sentiment(); } } class=class="str">"cmt">//--- Check for a reversal else if(PositionsTotal() > class="num">0) { if(model_forecast != state) { Alert("Reversal detected by our AI system! Closing all positions now."); Trade.PositionClose("UK100"); } } }
◍ 切换周期不用重训模型才是真省事
这套自优化 EA 在 MQL5 里落地的关键收益很直接:改时间框架不需要重新校准程序。对照用 ONNX 的实现,每换一个想交易的时间周期,就至少得备一个独立模型,维护成本随周期数线性上涨。 现有方案靠内部自我优化机制跑通了多条件适应,不绑定单一周期参数。外汇与贵金属杠杆高、滑点和跳空频繁,这种免重训特性只降低工程负担,不抵消品种本身的高风险。 评论区有读者指出预测计算里第 7、8、9 个数据误用了 guilts 数据而非 UK100,作者确认是人为调用错误并已修正。真要上 MT5 跑 UK100_Gilts.mq5,先核对那段数据引用再实盘模拟。