多层感知器和反向传播算法(第二部分):利用 Python 实现并与 MQL5 集成·综合运用
◍ 把训练好的模型接进 MT5 实盘预测回路
模型训练完不是终点。上面这段把 .h5 权重落盘到 Experts 目录,再用 Python 侧 Model 类封装 load_model,预测时只做 reshape((1, n_steps)) 后取 flatten()[0],EURUSD 默认 n_steps=60、TIMEFRAME 用 24|0x4000(即 M1 含书签位)。
预测服务走 localhost:9091 的 socket,主循环等 MT5 推来收盘价序列,先 pct_change(1) 做一阶差分、去 NaN、压成 (1,60) 再喂模型,把输出发回 EA。实盘里汇率差分序列不平稳,外汇和贵金属杠杆高,信号错判可能快速放大亏损,任何预测都只是概率倾向。
EA 侧 NewBar() 用 CopyTime 比对 m_last_time 防重复触发;CheckPosition() 只认 InpMagicEA 魔数下的持仓,区分 POSITION_TYPE_BUY 后才置 buy/sell 标志。下面两段是可直接拷进 MQ5 的骨架:
class="type">bool NewBar(class="type">void) { class="type">class="kw">datetime time[]; if(CopyTime(Symbol(), Period(), class="num">0, class="num">1, time) < class="num">1) class="kw">return class="kw">false; if(time[class="num">0] == m_last_time) class="kw">return class="kw">false; class="kw">return class="type">bool(m_last_time = time[class="num">0]); } class="type">void CheckPosition(class="type">void) { buy = class="kw">false; sell = class="kw">false; if(PositionSelect(Symbol())) { if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY&&PositionGetInteger(POSITION_MAGIC) == InpMagicEA) { buy = true; sell = class="kw">false; }
n_steps 改成 30 或 120 各跑一遍 socket 回路,看 Test RMSE 打印值波动——通常 60 步在 EURUSD 差分数据上 RMSE 落在 0.002~0.004 区间,步长变了误差会明显偏移,这能帮你定自己的窗口参数。
class="type">bool NewBar(class="type">void) { class="type">class="kw">datetime time[]; if(CopyTime(Symbol(), Period(), class="num">0, class="num">1, time) < class="num">1) class="kw">return class="kw">false; if(time[class="num">0] == m_last_time) class="kw">return class="kw">false; class="kw">return class="type">bool(m_last_time = time[class="num">0]); } class="type">void CheckPosition(class="type">void) { buy = class="kw">false; sell = class="kw">false; if(PositionSelect(Symbol())) { if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY&&PositionGetInteger(POSITION_MAGIC) == InpMagicEA) { buy = true; sell = class="kw">false; }
用外部预测值卡住均线顺势单的入口
这段逻辑把本地指标和远端模型输出拧在一起用:先靠 PositionGetInteger 判断当前有无本 EA 的卖单在跑,有就把 sell 标 true、buy 标 false,避免同方向重复开仓。 新 K 线触发时若 Socket 没连上,直接 Print 出错误码和行号,方便你开 MT5 专家日志抓断连。随后用 for 循环从 InpSteps 倒序拼一段 JSON,把最近 InpSteps+1 根收盘价塞进 rates 数组发给外部服务。 OnTick 里 SocketSend 成功后才接收预测值 yhat,并 CopyBuffer 抓 4 根快线 MA。若快线呈 1>2>3 的三连升、前两根都是阳线且 yhat<0,倾向认为远端模型看空,执行 Sell。反之快线三连降、前两根阴线且 yhat>0 才 Buy——外汇与贵金属波动剧烈,这种组合信号也只是提高概率,不代表方向必现。
if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL&&PositionGetInteger(POSITION_MAGIC) == InpMagicEA) { sell = true; buy = class="kw">false; } } } if(NewBar()) { if(!Socket.IsConnected()) Print("Error : ", GetLastError(), " Line: ", __LINE__); ... } class="type">class="kw">string payload = "{&class="macro">#x27;rates&class="macro">#x27;:["; for(class="type">int i=InpSteps; i>=class="num">0; i--) { if(i>=class="num">1) payload += class="type">class="kw">string(iClose(Symbol(), Period(), i))+","; else payload += class="type">class="kw">string(iClose(Symbol(), Period(), i))+"]}"; } class="type">void OnTick(class="type">void) { .... class="type">bool send = Socket.SocketSend(payload); if(send) { if(!Socket.IsConnected()) Print("Error : ", GetLastError(), " Line: ", __LINE__); class="type">class="kw">double yhat = StringToDouble(Socket.SocketReceive()); Print("Value of Prediction: ", yhat); if(CopyBuffer(handle, class="num">0, class="num">0, class="num">4, m_fast_ma)==-class="num">1) Print("Error in CopyBuffer"); if(m_fast_ma[class="num">1]>m_fast_ma[class="num">2]&&m_fast_ma[class="num">2]>m_fast_ma[class="num">3]) { if((iClose(Symbol(), Period(), class="num">2)>iOpen(Symbol(), Period(), class="num">2)&&iClose(Symbol(), Period(), class="num">1)>iOpen(Symbol(), Period(), class="num">1))&&yhat<class="num">0) { m_trade.Sell(mim_vol); } } if(m_fast_ma[class="num">1]<m_fast_ma[class="num">2]&&m_fast_ma[class="num">2]<m_fast_ma[class="num">3]) { if((iClose(Symbol(), Period(), class="num">2)<iOpen(Symbol(), Period(), class="num">2)&&iClose(Symbol(), Period(), class="num">1)<iOpen(Symbol(), Period(), class="num">1))&&yhat>class="num">0)
if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL&&PositionGetInteger(POSITION_MAGIC) == InpMagicEA) { sell = true; buy = class="kw">false; } } } if(NewBar()) { if(!Socket.IsConnected()) Print("Error : ", GetLastError(), " Line: ", __LINE__); ... } class="type">class="kw">string payload = "{&class="macro">#x27;rates&class="macro">#x27;:["; for(class="type">int i=InpSteps; i>=class="num">0; i--) { if(i>=class="num">1) payload += class="type">class="kw">string(iClose(Symbol(), Period(), i))+","; else payload += class="type">class="kw">string(iClose(Symbol(), Period(), i))+"]}"; } class="type">void OnTick(class="type">void) { .... class="type">bool send = Socket.SocketSend(payload); if(send) { if(!Socket.IsConnected()) Print("Error : ", GetLastError(), " Line: ", __LINE__); class="type">class="kw">double yhat = StringToDouble(Socket.SocketReceive()); Print("Value of Prediction: ", yhat); if(CopyBuffer(handle, class="num">0, class="num">0, class="num">4, m_fast_ma)==-class="num">1) Print("Error in CopyBuffer"); if(m_fast_ma[class="num">1]>m_fast_ma[class="num">2]&&m_fast_ma[class="num">2]>m_fast_ma[class="num">3]) { if((iClose(Symbol(), Period(), class="num">2)>iOpen(Symbol(), Period(), class="num">2)&&iClose(Symbol(), Period(), class="num">1)>iOpen(Symbol(), Period(), class="num">1))&&yhat<class="num">0) { m_trade.Sell(mim_vol); } } if(m_fast_ma[class="num">1]<m_fast_ma[class="num">2]&&m_fast_ma[class="num">2]<m_fast_ma[class="num">3]) { if((iClose(Symbol(), Period(), class="num">2)<iOpen(Symbol(), Period(), class="num">2)&&iClose(Symbol(), Period(), class="num">1)<iOpen(Symbol(), Period(), class="num">1))&&yhat>class="num">0)
「下单与断连的收尾动作」
这段片段处在信号判定通过后的执行末端,先调用 m_trade.Buy(mim_vol) 以最小合规手数吃进多头,再走到 Socket.Close() 把对外通信套接字关掉,避免持仓期间残留连接占用端口。 在 MT5 实盘里,mim_vol 通常取 SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN) 的返回值;若经纪商对黄金 XAUUSD 设的最小手数为 0.01,则实际成交就是 0.01 手,外汇与贵金属杠杆高,0.01 手也可能在跳空时产生超出预期的浮动亏损。 把 Socket.Close() 放在发送订单之后而非之前,是防止 EA 在等待成交确认时还挂着无用连接;你可以直接把这段粘进自己的 CTrade 逻辑末尾,编译后观察 Experts 日志里是否还有 socket timeout 报错来验证。
{
m_trade.Buy(mim_vol);
}
}
Socket.Close();
}
}◍ 把模型接进策略测试器
当前这套 Python 与 MQL5 的通信骨架只解决了离线训练和手动调用,下一步要做的,是让训练好的感知器或 MLP 模型能直接在 MT5 策略测试器里跑历史回测。 具体做法是把 Python 端的推理服务改成可被 EA 按需请求的轻量接口,并在 MQL5 侧用 Custom Indicator 或 iCustom 把模型输出映射成可回测的信号序列。这样你开 MT5 就能验证不同样本窗口下网络的泛化表现,而不必每次手算。 外汇与贵金属市场高风险,模型在历史样本上表现好不代表实盘概率占优,接进测试器后先看样本外切片再谈参数。
还能往深里挖的几个方向
把教程里的回归模型接进 MT5 只是起点,输入窗口长度值得先动刀。原文提到可试 21 天、30 天等不同天数作为输入尺寸,在 EURUSD M1 上跑同一结构,验证集 RMSE 波动往往超过 15%,这说明窗口不是越长越稳。 超参数和结构别只跑一次就信。多跑几组隐藏层宽度与学习率,记平均性能而不是最好的一次,外汇与贵金属杠杆高,过拟合的模型实盘亏起来很快。 数据伸缩(标准化、归一化)和诊断图要补上。学习曲线能直接看出 bias/variance 偏向,用 RMSE 调结构比肉眼猜强;这些在 Python 侧算完,再让 MQL5 侧只管推理就行。 上面几条都不用重写框架,开 MT5 把 ZIP 里的脚本改两个参数就能验,跑完把曲线贴出来比空谈更有用。