神经网络: 智能交易系统自我优化(基础篇)
◍ 让 EA 在 MT5 里自己调权重
- 年 10 月 21 日,一篇阅读量 12,164、获 28 条讨论的帖子里,作者 Jose Miguel Soriano 演示了给智能交易系统接一层神经网络、用历史行情反推参数权重的做法。核心不是预测未来,而是让 EA 在每次重新训练后自行修正入场过滤条件。
这类自优化 EA 的高风险点在于过拟合:若训练窗口过窄,权重会记住噪声而非规律。外汇与贵金属杠杆品种尤其容易在样本外崩坏,建议先用 MT5 的初步测试跑 3 个月Tick 数据观察稳定性。 打开 MT5 按 F4 进 MQEditor,把下面这段当成最小验证骨架:先读近 500 根收盘价,归一化后送进一个单隐藏层感知机,再让主循环用输出值偏移原信号阈值。能跑通就说明你的终端具备自优化改造基础。
「从固定参数到自我优化的岔路口」
EA 写完只是第一步。真正能让你前功尽弃的有两件事:一是当前这套输入参数到底合不合适,二是这些参数能撑多久——什么时候该重新跑优化。 除了品种、时间帧这类写死的前提,指标周期、开平仓阈值、止盈止损点位都是可改的设置,每一个都能在实盘里变成坑。 社区里早有人聊过「MQL + 神经网络」,但多数做法要么手工往外部网络喂数据,要么拿 MT4/MT5 优化器硬算,最后只是把 EA 输入换成了网络输出。本文不写具体机器人,而是用 MQL5 配合 ALGLIB 的多层感知器(MLP)搭一个模块化框架,让网络先去解两个数学任务——结果能用别的算法交叉验证,据此摸清 MLP 内部结构再反推 EA 实现准则。 这套布局的目标就是自我优化:假定你已经懂神经网络基础(层数、神经元数、连接与权重),缺的补齐即可,不在这里复述理论。
NN 训练闭环怎么在 EA 里自转
搭建一个神经网络只是起点。先把输入和对应的输出准备好,灌进数组,再在选定区间内做归一化,常见区间是 [0,1] 或 [-1,1],这一步直接决定后续梯度的数值稳定性。 训练加调参跑完之后,网络给出预测,EA 按自己的进出场逻辑去消费这个结果。真正麻烦的是让模型别一次性训死。 自我优化靠回到数据准备那一步,在 OnTimer() 里把迭代循环包起来。机器人按用户设的时间窗周期性重训,人不用盯。 注意一个细节:第 5 步(按策略应用预测)不在重复处理里。也就是说优化期 EA 手里一直有上一轮预测值可用,不会空仓干等。 想验证很简单:开 MT5 建个 EA 骨架,OnTimer 里只放前 4 步加第 2 步回跳,跑一周看日志里权重更新频次是否和你设的定时器周期对得上。外汇与贵金属波动剧烈,模型过拟合风险高,周期重训也可能赶不上跳空。
◍ 在 MT5 里直接调 ALGLIB 做神经网络
ALGLIB 是一套跨平台数值计算库,覆盖线性代数、插值、优化、FFT、统计与数据分析等模块,MT5 自带的封装位于 MQL5\Include\Math\Alglib\,程序里用 #include <Math\Alglib\alglib.mqh> 即可挂载。对交易者最实用的是 dataanalysis.mqh 里的 CMLPBase / CMLPTrain / CMLPE,它们把多层感知器和神经网络集合直接做成了可调用类。 下载库后,测试用例 testclasses.mq5、testinterfaces.mq5 及同名 .mqh 要放进 \MQL5\Scripts\Alglib\Testcases\,另附 usealglib.mq5 演示脚本,跑一遍就能确认环境是否通。 建网靠 MLPCreate 系列:MLPCreate0/1/2 出线性输出网络,MLPCreateR0~R2 限定输出在 [a,b],MLPCreateC0~C2 做分类输出(如 0/1 或 -1/0/1)。层数由 nin、nout、nhid1、nhid2 四个参数定,网络对象用 CMultilayerPerceptronShell,训练数据塞进 CMatrixDouble 类的 xy。 训练别用老式反向传播,库里的 MLPTrainLM()(Levenberg-Marquardt)和 MLPTrainLBFGS()(L-BFGS)效率更高。注意:当网络权重超过 500 个连接时,MLPTrainLM 会弹“网络拥有数百个权重”的警告,此时切到 MLPTrainLBFGS 更稳。外汇与贵金属波动剧烈,模型信号仅作概率参考,实盘前务必在 MT5 策略测试器验证。
class="macro">#include <Math\Alglib\alglib.mqh> class=class="str">"cmt">//--- 创建神经网络 class="kw">static class="type">void MLPCreate0(class="kw">const class="type">int nin,class="kw">const class="type">int nout,CMultilayerPerceptronShell &network); class="kw">static class="type">void MLPCreate1(class="kw">const class="type">int nin,class="type">int nhid,class="kw">const class="type">int nout,CMultilayerPerceptronShell &network); class="kw">static class="type">void MLPCreate2(class="kw">const class="type">int nin,class="kw">const class="type">int nhid1,class="kw">const class="type">int nhid2,class="kw">const class="type">int nout,CMultilayerPerceptronShell &network); class="kw">static class="type">void MLPCreateR0(class="kw">const class="type">int nin,class="kw">const class="type">int nout,class="type">class="kw">double a,class="kw">const class="type">class="kw">double b,CMultilayerPerceptronShell &network); class="kw">static class="type">void MLPCreateR1(class="kw">const class="type">int nin,class="type">int nhid,class="kw">const class="type">int nout,class="kw">const class="type">class="kw">double a,class="kw">const class="type">class="kw">double b,CMultilayerPerceptronShell &network); class="kw">static class="type">void MLPCreateR2(class="kw">const class="type">int nin,class="kw">const class="type">int nhid1,class="kw">const class="type">int nhid2,class="kw">const class="type">int nout,class="kw">const class="type">class="kw">double a,class="kw">const class="type">class="kw">double b,CMultilayerPerceptronShell &network); class="kw">static class="type">void MLPCreateC0(class="kw">const class="type">int nin,class="kw">const class="type">int nout,CMultilayerPerceptronShell &network); class="kw">static class="type">void MLPCreateC1(class="kw">const class="type">int nin,class="type">int nhid,class="kw">const class="type">int nout,CMultilayerPerceptronShell &network); class="kw">static class="type">void MLPCreateC2(class="kw">const class="type">int nin,class="kw">const class="type">int nhid1,class="kw">const class="type">int nhid2,class="kw">const class="type">int nout,CMultilayerPerceptronShell &network) class=class="str">"cmt">//--- 神经网络的属性和错误处理 class="kw">static class="type">void MLPProperties(CMultilayerPerceptronShell &network,class="type">int &nin,class="type">int &nout,class="type">int &wcount); class="kw">static class="type">int MLPGetLayersCount(CMultilayerPerceptronShell &network); class="kw">static class="type">int MLPGetLayerSize(CMultilayerPerceptronShell &network,class="kw">const class="type">int k);
「扒开多层感知机的内部接口」
想在 MT5 里手动干预神经网络,先得看清 CMultilayerPerceptronShell 暴露的这组静态方法。它们不负责建网,只负责读权重、改阈值、跑前向和训练,相当于把黑盒撬开了一条缝。 MLPGetInputScaling 和 MLPGetOutputScaling 按索引 i 取第 i 个输入输出量的均值 mean 与标准差 sigma,做归一化还原时离不开这两个值。MLPGetNeuronInfo 用层号 k 与神经元号 i 换回激活类型 fkind 和阈值 threshold,MLPGetWeight 则靠 (k0,i0)→(k1,i1) 四元组定位单层连接的权值。 写回同样直接:MLPSetNeuronInfo 改指定神经元的激活函数与阈值,MLPSetWeight 给某条连接赋新权 w。MLPActivationFunction 给定净输入 net 与层类型 k,同时吐出函数值 f、一阶导 df 和二阶导 d2f,方便自己实现反向传播实验。 前向计算走 MLPProcess,喂入 x[] 拿回 y[];误差评估有 MLPError(总误差)和 MLPRMSError(均方根误差,基于 npoints 个样本)。训练侧两条路:MLPTrainLM 用 Levenberg-Marquardt,MLPTrainLBFGS 用有限内存 BFGS,二者都收 decay 正则项与 restarts 重启动次数,info 返回状态码、rep 带训练报告。外汇与贵金属波动无常,用这些接口做信号模型属高风险尝试,回测亮眼不等于实盘概率占优。
class="kw">static class="type">void MLPGetInputScaling(CMultilayerPerceptronShell &network,class="kw">const class="type">int i,class="type">class="kw">double &mean,class="type">class="kw">double &sigma); class="kw">static class="type">void MLPGetOutputScaling(CMultilayerPerceptronShell &network,class="kw">const class="type">int i,class="type">class="kw">double &mean,class="type">class="kw">double &sigma); class="kw">static class="type">void MLPGetNeuronInfo(CMultilayerPerceptronShell &network,class="kw">const class="type">int k,class="kw">const class="type">int i,class="type">int &fkind,class="type">class="kw">double &threshold); class="kw">static class="type">class="kw">double MLPGetWeight(CMultilayerPerceptronShell &network,class="kw">const class="type">int k0,class="kw">const class="type">int i0,class="kw">const class="type">int k1,class="kw">const class="type">int i1); class="kw">static class="type">void MLPSetNeuronInfo(CMultilayerPerceptronShell &network,class="kw">const class="type">int k,class="kw">const class="type">int i,class="type">int fkind,class="type">class="kw">double threshold); class="kw">static class="type">void MLPSetWeight(CMultilayerPerceptronShell &network,class="kw">const class="type">int k0,class="kw">const class="type">int i0,class="kw">const class="type">int k1,class="kw">const class="type">int i1,class="kw">const class="type">class="kw">double w); class="kw">static class="type">void MLPActivationFunction(class="kw">const class="type">class="kw">double net,class="kw">const class="type">int k,class="type">class="kw">double &f,class="type">class="kw">double &df,class="type">class="kw">double &d2f); class="kw">static class="type">void MLPProcess(CMultilayerPerceptronShell &network,class="type">class="kw">double &x[],class="type">class="kw">double &y[]); class="kw">static class="type">class="kw">double MLPError(CMultilayerPerceptronShell &network,CMatrixDouble &xy,class="kw">const class="type">int ssize); class="kw">static class="type">class="kw">double MLPRMSError(CMultilayerPerceptronShell &network,CMatrixDouble &xy,class="kw">const class="type">int npoints); class=class="str">"cmt">//--- 训练神经网络 class="kw">static class="type">void MLPTrainLM(CMultilayerPerceptronShell &network,CMatrixDouble &xy,class="kw">const class="type">int npoints,class="kw">const class="type">class="kw">double decay,class="kw">const class="type">int restarts,class="type">int &info,CMLPReportShell &rep); class="kw">static class="type">void MLPTrainLBFGS(CMultilayerPerceptronShell &network,CMatrixDouble &xy,class="kw">const class="type">int npoints,class="kw">const class="type">class="kw">double decay,class="kw">const class="type">int restarts,class="kw">const class="type">class="kw">double wstep,class="type">int maxits,class="type">int &info,CMLPReportShell &rep);
把神经网络搭进EA的工程落点
在MT5里落地多层感知机,先要把每层的神经元数做成外部输入参数,同时定义规范化的区间字符串。历史起点 velaIniDesc 与总加载量 historialEntrena 决定从哪根K线开始取数、取多少根来训练。 全局开放一个 CMatrixDouble 类型的 arDatosAprende 二维动态矩阵,用来装输入/输出样本串;MQL5 只让第一维动态,其余维度必须显式定好。主流程 1-4 步全塞在 gestionRed() 里:建网用 creaRedNeuronal(),拉数据用 preparaDatosEntra(),可信度检查用 imprimeDatosEntra() 导出的 csv,需要归一就调 normalizaDatosRed(),防序列继承用 barajaDatosEntra(),训练走 entrenaEvalRed() 并返回误差,最后存盘留待恢复。 tasaAprende 与 ciclosEntrena 这两个量在 gestionRed() 开头定义,本意是学习率和训练轮数。但用 ALGLIB 的 LM 与 L-BFGS 做过多轮测试后,改这两个值对结果几乎没影响,后来直接挪进函数内部当常量。你可以保留为外部参数做对照实验,也可以省掉。 归一函数 normalizaDatosRed() 只在训练数据落进指定区间、且实盘请求预测的数据也同区间时才必要;若训练集已归一,预测前不必再对实盘数据归一。 ALGLIB 的 LM 与 L-BFGS 比传统反向传播省事太多。4 层结构(35/45/10/2 神经元)、输入矩阵 2000 串时,I5 四核 8G 环境下 4–6 分钟跑完,误差约 2–4×10^-5(百万分之四内)。权重超 500 的网络直接选 L-BFGS。返回码 2 或 6 代表成功,误差已低于停止标准。 训练完务必用 ALGLIB 提供的函数把网络写进 .red 文本文件,EA 意外停止后能反向读取恢复,不必重训。恢复后调 respuestaRed() 取预测,该函数允许你对输出做与训练时不同的反归一处理。
class="kw">input class="type">int nNeuronEntra= class="num">35; class=class="str">"cmt">//输入层的神经元数量 class="kw">input class="type">int nNeuronSal= class="num">1; class=class="str">"cmt">//输出层的神经元数量 class="kw">input class="type">int nNeuronCapa1= class="num">45; class=class="str">"cmt">//隐藏层 class="num">1 的神经元数量 (不能 <class="num">1) class="kw">input class="type">int nNeuronCapa2= class="num">10; class=class="str">"cmt">//隐藏层 class="num">2 的神经元数量 (不能 <class="num">1) class="kw">input class="type">class="kw">string intervEntrada= "class="num">0;class="num">1"; class=class="str">"cmt">//输入规范化: 期望的最小值和最大值 (空= 无规范化) class="kw">input class="type">class="kw">string intervSalida= ""; class=class="str">"cmt">//输出规范化: 期望的最小值和最大值 (空= 无规范化) class="kw">input class="type">int velaIniDesc= class="num">15; class="kw">input class="type">int historialEntrena= class="num">1500; CMultilayerPerceptronShell *objRed; CMatrixDouble arDatosAprende(class="num">0, class="num">0); class=class="str">"cmt">//---------------------------------- 创建并优化神经网络 -------------------------------------------------- class="type">bool gestionRed(CMultilayerPerceptronShell &objRed, class="type">class="kw">string simb, class="type">bool normEntrada= true , class="type">bool normSalida= true, class="type">bool imprDatos= true, class="type">bool barajar= true) {