利用 MQL5 矩阵的反向传播神经网络·综合运用
(3/3)· 用 MQL5 矩阵 API 把反向传播网络写进指标与 EA,新手也能跑通训练到预测
◍ 用 BPNN 矩阵指标看神经网络怎么推价格增量
BPNNMatrixPredictorDemo 这套指标把神经网络从 C++ 旧版移植进 MQL5,核心思路是从过去 EMA 价格增量里抽输入向量,抽点的位置按斐波那契间隔排:1、2、3、5、8、13、21、34、55、89、144 根柱。网络据此去推向量右侧下一根柱的价格增量,输入层长度由 _numInputs 定,最多叠 6 层。 训练集上限 _ntr、最大迭代 _nep、最小 MSE 误差 _maxMSEpwr 都是外部参数;EMA 平滑周期走 _smoothPer。默认 _lastBar=0 从最后一根柱抓训练数据,向前推 _futBars 根;若给 _lastBar 填正数,就能回看历史预测和真实报价比对。 指标吐三条缓冲:浅绿是训练集目标值,蓝是训练集上的网络输出,红是预测值。Train 函数训完回传权重数组,测试器里只训一次,在线时新柱触发重训(源码可改);Test 函数按权重重建网络做一次性预测,矩阵版不必手动按维度重塑数组,这是和老代码最直观的差异。 EURUSD H1 默认参数跑出来的图只能说明网络能跑通,简化版别直接拿来做单。外汇和贵金属杠杆高,这类信号仅作方法验证,实盘亏损概率不低。
input class="type">int _lastBar = class="num">0; class=class="str">"cmt">// Last bar in the past data input class="type">int _futBars = class="num">10; class=class="str">"cmt">// # of future bars to predict input class="type">int _smoothPer = class="num">6; class=class="str">"cmt">// Smoothing period input class="type">int _numLayers = class="num">3; class=class="str">"cmt">// # of layers including input, hidden & output(class="num">2..class="num">6) input class="type">int _numInputs = class="num">12; class=class="str">"cmt">// # of inputs(that is neurons in input class="num">0-th layer) input class="type">int _numNeurons1 = class="num">5; class=class="str">"cmt">// # of neurons in the class="num">1-st hidden or output layer input class="type">int _numNeurons2 = class="num">1; class=class="str">"cmt">// # of neurons in the class="num">2-nd hidden or output layer input class="type">int _numNeurons3 = class="num">0; class=class="str">"cmt">// # of neurons in the class="num">3-rd hidden or output layer input class="type">int _numNeurons4 = class="num">0; class=class="str">"cmt">// # of neurons in the class="num">4-th hidden or output layer input class="type">int _numNeurons5 = class="num">0; class=class="str">"cmt">// # of neurons in the class="num">5-th hidden or output layer input class="type">int _ntr = class="num">500; class=class="str">"cmt">// # of training sets / bars input class="type">int _nep = class="num">1000; class=class="str">"cmt">// Max # of epochs input class="type">int _maxMSEpwr = -class="num">7; class=class="str">"cmt">// Error(as power of class="num">10) for training to stop; mse < class="num">10^this
把训好的神经网络落盘复用
做日线级别的中长线系统,没必要每次启动都重新喂数据训练。市场源数据变化快、即时训练吃资源,保存已训练网络后直接加载复用,能省掉重复算力和时间开销。 MatrixNetStore 这个类干的就是存和取:模板方法 save/load 接收 MatrixNet 系列任意子类(目前有 MatrixNet 和 MatrixNetVisual 两个),靠文件名定位,落盘内容涵盖层数、各层尺寸、权重矩阵和激活函数。文件头先写一段签名串 "BPNNMS/1.0",加载时比对签名可拦掉格式不对的脏文件;签名本身也能改,类里留了接口。 save 还留了个口子:传 Storage 接口指针,就能在标准网络信息之外附加任意用户数据(比如样本区间、训练代次)。下面这段代码是存和取的核心骨架,逐行拆开看逻辑。 [CODE] class MatrixNetStore { static string signature; public: template<typename M> // M is a MatrixNet static bool save(const string filename, const M &net, Storage *storage = NULL, const int flags = 0) { // 先拿权重:优先取 best 权重,没有就取普通权重,都拿不到直接返回失败 matrix w[]; if(!net.getBestWeights(w)) { if(!net.getWeights(w)) { return false; } } // 以二进制写模式打开文件,带共享读写和 ANSI 标记
| int h = FileOpen(filename, FILE_WRITE | FILE_BIN | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_ANSI | flags); |
|---|
if(h == INVALID_HANDLE) return false; // 写签名、两个激活函数枚举、权重矩阵个数 FileWriteString(h, signature); FileWriteInteger(h, net.getActivationFunction()); FileWriteInteger(h, net.getActivationFunction(true)); FileWriteInteger(h, ArraySize(w)); // 逐层把矩阵展平成一维 double 数组写进去 for(int i = 0; i < ArraySize(w); ++i) { matrix m = w[i]; FileWriteInteger(h, (int)m.Rows()); FileWriteInteger(h, (int)m.Cols()); double a[]; m.Swap(a); FileWriteArray(h, a); } // 有外部存储对象就顺带写用户数据,失败仅打印提示 if(storage) { if(!storage.store(h)) Print("External info wasn't saved"); } FileClose(h); return true; } ... // load 见下 }; static string MatrixNetStore::signature = "BPNNMS/1.0"; // Storage 是纯虚接口,用户继承后实现 store/restore 挂自己的数据 class Storage { public: virtual bool store(const int h) = 0; virtual bool restore(const int h) = 0; }; class MatrixNetStore { ... template<typename M> // M is a MatrixNet static M *load(const string filename, Storage *storage = NULL, const int flags = 0) { // 二进制读模式打开
| int h = FileOpen(filename, FILE_READ | FILE_BIN | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_ANSI | flags); |
|---|
if(h == INVALID_HANDLE) return NULL; // 比对签名,不一致说明文件不对,关句柄报错返回 const string header = FileReadString(h, StringLen(signature)); if(header != signature) { FileClose(h); Print("Incorrect file header"); return NULL; } // 按顺序读回两个激活函数枚举 const ENUM_ACTIVATION_FUNCTION f1 = (ENUM_ACTIVATION_FUNCTION)FileReadInteger(h); const ENUM_ACTIVATION_FUNCTION f2 = (ENUM_ACTIVATION_FUNCTION)FileReadInteger(h); [/CODE] 开 MT5 建个 EA 把这段头文件逻辑接上,训一次网络存成 .bin,第二天直接 load 进策略跑,外汇和贵金属波动大、杠杆高,复用旧网络前最好确认行情结构没发生断裂式变化,否则旧权重可能偏向失效。
class MatrixNetStore { class="kw">static class="type">class="kw">string signature; class="kw">public: class="kw">template<class="kw">typename M> class=class="str">"cmt">// M is a MatrixNet class="kw">static class="type">bool save(const class="type">class="kw">string filename, const M &net, Storage *storage = NULL, const class="type">int flags = class="num">0) { class=class="str">"cmt">// get the matrix of weights(the best weights, if any) matrix w[]; if(!net.getBestWeights(w)) { if(!net.getWeights(w)) { class="kw">return false; } } class=class="str">"cmt">// open file class="type">int h = FileOpen(filename, FILE_WRITE | FILE_BIN | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_ANSI | flags); if(h == INVALID_HANDLE) class="kw">return false; class=class="str">"cmt">// write network metadata FileWriteString(h, signature); FileWriteInteger(h, net.getActivationFunction()); FileWriteInteger(h, net.getActivationFunction(true)); FileWriteInteger(h, ArraySize(w)); class=class="str">"cmt">// write weight matrices for(class="type">int i = class="num">0; i < ArraySize(w); ++i) { matrix m = w[i]; FileWriteInteger(h, (class="type">int)m.Rows()); FileWriteInteger(h, (class="type">int)m.Cols()); class="type">class="kw">double a[]; m.Swap(a); FileWriteArray(h, a); } class=class="str">"cmt">// if user data is provided, write it if(storage) { if(!storage.store(h)) Print("External info wasn&class="macro">#x27;t saved"); } FileClose(h); class="kw">return true; } ...}; class="kw">static class="type">class="kw">string MatrixNetStore::signature = "BPNNMS/class="num">1.0"; class Storage { class="kw">public: class="kw">virtual class="type">bool store(const class="type">int h) = class="num">0; class="kw">virtual class="type">bool restore(const class="type">int h) = class="num">0; }; class MatrixNetStore { ... class="kw">template<class="kw">typename M> class=class="str">"cmt">// M is a MatrixNet class="kw">static M *load(const class="type">class="kw">string filename, Storage *storage = NULL, const class="type">int flags = class="num">0) { class="type">int h = FileOpen(filename, FILE_READ | FILE_BIN | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_ANSI | flags); if(h == INVALID_HANDLE) class="kw">return NULL; class=class="str">"cmt">// check the format by signature const class="type">class="kw">string header = FileReadString(h, StringLen(signature)); if(header != signature) { FileClose(h); Print("Incorrect file header"); class="kw">return NULL; } class=class="str">"cmt">// read standard network metadata set const ENUM_ACTIVATION_FUNCTION f1 = (ENUM_ACTIVATION_FUNCTION)FileReadInteger(h); const ENUM_ACTIVATION_FUNCTION f2 = (ENUM_ACTIVATION_FUNCTION)FileReadInteger(h);
「从文件流复原神经网络权重矩阵」
把训练好的模型落盘后,下一步是能在 MT5 里原样读回。上面这段逻辑先读一个 int 作为层数标记 size,再按层循环:每次读 rows、cols 两个维度,然后用 FileReadArray 一次性拉取 rows*cols 个 double 权重进临时数组 a。 矩阵 w 用 ArrayResize 预扩到 size 层;每层拿到 a 后调用 Swap 接管内存、Reshape 成 (rows, cols) 的真实形状,避免拷贝开销。外汇与贵金属模型权重量级常在数万 double,这种整块读比逐元素快一个数量级,但文件损坏会导致 Reshape 维度错乱,概率上会引发后续推理崩锅。 可选 storage 块负责还原用户附加数据,restore 失败仅 Print 提示而不中断,属于容错设计。最后用读出的权重 w 和激活函数 f1、f2 构造网络对象 M 并关句柄返回——你可以直接把这段代码塞进 EA 的 OnInit,用 FileOpen 指到自己的 .nn 文件验证加载耗时。
const class="type">int size = FileReadInteger(h); matrix w[]; ArrayResize(w, size); class=class="str">"cmt">// read weight matrices for(class="type">int i = class="num">0; i < size; ++i) { const class="type">int rows = FileReadInteger(h); const class="type">int cols = FileReadInteger(h); class="type">class="kw">double a[]; FileReadArray(h, a, class="num">0, rows * cols); w[i].Swap(a); w[i].Reshape(rows, cols); } class=class="str">"cmt">// read user data if(storage) { if(!storage.restore(h)) Print("External info wasn&class="macro">#x27;t read"); } class=class="str">"cmt">// create a network object M *m = new M(w, f1, f2); FileClose(h); class="kw">return m; }
◍ 用预测方向驱动 EA 的神经网络骨架
TradeNN.mq5 把策略压到极简:下一根柱线预测为正就买、为负就卖,网络输出不解释成目标价,只取符号。这么做是故意的——把注意力留给网络参数本身,而不是盈利外壳。 Symbols 参数用逗号分隔品种,图表品种必须排第一;默认放 XAGUSD、XAUUSD、EURUSD,是因为金银相关性高、新闻冲击少,EURUSD 只当全局预测器。 输入向量大小 = 品种数 × Depth。默认 3 个品种、Depth=5,向量总长 15;Reserve 默认 250 根,在 D1 上约等于一年,Depth=5 则对应一周。网络共 5 层(输入+3隐藏+输出),倒数第二层用经验公式卡在前层与输出之间,并引入 DropOutPercent 随机断权重做正则化。 在 XAGUSD 的 D1、开盘价模式、2022.01.01 启动的实测里,网络先用 2021 全年训练,随后前向验证:2022 大部分时间亏损,唯训练集紧接的短暂窗口盈利,说明学到的形态只维持了一阵。分别把 DropOut 设为 25% 和 50% 重训,误差依次放大、训练曲线变毛糙、总利润下降,但 25% 通常比 0% 或 50% 更抗过拟合。 对 Depth(1–5)、Reserve(50–400 步长50)、HiddenLayerFactor(1–5)、DropOut(0/25/50)、Randomizer(0–9) 做 6000 组穷举优化,以利润因子为准则。统计显示 HiddenLayerFactor=1、Depth=4、Reserve=400、DropOut=25% 或 50% 优于默认;更深历史(≥350)普遍更好。外汇与贵金属属高风险,神经网络不是免死金牌,失效常源于依赖假设错误或数据准备失误。
训练外样本才是照妖镜
这次优化开了 Randomizer,实际跑了 30 个网络实例:每个舍弃级别(0%、25%、50%)各 10 个,我们只要 25% 和 50% 这两档。把 XML 优化报告导进去按盈利过滤(过滤器设 >1),取排序后第一行那个最好的。 网络文件都落在 MQL5/Files/<ea名>/<优化日期> 里,命名规则直接带参数:Depth=4-Reserve=400-HiddenLayerFactor=1-DropOutPercentage=25-Randomizer=6-3838(0.428079).bpn,括号前是验算号、括号内是网络误差 0.428079。文件尾端以文本存了训练元数据,能看到区间是 2021.01.12–2022.07.28 的 400 根 D1 柱。 把短名文件如 test3838.bpn 丢进终端共用文件夹,NetBinFileName 填它,Vector size 设 4,纯预测模式下其余参数无所谓。验证时故意拿 2020 年当陌生时段——训练集是 21–22 年,22–23 年做前测。 结果很直白:新数据上系统无利可图,别的参数组合也一个样。坏消息是这套思路在当前因子空间里走不通;好消息是这套神经网络工具包本身能正常产出技术预期内的结果,拿来证伪策略够用。外汇与贵金属波动剧烈,样本外失效是高概率风险,别把回测当保本凭证。 下面这段输入参数是实跑配置的骨架,注意 Randomizer 进 MathSrand 决定随机种子,DropOutPercentage 和 Depth 直接写死在文件名里方便回溯。
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span> Symbols = <span class="class="type">class="kw">string">"XAGUSD,XAUUSD,EURUSD"</span>; <span class="keyword">input</span> <span class="keyword">class="type">int</span> Depth = class="num">5; <span class="comment">class=class="str">"cmt">// Vector size(bars)</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> Reserve = class="num">250; <span class="comment">class=class="str">"cmt">// Training set size(vectors)</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> Epochs = class="num">1000; <span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> Accuracy = class="num">0.0001; <span class="comment">class=class="str">"cmt">// Accuracy(and training speed)</span> <span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> HiddenLayerFactor = class="num">2.0; <span class="comment">class=class="str">"cmt">// Hidden Layers Factor(to vector size)</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> DropOutPercentage = class="num">0; <span class="comment">class=class="str">"cmt">// DropOut Percentage</span> <span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span> NetBinFileName = <span class="class="type">class="kw">string">""</span>; <span class="keyword">input</span> <span class="keyword">class="type">int</span> Randomizer = class="num">0; <span class="keyword">class="kw">struct</span> Closes { <span class="keyword">class="type">class="kw">double</span> C[]; }; Closes CC[]; <span class="keyword">class="type">class="kw">string</span> S[]; <span class="keyword">class="type">int</span> Q; <span class="keyword">class="type">int</span> OnInit() { Q = <span class="functions">StringSplit</span>(<span class="functions">StringLen</span>(Symbols) ? Symbols : <span class="predefines">_Symbol</span>, &class="macro">#x27;,&class="macro">#x27;, S); <span class="functions">ArrayResize</span>(CC, Q); <span class="functions">MathSrand</span>(Randomizer); ... <span class="keyword">class="kw">return</span> <span style="class="type">class="kw">color:rgb(class="num">226, class="num">8, class="num">0);">INIT_SUCCEEDED</span>; } <span class="keyword">class="type">bool</span> Calc(<span class="keyword">const</span> <span class="keyword">class="type">int</span> offset) { <span class="keyword">const</span> <span class="keyword">class="type">class="kw">datetime</span> dt = iTime(<span class="predefines">_Symbol</span>, <span class="predefines">_Period</span>, offset); <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = class="num">0; i < Q; ++i) { <span class="keyword">const</span> <span class="keyword">class="type">int</span> bar = iBarShift(S[i], <span class="macro">PERIOD_CURRENT</span>, dt); <span class="comment">class=class="str">"cmt">// +class="num">1 for differences, +class="num">1 for model</span> <span class="keyword">const</span> <span class="keyword">class="type">int</span> n = <span class="functions">CopyClose</span>(S[i], <span class="macro">PERIOD_CURRENT</span>, bar, Depth + class="num">2, CC[i].C); <span class="keyword">for</span>(<span class="keyword">class="type">int</span> j = class="num">0; j < n - class="num">1; ++j) { CC[i].C[j] = (CC[i].C[j + class="num">1] - CC[i].C[j]) / <span class="functions">SymbolInfoDouble</span>(S[i], <span class="macro">SYMBOL_TRADE_TICK_SIZE</span>) * <span class="functions">SymbolInfoDouble</span>(S[i], <span class="macro">SYMBOL_TRADE_TICK_VALUE</span>); } <span class="functions">ArrayResize</span>(CC[i].C, n - class="num">1); } <span class="keyword">class="kw">return</span> <span class="keyword"><span class="macro">true</span></span>; } <span class="keyword">class="type">class="kw">double</span> Diff(<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &a[], <span class="keyword">class="type">class="kw">double</span> &d[]) { <span class="keyword">const</span> <span class="keyword">class="type">int</span> n = <span class="functions">ArraySize</span>(a); <span class="functions">ArrayResize</span>(d, n - class="num">1); <span class="comment">class=class="str">"cmt">// -class="num">1 minus the "future" model</span> <span class="keyword">class="type">class="kw">double</span> overall = class="num">0; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> j = class="num">0; j < n - class="num">1; ++j) <span class="comment">class=class="str">"cmt">// left(from old) to right(toward new)</span> { <span class="keyword">class="type">int</span> k = n - class="num">2 - j; overall += a[k]; d[j] = overall / <span class="functions">sqrt</span>(j + class="num">1); } ... <span class="comment">class=class="str">"cmt">// additional normalization</span> <span class="keyword">class="kw">return</span> a[n - class="num">1]; } <span class="keyword">class="type">void</span> OnTick() { ... <span class="keyword">class="kw">static</span> <span class="keyword">class="type">class="kw">datetime</span> last = class="num">0; <span class="keyword">if</span>(last == iTime(<span class="predefines">_Symbol</span>, <span class="predefines">_Period</span>, class="num">0)) <span class="keyword">class="kw">return</span>; ... <span class="keyword">class="kw">static</span> AutoPtr<MatrixNet> run; <span class="keyword">class="kw">static</span> <span class="keyword">class="type">class="kw">double</span> std; <span class="keyword">if</span>(NetBinFileName != <span class="class="type">class="kw">string">""</span>) { <span class="keyword">if</span>(!run[]) { run = LoadNet(NetBinFileName, std); <span class="keyword">if</span>(!run[]) { <span class="functions">ExpertRemove</span>(); <span class="keyword">class="kw">return</span>; } } } <span class="keyword">if</span>(run[]) { TradeTest(run[], std); } <span class="keyword">else</span> { run = TrainNet(std); } last = iTime(<span class="predefines">_Symbol</span>, <span class="predefines">_Period</span>, class="num">0); } MatrixNet *TrainNet(<span class="keyword">class="type">class="kw">double</span> &std) { <span class="keyword">class="type">class="kw">double</span> coefs[]; <span style="class="type">class="kw">color:rgb(class="num">0, class="num">87, class="num">174);">matrix</span> sys(Reserve, Q * Depth);
「把多品种价差塞进神经网络训练」
这段逻辑在做一件事:把 Reserve 根历史 bar 里多个品种的收盘价差分,拼成训练矩阵 sys,再把主图品种的模型值 model 当作拟合目标。循环从 Reserve-1 倒着走到 0,每根 bar 先调用 Calc(j+1) 补齐各品种深度数据,若某个品种历史不够就直接返回 NULL 等下一轮。 归一化时用了 sys.Std() * 3,也就是三倍标准差做缩放,Print 会输出类似 "Normalization by 3 std: 1.234" 的数,外汇和贵金属价差波动大,三倍标准差压缩后梯度可能仍偏陡,属于高风险建模前提。 隐藏层维度由数据列数推导:layers[1]、layers[2] 都等于 sys.Cols() * HiddenLayerFactor,layers[3] 取了行数平方根与各层乘积平方根的最大值,保证至少 1 个输出。net.train(sys, target, Epochs, Accuracy) 返回的误差值会直接打印,可在 MT5 专家日志里看收敛情况。 训练完把最优权重拿出来用 MatrixNet 重新测一遍,net2.test(sys, target) 的结果若明显低于 train 误差,可能说明过拟合不严重。最后网络连同品种列表、Depth/Reserve、Epochs 等参数写进 .bpn 文件,非优化模式下还会按修改时间重命名到公共目录,方便后续 EA 直接加载。
vector model(Reserve); vector t; class="type">class="kw">datetime start = class="num">0; for(class="type">int j = Reserve - class="num">1; j >= class="num">0; --j) class=class="str">"cmt">// loop through historical bars { class=class="str">"cmt">// since close prices are used, we make +class="num">1 to the bar index if(!Calc(j + class="num">1)) class=class="str">"cmt">// collect data for all symbols starting with bar j to Depth bars { class="kw">return NULL; class=class="str">"cmt">// probably other symbols don&class="macro">#x27;t have enough history(wait) } class=class="str">"cmt">// remember training sample start date/time if(start == class="num">0) start = iTime(_Symbol, _Period, j); ArrayResize(coefs, class="num">0); class=class="str">"cmt">// calculate price difference for all symbols for Depth bars for(class="type">int i = class="num">0; i < Q; ++i) { class="type">class="kw">double temp[]; class="type">class="kw">double m = Diff(CC[i].C, temp); if(i == class="num">0) { model[j] = m; } class="type">int dest = ArraySize(coefs); ArrayCopy(coefs, temp, dest, class="num">0); } t.Assign(coefs); sys.Row(t, j); } class=class="str">"cmt">// normalize std = sys.Std() * class="num">3; Print("Normalization by class="num">3 std: ", std); sys /= std; matrix target = {}; target.Col(model, class="num">0); target /= std; class=class="str">"cmt">// the size of layers class="num">0, class="num">1, class="num">2, class="num">3 is derived from the data, always one output class="type">int layers[] = {class="num">0, class="num">0, class="num">0, class="num">0, class="num">1}; layers[class="num">0] = (class="type">int)sys.Cols(); layers[class="num">1] = (class="type">int)(sys.Cols() * HiddenLayerFactor); layers[class="num">2] = (class="type">int)(sys.Cols() * HiddenLayerFactor); layers[class="num">3] = (class="type">int)fmax(sqrt(sys.Rows()), fmax(sqrt(layers[class="num">1] * layers[class="num">3]), sys.Cols() * sqrt(HiddenLayerFactor))); class=class="str">"cmt">// create and configure the network of the specified configuration ArrayPrint(layers); MatrixNetVisual *net = new MatrixNetVisual(layers); net.setupSpeedAdjustment(SpeedUp, SpeedDown, SpeedHigh, SpeedLow); net.enableDropOut(DropOutPercentage); class=class="str">"cmt">// train the network and display the result(error) Print("Training result: ", net.train(sys, target, Epochs, Accuracy)); ... matrix w[]; if(net.getBestWeights(w)) { MatrixNet net2(w); if(net2.isReady()) { Print("Best result: ", net2.test(sys, target)); ... } } class="kw">return net; } class=class="str">"cmt">// the most important or all EA settings can be added to the network file const class="type">class="kw">string context = StringFormat("\r\n%s %s %s-%s", _Symbol, EnumToString(_Period), TimeToString(start), TimeToString(iTime(_Symbol, _Period, class="num">0))) + "\r\n" + Symbols + "\r\n" + (class="type">class="kw">string)Depth + "/" + (class="type">class="kw">string)Reserve + "\r\n" + (class="type">class="kw">string)Epochs + "/" + (class="type">class="kw">string)Accuracy + "\r\n" + (class="type">class="kw">string)HiddenLayerFactor + "/" + (class="type">class="kw">string)DropOutPercentage + "\r\n"; class=class="str">"cmt">// prepare a temporary file name const class="type">class="kw">string tempfile = "bpnnmtmp" + (class="type">class="kw">string)GetTickCount64() + ".bpn"; class=class="str">"cmt">// save the network and user data to a file MatrixNetStore store; class=class="str">"cmt">// main class unloading/loading the networks BinFileNetStorage writer(context, net.getStats(), std); class=class="str">"cmt">// optional class with our information store.save(tempfile, *net, &writer); ... if(!MQLInfoInteger(MQL_OPTIMIZATION)) { class=class="str">"cmt">// set a new name in a more understandable time format, in the common folder class="type">class="kw">string filename = "bpnn" + TimeStamp((class="type">class="kw">datetime)FileGetInteger(tempfile, FILE_MODIFY_DATE)) + StringFormat("(%7g)", net.getStats().bestLoss) + ".bpn"; if(!FileMove(tempfile, class="num">0, filename, FILE_COMMON)) {
◍ 把矩阵网络塞进MT5交易闭环
LoadNet 负责从本地或共享目录读回训练好的 MatrixNet 模型,同时取回归一化尺度 std 与用户备注。代码里 std 默认 1.0,真正赋值来自 reader.getScale(),这一步若漏掉,后续预测值会整体偏一个数量级。 TradeTest 先以 Calc(0) 刷新品种数据,再把 Q 个通道的差分系数拼进 coefs 数组,用 vector::Assign 转成行向量后除以 std 做反归一化还原。喂给 net.feedForward(data) 后,取 y[0][0]*std 作为方向预测,大于 0 开 BUY 否则开 SELL——外汇与贵金属杠杆高,信号仅代表概率倾向,实盘须自担回撤风险。 ClosePosition 用 PositionSelect 查当前仓,按持仓类型异或 1 得到平仓指令类型,deviation 写死 5 点容差,comment 直接塞进浮盈字符串如 '+1.23'。OrderSend 后只认 TRADE_RETCODE_DONE 或 PLACED 为成功,其余一律返 false。 回测日志里能看到真实落点:归一化用 3 倍标准差得 1.3416,训练 1000 代后精度 0.024416,早停 EMA 设在 31 代(阈值 0.0625)。开 MT5 把这段接进 EA,先跑 LoadNet 确认 std 打印值,再观察 TradeTest 的 Print 预测行是否随柱更新。
MatrixNet *LoadNet(const class="type">class="kw">string filename, class="type">class="kw">double &std, const class="type">int flags = FILE_COMMON) { BinFileNetStorage reader; class=class="str">"cmt">// optional user data MatrixNetStore store; class=class="str">"cmt">// general metadata MatrixNet *net; std = class="num">1.0; Print("Loading ", filename); ResetLastError(); net = store.load<MatrixNet>(filename, &reader, flags); if(net == NULL) { Print("Failed: ", _LastError); class="kw">return NULL; } MatrixNet::Stats s[class="num">1]; s[class="num">0] = reader.getStats(); ArrayPrint(s); std = reader.getScale(); Print(std); Print(reader.getDescription()); class="kw">return net; } class="type">bool TradeTest(MatrixNet *net, const class="type">class="kw">double std) { if(!Calc(class="num">0)) class="kw">return false; class="type">class="kw">double coefs[]; for(class="type">int i = class="num">0; i < Q; ++i) { class="type">class="kw">double temp[]; class=class="str">"cmt">// difference on the 0th bar is ignored, it will be predicted class=class="str">"cmt">/* class="type">class="kw">double m = */Diff(CC[i].C, temp, true); ArrayCopy(coefs, temp, ArraySize(coefs), class="num">0); } vector t; t.Assign(coefs); matrix data = {}; data.Row(t, class="num">0); data /= std; ... ClosePosition(); if(net.feedForward(data)) { matrix y = net.getResults(); Print("Prediction: ", y[class="num">0][class="num">0] * std); OpenPosition((y[class="num">0][class="num">0] > class="num">0) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); class="kw">return true; } class="kw">return false; } class="type">bool ClosePosition() { class=class="str">"cmt">// define an empty structure class="type">MqlTradeRequest request = {}; if(!PositionSelect(_Symbol)) class="kw">return false; const class="type">class="kw">string pl = StringFormat("%+.2f", PositionGetDouble(POSITION_PROFIT)); class=class="str">"cmt">// fill in the required fields request.action = TRADE_ACTION_DEAL; request.position = PositionGetInteger(POSITION_TICKET); const ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)(PositionGetInteger(POSITION_TYPE) ^ class="num">1); request.type = type; request.price = SymbolInfoDouble(_Symbol, type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID); request.volume = PositionGetDouble(POSITION_VOLUME); request.deviation = class="num">5; request.comment = pl; class=class="str">"cmt">// send request ResetLastError(); class="type">MqlTradeResult result[class="num">1]; const class="type">bool ok = OrderSend(request, result[class="num">0]); Print("Status: ", _LastError, ", P/L: ", pl); ArrayPrint(result); if(ok && (result[class="num">0].retcode == TRADE_RETCODE_DONE || result[class="num">0].retcode == TRADE_RETCODE_PLACED)) { class="kw">return true; } class="kw">return false; }
BPNN 回测表格里该盯哪几个数
上面这段是某次 BPNN 模型在 MT5 遗传优化器里的原始 dump。模型文件 bpnnm202302121707(0.0244162).bpn 对应的训练区间锁定在 XAGUSD 日线 2021.01.18 至 2022.01.04,输入特征混用了 XAGUSD、XAUUSD、EURUSD 三个品种,隐含层 5 节点、训练集 250 根、验证集 5 根。 两次独立训练跑满 1000 epoch:第一轮终 loss 0.18258,第二轮 0.25191。注意验证集精度反而低于训练集,说明网络在日线尺度上倾向于记忆而非泛化,外汇与贵金属的高波动会放大这种过拟合风险。 遗传优化吐出的 5 行结果中,Pass 3838 那行 Expected Profit 2.41741、Sharpe 1.34991、Recovery Factor 1.98582 均为全场最高,Hidden LayerF 1.61、DropOut 25、Randomizer 6。这组参数组合倾向在银价日线跨品种特征上给出更稳的赔率,但样本外表现仍可能随宏观流动性突变而漂移。 开 MT5 把上面 Pass 3838 的 DropOut=25 / Randomizer=6 填回网络配置,用 2022 年以后的 XAGUSD 日线重跑一遍,比对着原表看 Expected Profit 是否还能守住 2.0 上方,比盲目信遗传排名更实在。
「参数扫描结果的原始数据排布」
上面这串数字不是行情报价,而是一轮 EA 参数扫描后按某项指标排序的输出行。每行最左的 3238、2038 这类四位数可看作测试编号,紧跟的 1.13、1.11 是盈利因子(Profit Factor),141.35、118.49 为净利点数,后面的 1.01691、0.85245 等成对浮点大概率是入场阈值与出场阈值。 注意第 5 行编号 238 的那组:盈利因子掉到 1.07,净利仅 73.33 点,末尾信号计数为 0,说明该组参数在样本内未触发任何交易。对比首行编号 3238 的 1.13 盈利因子与 141.35 净利、信号计数 5,参数敏感度的落差一眼可辨。 这种原始排布直接丢进 MT5 的 CSV 或终端 Journal 里核对最稳妥。外汇与贵金属品种杠杆高、滑点随机,样本内好看不代表实盘概率占优,开 MT5 用同周期同品种重跑一遍才能确认。
class="num">3238 class="num">1.13 class="num">141.35 class="num">1.01691 class="num">1.13314 class="num">0.46758 class="num">0.48160 class="num">1 class="num">2.95 class="num">139 class="num">4 class="num">400 class="num">1 class="num">25 class="num">5 class="num">2038 class="num">1.11 class="num">118.49 class="num">0.85245 class="num">1.11088 class="num">0.38826 class="num">0.41380 class="num">1 class="num">2.96 class="num">139 class="num">4 class="num">400 class="num">1 class="num">25 class="num">3 class="num">4038 class="num">1.10 class="num">107.46 class="num">0.77309 class="num">1.09951 class="num">0.49377 class="num">0.38716 class="num">1 class="num">2.12 class="num">139 class="num">4 class="num">400 class="num">1 class="num">50 class="num">6 class="num">1438 class="num">1.10 class="num">104.52 class="num">0.75194 class="num">1.09700 class="num">0.51681 class="num">0.37404 class="num">1 class="num">1.99 class="num">139 class="num">4 class="num">400 class="num">1 class="num">25 class="num">2 class="num">238 class="num">1.07 class="num">73.33 class="num">0.52755 class="num">1.06721 class="num">0.19040 class="num">0.26499 class="num">1 class="num">3.69 class="num">139 class="num">4 class="num">400 class="num">1 class="num">25 class="num">0 class="num">2838 class="num">1.03 class="num">34.62 class="num">0.24907 class="num">1.03111 class="num">0.10290 class="num">0.13053 class="num">1 class="num">3.29 class="num">139 class="num">4 class="num">400 class="num">1 class="num">50 class="num">4 class="num">2238 class="num">1.02 class="num">21.62 class="num">0.15554 class="num">1.01927 class="num">0.05130 class="num">0.07578 class="num">1 class="num">4.12 class="num">139 class="num">4 class="num">400 class="num">1 class="num">50 class="num">3
◍ 记住这一条就够了
把反向传播神经网络塞进 MT5 不用依赖 Python 或 OpenCL 显卡,纯 MQL5 矩阵类就能跑训练、推理、过程可视化和模型落盘。附件 MQL5bpnm.zip 仅 22.73 KB,直接解压就能挂进自己的 EA 或指标里做二次开发。 但神经网络只是处理素材的工具,不是点石成金的圣杯。若喂进去的财务序列噪声过大、信息不足或字段无关,再深的网络也学不出稳定信号——外汇和贵金属市场的高杠杆特性会放大这种失效,实盘前务必用历史数据做概率层面的回测。 要在净额结算账户上正常平仓,ClosePosition 里必须显式写 request.symbol = _Symbol,否则持仓匹配可能出错。下面这段代码就是修正后的核心片段,黄底那行是关键补漏点。 反向传播本身只是基础学法,后面接 RNN、CNN 或强化学习都从它起手;工具到手了,剩下的全看你能不能挑出干净的行情特征。
class="type">bool ClosePosition() { class=class="str">"cmt">// 定义一个空结构 class="type">MqlTradeRequest request = {}; ... class=class="str">"cmt">// 填写必填字段 request.action = TRADE_ACTION_DEAL; request.position = PositionGetInteger(POSITION_TICKET); request.symbol = _Symbol; const ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)(PositionGetInteger(POSITION_TYPE) ^ class="num">1); request.type = type; request.price = SymbolInfoDouble(_Symbol, type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID); request.volume = PositionGetDouble(POSITION_VOLUME); ... class=class="str">"cmt">// 发送请求 ... }