使用带 ENCOG 机器学习框架的 MetaTrader 5 指标进行时间序列预测·综合运用
📘

使用带 ENCOG 机器学习框架的 MetaTrader 5 指标进行时间序列预测·综合运用

第 3/3 篇

从一行历史报价看 EURUSD 与利差耦合

上面这行数据是 2003-08-18 00:00 的某组跨市场记录,字段依次为日期时间、EURUSD 报价的 1.38910000、以及两组数量级在 10^1 附近的数值 79.9089 与 64.1474,最后一项为 -9.2820。把它直接丢进 MT5 的 CSV 预览或自定义指标缓冲区,能验证那个年代欧元刚站稳 1.38 上方时,利差项的波动已经走到个位数负区间。 外汇与贵金属属高风险品种,这类早期样本只说明定价因子在缓慢重排,不预示任何方向;打开历史中心把 2003 年 8 月整月调出来,对比同期的美元指数日线,你大概会倾向认为当时的汇率对利差敏感度比现在钝。

MQL5 / C++
class="num">20030818,class="num">0000,class="num">1.38910000,class="num">7.9908906883e+001,class="num">6.4147384124e+001,-class="num">9.2819614711e+000

「调参和限时训练才是实盘前必做的功课」

用 C# 搭好的训练框架把金融时间序列预测的底子打在了 Encog.App.Quant 名称空间上,脚本本身很活,输入指标数量随便改,你真正要动的往往只是 DIRECTORY 常量里的 MT5 文件目录路径。 网络结构和训练强度都靠几个顶部的常量控制,代码注释写得密,建议逐行读一遍再动手。下面这段常量定义就是架构入口:INPUT_WINDOW=6 代表拿前 6 根 K 线去推下一根,PREDICT_WINDOW=1 是只预测 1 根,HIDDEN1_NEURONS=12 给第一隐层 12 个神经元,TARGET_ERROR=0.01 是误差门槛。 [CODE] /// <summary> /// The size of the input window. This is the number of bars used to predict the next bar. /// </summary> public const int INPUT_WINDOW = 6; /// <summary> /// The number of bars forward we are trying to predict. This is usually just 1 bar. The future indicator used in step 1 may /// well look more forward into the future. /// </summary> public const int PREDICT_WINDOW = 1; /// <summary> /// The number of bars forward to look for the best result. /// </summary> public const int RESULT_WINDOW = 5; /// <summary> /// The number of neurons in the first hidden layer. /// </summary> public const int HIDDEN1_NEURONS = 12; /// <summary> /// The target error to train to. /// </summary> public const double TARGET_ERROR = 0.01; using System; using System.Collections.Generic; using System.Linq; using System.Text; using Encog.App.Quant.Normalize; using Encog.Util.CSV; using Encog.App.Quant.Indicators; using Encog.App.Quant.Indicators.Predictive; using Encog.App.Quant.Temporal; using Encog.Neural.NeuralData; using Encog.Neural.Data.Basic; using Encog.Util.Simple; using Encog.Neural.Networks; using Encog.Neural.Networks.Layers; using Encog.Engine.Network.Activation; using Encog.Persist; namespace NetworkTrainer { public class Program { /// <summary> /// The directory that all of the files will be stored in. /// </summary> public const String DIRECTORY = "d:\\mt5\\MQL5\\Files\\"; /// <summary> /// The input file that starts the whole process. This file should be downloaded from NinjaTrader using the EncogStreamWriter object. /// </summary> public const String STEP1_FILENAME = DIRECTORY + "mt5export.csv"; /// <summary> /// We apply a predictive future indicator and generate a second file, with the additional predictive field added. /// </summary> public const String STEP2_FILENAME = DIRECTORY + "step2_future.csv"; /// <summary> [/CODE] 原文示例里把 EncogUtility.TrainToError() 注释掉、换成了 TrainConsole(),后者按分钟数跑训练——示例设了 3 分钟。实际训练耗时看网络复杂度和数据量,可能几分钟,也可能几小时甚至几天,外汇和贵金属行情的高波动特征会让样本规模膨胀得很快。 别把误差门槛当保底 神经元太少时网络可能根本训不到 TARGET_ERROR=0.01,这时候要么加 HIDDEN1_NEURONS,要么回到 TrainToError() 注释行放开、把 TrainConsole() 注释掉,让它在达标后自动停。两种训练函数切换只在一行注释,开 MT5 导完数据就能直接试。

MQL5 / C++
        class=class="str">"cmt">/// <summary>
        class=class="str">"cmt">/// The size of the input window.  This is the number of bars used to predict the next bar.
        class=class="str">"cmt">/// </summary>
        class="kw">public class="kw">const class="type">int INPUT_WINDOW = class="num">6;
        class=class="str">"cmt">/// <summary>
        class=class="str">"cmt">/// The number of bars forward we are trying to predict.  This is usually just class="num">1 bar.  The future indicator used in step class="num">1 may
        class=class="str">"cmt">/// well look more forward into the future. 
        class=class="str">"cmt">/// </summary>
        class="kw">public class="kw">const class="type">int PREDICT_WINDOW = class="num">1;
        class=class="str">"cmt">/// <summary>
        class=class="str">"cmt">/// The number of bars forward to look for the best result.
        class=class="str">"cmt">/// </summary>
        class="kw">public class="kw">const class="type">int RESULT_WINDOW = class="num">5;
        class=class="str">"cmt">/// <summary>
        class=class="str">"cmt">/// The number of neurons in the first hidden layer.
        class=class="str">"cmt">/// </summary>
        class="kw">public class="kw">const class="type">int HIDDEN1_NEURONS = class="num">12;
        class=class="str">"cmt">/// <summary>
        class=class="str">"cmt">/// The target error to train to.
        class=class="str">"cmt">/// </summary>
        class="kw">public class="kw">const class="type">class="kw">double TARGET_ERROR = class="num">0.01;
        class="kw">using System;
        class="kw">using System.Collections.Generic;
        class="kw">using System.Linq;
        class="kw">using System.Text;
        class="kw">using Encog.App.Quant.Normalize;
        class="kw">using Encog.Util.CSV;
        class="kw">using Encog.App.Quant.Indicators;
        class="kw">using Encog.App.Quant.Indicators.Predictive;
        class="kw">using Encog.App.Quant.Temporal;
        class="kw">using Encog.Neural.NeuralData;
        class="kw">using Encog.Neural.Data.Basic;
        class="kw">using Encog.Util.Simple;
        class="kw">using Encog.Neural.Networks;
        class="kw">using Encog.Neural.Networks.Layers;
        class="kw">using Encog.Engine.Network.Activation;
        class="kw">using Encog.Persist;
        class="kw">namespace NetworkTrainer
        {
            class="kw">public class Program
            {
                class=class="str">"cmt">/// <summary>
                class=class="str">"cmt">/// The directory that all of the files will be stored in.
                class=class="str">"cmt">/// </summary>
                class="kw">public class="kw">const String DIRECTORY = "d:\\mt5\\MQL5\\Files\\";
                class=class="str">"cmt">/// <summary>
                class=class="str">"cmt">/// The input file that starts the whole process.  This file should be downloaded from NinjaTrader class="kw">using the EncogStreamWriter object.
                class=class="str">"cmt">/// </summary>
                class="kw">public class="kw">const String STEP1_FILENAME = DIRECTORY + "mt5export.csv";
                class=class="str">"cmt">/// <summary>
                class=class="str">"cmt">/// We apply a predictive future indicator and generate a second file, with the additional predictive field added.
                class=class="str">"cmt">/// </summary>
                class="kw">public class="kw">const String STEP2_FILENAME = DIRECTORY + "step2_future.csv";
                class=class="str">"cmt">/// <summary>

◍ 把训练管道钉死在常量里

做 MT5 历史样本训练时,最怕路径和窗口参数散落在函数体里,下次换品种就得全文搜。下面这段把五个阶段产物和核心窗口全部提为 const,复制进 C# 控制台工程即可直接跑管道。 归一化结果落在 step3_norm.csv,时间切片训练集是 step4_train.csv,训完的网络存成 step5_network.eg,三个文件都在 DIRECTORY 下,改目录只动一处。 INPUT_WINDOW=6 代表用最近 6 根 bar 去推下一根;PREDICT_WINDOW=1 是标准单根前瞻;RESULT_WINDOW=5 则是在未来 5 根内找最优收益作为标签。隐藏层 HIDDEN1_NEURONS=12、TARGET_ERROR=0.01 是这套示例的止训阈值,外汇与贵金属波动跳变多,0.01 误差在实盘可能倾向过拟合,建议先原样跑通再调。 Main 里第一步先让 ProcessIndicators 读 STEP1_FILENAME 并写未来指标,externalIndicatorCount 用总列减 3 算外部因子数,随后挂 BestReturn(5,true) 做标签列落盘。开 MT5 导一份 EURUSD 的 H1 csv,把 STEP1_FILENAME 指过去,就能看到控制台打出外部指标数量。

MQL5 / C++
class="kw">public class="kw">const String STEP3_FILENAME = DIRECTORY + "step3_norm.csv";
class="kw">public class="kw">const String STEP4_FILENAME = DIRECTORY + "step4_train.csv";
class="kw">public class="kw">const String STEP5_FILENAME = DIRECTORY + "step5_network.eg";
class="kw">public class="kw">const class="type">int INPUT_WINDOW = class="num">6;
class="kw">public class="kw">const class="type">int PREDICT_WINDOW = class="num">1;
class="kw">public class="kw">const class="type">int RESULT_WINDOW = class="num">5;
class="kw">public class="kw">const class="type">int HIDDEN1_NEURONS = class="num">12;
class="kw">public class="kw">const class="type">class="kw">double TARGET_ERROR = class="num">0.01;
class="kw">static class="type">void Main(class="type">class="kw">string[] args)
{
   Console.WriteLine("Step class="num">1: Analyze MT5 Export & Create Future Indicators");
   ProcessIndicators ind = new ProcessIndicators();
   ind.Analyze(STEP1_FILENAME, true, CSVFormat.DECIMAL_POINT);
   class="type">int externalIndicatorCount = ind.Columns.Count - class="num">3;
   ind.AddColumn(new BestReturn(RESULT_WINDOW,true));
   ind.Process(STEP2_FILENAME);
   Console.WriteLine("External indicators found: " + externalIndicatorCount);
}

特征归一化与时间窗切分的关键设置

把原始 CSV 送进 Encog 做建模前,先要跑一步归一化:日期和时间列设为 PassThrough 原样透传,Close、Stoch K、Stoch D、WilliamsR 以及未来收益标签全部 Normalize,避免量纲差异把神经网络带偏。 归一化对象由 norm.Stats 数组索引控制,索引 2 到 6 分别对应上述五个数值字段;输出文件 STEP3_FILENAME 供下一步使用。这一处若漏掉 Close 的归一化,后续训练误差可能放大数倍。 时间窗切分用 TemporalWindow 完成:InputWindow 和 PredictWindow 决定回望与预测长度,日期、时间、收盘价为 Ignore,外部指标(externalIndicatorCount 个)标 Input,最优收益列标 Predict,最终落盘 STEP4_FILENAME。 神经元数量由 inputNeurons = INPUT_WINDOW * externalIndicatorCount 与 outputNeurons = PREDICT_WINDOW 算出,直接喂给 LoadCSV2Memory 构造训练集;网络首层用 TANH 激活、节点数等于 inputNeurons。外汇与贵金属波动剧烈,此类模型仅作概率参考,实盘前务必在 MT5 用历史数据回测验证。

MQL5 / C++
Console.WriteLine("Step class="num">2: Create Future Indicators");
EncogNormalize norm = new EncogNormalize();
norm.Analyze(STEP2_FILENAME, true, CSVFormat.ENGLISH);
norm.Stats[class="num">0].Action = NormalizationDesired.PassThrough; class=class="str">"cmt">// Date
norm.Stats[class="num">1].Action = NormalizationDesired.PassThrough; class=class="str">"cmt">// Time
norm.Stats[class="num">2].Action = NormalizationDesired.Normalize; class=class="str">"cmt">// Close
norm.Stats[class="num">3].Action = NormalizationDesired.Normalize; class=class="str">"cmt">// Stoch K
norm.Stats[class="num">4].Action = NormalizationDesired.Normalize; class=class="str">"cmt">// Stoch Dd
norm.Stats[class="num">5].Action = NormalizationDesired.Normalize; class=class="str">"cmt">// WilliamsR
norm.Stats[class="num">6].Action = NormalizationDesired.Normalize; class=class="str">"cmt">// best class="kw">return [RESULT_WINDOW]
norm.Normalize(STEP3_FILENAME);
class="type">int inputNeurons = INPUT_WINDOW * externalIndicatorCount;
class="type">int outputNeurons = PREDICT_WINDOW;
Console.WriteLine("Step class="num">3: Timebox");
TemporalWindow window = new TemporalWindow();
window.Analyze(STEP3_FILENAME, true, CSVFormat.ENGLISH);
window.InputWindow = INPUT_WINDOW;
window.PredictWindow = PREDICT_WINDOW;
class="type">int index = class="num">0;
window.Fields[index++].Action = TemporalType.Ignore; class=class="str">"cmt">// date
window.Fields[index++].Action = TemporalType.Ignore; class=class="str">"cmt">// time
window.Fields[index++].Action = TemporalType.Ignore; class=class="str">"cmt">// close
for(class="type">int i=class="num">0;i<externalIndicatorCount;i++)
  window.Fields[index++].Action = TemporalType.Input; class=class="str">"cmt">// external indicators
window.Fields[index++].Action = TemporalType.Predict; class=class="str">"cmt">// PredictBestReturn
window.Process(STEP4_FILENAME);
Console.WriteLine("Step class="num">4: Train");
INeuralDataSet training = (BasicNeuralDataSet)EncogUtility.LoadCSV2Memory(STEP4_FILENAME, inputNeurons, outputNeurons, true, CSVFormat.ENGLISH);
BasicNetwork network = new BasicNetwork();
network.AddLayer(new BasicLayer(new ActivationTANH(), true, inputNeurons));

「隐藏层到落盘的接线细节」

上面这段把网络结构收口并直接丢进训练与保存流程。第一层用 TANH 激活、带偏置、神经元数由 HIDDEN1_NEURONS 控制;输出层用线性激活,适合回归类价格预测,而非二分类。 Structure.FinalizeStructure() 之后必须 Reset(),否则权重沿用旧值,MT5 里跑出来的拟合可能偏向初始噪声。被注释掉的 TrainToError 是按目标误差停训,而实际跑的是 TrainConsole(network, training, 3)——这里的 3 是控制台训练轮数上限,不是误差值,别误改成小数。 训练完用 EncogMemoryCollection 把 network 和归一化统计 stat 一起存到 STEP5_FILENAME。少存 stat,后面做实时推断时反归一化会直接错位。外汇与贵金属波动具有高杠杆风险,模型仅作概率参考。 开 MT5 把这段接在你自己的特征工程后,先只跑 3 轮看控制台损失曲线,确认不报维度错再放开 TrainToError。

MQL5 / C++
network.AddLayer(new BasicLayer(new ActivationTANH(), true, HIDDEN1_NEURONS));
network.AddLayer(new BasicLayer(new ActivationLinear(), true, outputNeurons));
network.Structure.FinalizeStructure();
network.Reset();
class=class="str">"cmt">//EncogUtility.TrainToError(network, training, TARGET_ERROR);
EncogUtility.TrainConsole(network, training, class="num">3);
class=class="str">"cmt">// Step class="num">5: Save neural network and stats
EncogMemoryCollection encog = new EncogMemoryCollection();
encog.Add("network", network);
encog.Add("stat", norm.Stats);
encog.Save(STEP5_FILENAME);
Console.ReadKey();
}
}
}
EncogUtility.TrainConsole(network, training, class="num">3);
EncogUtility.TrainToError(network, training, TARGET_ERROR);

◍ 把训练好的网络塞进 MT5 指标里

训练完的神经网络不会自己画图,得靠一个双语言指标把它跑起来。MT5 里的 Encog 神经指标分两头:MQL5 端负责取指标数值并喂给网络,C# 端做时间归一化再把输出吐回 MQL5,后者依赖把 C# 编进 MT5 的那套桥接思路。 MQL5 部分必须导入 EncogNNTrainDLL.dll,并调用其中导出的 initializeTrainedNN() 与 computeNNIndicator()。在 computeNNIndicator() 里,前三个参数是各指标输入数组,第四个是输入窗口长度,SizeParamIndex = 3 正是因为它从 0 开始计数指向窗口长度变量,第五个参数是装网络结果的表。想换三个以外的指标数量,就改这个方法。 用 USDCHF 日线、随机指标加威廉 %R 训出的网络挂成指标后,输出显示下一根 K 线预测的最优回报倾向。注意图表上我把指标整体右移一根柱,这明确标示它是前瞻预测值而非回看。外汇与贵金属杠杆高,这类预测仅作概率参考,实盘前请在 MT5 用历史数据验证偏移与 dll 调用是否稳。 下面这段 C# 导出层是 dll 的骨架,initializeTrainedNN 载入网络文件,computeNNIndicator 接收三组双精度数组: using System; using System.Collections.Generic; using System.Text; using RGiesecke.DllExport; using System.Runtime.InteropServices; using Encog.Neural.Networks; using Encog.Persist; using Encog.App.Quant.Normalize; using Encog.Neural.Data; using Encog.Neural.Data.Basic; namespace EncogNeuralIndicatorMT5DLL { public class NeuralNET { private EncogMemoryCollection encog; public BasicNetwork network; public NormalizationStats stats; public NeuralNET(string nnPath) { initializeNN(nnPath); } public void initializeNN(string nnPath) { try { encog = new EncogMemoryCollection(); encog.Load(nnPath); network = (BasicNetwork)encog.Find("network"); stats = (NormalizationStats)encog.Find("stat"); } catch (Exception e) { Console.WriteLine(e.StackTrace); } } }; class UnmanagedExports { static NeuralNET neuralnet; [DllExport("initializeTrainedNN", CallingConvention = CallingConvention.StdCall)] static int initializeTrainedNN([MarshalAs(UnmanagedType.LPWStr)]string nnPath) { neuralnet = new NeuralNET(nnPath); if (neuralnet.network != null) return 0; else return -1; } [DllExport("computeNNIndicator", CallingConvention = CallingConvention.StdCall)] public static int computeNNIndicator([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t1, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t2, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t3,

MQL5 / C++
class="kw">using System;
class="kw">using System.Collections.Generic;
class="kw">using System.Text;
class="kw">using RGiesecke.DllExport;
class="kw">using System.Runtime.InteropServices;
class="kw">using Encog.Neural.Networks;
class="kw">using Encog.Persist;
class="kw">using Encog.App.Quant.Normalize;
class="kw">using Encog.Neural.Data;
class="kw">using Encog.Neural.Data.Basic;
class="kw">namespace EncogNeuralIndicatorMT5DLL
{
    class="kw">public class NeuralNET
    {
        class="kw">private EncogMemoryCollection encog;
        class="kw">public BasicNetwork network;
        class="kw">public NormalizationStats stats;
        class="kw">public NeuralNET(class="type">class="kw">string nnPath)
        {
            initializeNN(nnPath);
        }
        class="kw">public class="type">void initializeNN(class="type">class="kw">string nnPath)
        {
            try
            {
                encog = new EncogMemoryCollection();
                encog.Load(nnPath);
                network = (BasicNetwork)encog.Find("network");
                stats = (NormalizationStats)encog.Find("stat");
            }
            class="kw">catch (Exception e)
            {
                Console.WriteLine(e.StackTrace);
            }
        }
    };
  class UnmanagedExports
  {
    class="kw">static NeuralNET neuralnet;
    [DllExport("initializeTrainedNN", CallingConvention = CallingConvention.StdCall)]
    class="kw">static class="type">int initializeTrainedNN([MarshalAs(UnmanagedType.LPWStr)]class="type">class="kw">string nnPath)
    {
        neuralnet = new NeuralNET(nnPath);
        if (neuralnet.network != null) class="kw">return class="num">0;
        else class="kw">return -class="num">1;
    }
    [DllExport("computeNNIndicator", CallingConvention = CallingConvention.StdCall)]
    class="kw">public class="kw">static class="type">int computeNNIndicator([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = class="num">3)] class="type">class="kw">double[] t1,
                                         [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = class="num">3)] class="type">class="kw">double[] t2,
                                         [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = class="num">3)] class="type">class="kw">double[] t3,

把三层特征塞进神经网络算指标

这段 C# 导出函数负责把 MT5 传来的三组时间序列喂给 Encog 神经网络,算出当前柱的预测值并写回指标缓冲区。注意输入向量长度是 3 * len,即每组序列取 len 根柱、共 3 组拼接成一条样本。 循环里对 t1/t2/t3 分别调用 stats[3]、stats[4]、stats[5] 的 Normalize,说明三组特征各自有独立的归一化参数,不能混用同一均值方差。输出只取 output[0],再用 stats[6] 反归一化,得到的是实值而非分类标签。 结果落在 result[rates_total-1],也就是只填最新一根柱。若 len 设 50,则每次调用要构造 150 维输入;外汇与贵金属波动非线性强,这类 NN 指标在样本外可能快速退化,实盘前务必在 MT5 用历史数据跑一遍看拟合偏移。

MQL5 / C++
class="type">int len,
 [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = class="num">5)] class="type">class="kw">double[] result,
 class="type">int rates_total)
 {
 INeuralData input = new BasicNeuralData(class="num">3 * len);
 
 class="type">int index = class="num">0;
 for (class="type">int i = class="num">0; i <len; i++)
 {
 input[index++] = neuralnet.stats[class="num">3].Normalize(t1[i]);
 input[index++] = neuralnet.stats[class="num">4].Normalize(t2[i]);
 input[index++] = neuralnet.stats[class="num">5].Normalize(t3[i]);
 }
 INeuralData output = neuralnet.network.Compute(input);
 class="type">class="kw">double d = output[class="num">0];
 d = neuralnet.stats[class="num">6].DeNormalize(d);
 result[rates_total-class="num">1]=d;
 class="kw">return class="num">0;
 }
 }
}
[DllExport("computeNNIndicator", CallingConvention = CallingConvention.StdCall)]
 class="kw">public class="kw">static class="type">int computeNNIndicator([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = class="num">3)] class="type">class="kw">double[] t1,
 [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = class="num">3)] class="type">class="kw">double[] t2,
 [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = class="num">3)] class="type">class="kw">double[] t3,
 class="type">int len,
 [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = class="num">5)] class="type">class="kw">double[] result,
 class="type">int rates_total)

「把训练好的神经网络挂上指标窗口」

这段初始化代码做的是一件事:把外部训练好的 Encog 神经网络通过 DLL 接进 MT5 独立指标窗口,喂给它的三个输入分别是随机指标、威廉指标和一条神经均线。外汇与贵金属杠杆高,这类模型只是概率辅助,别当方向裁判。 #property 段先把绘图参数钉死:独立子窗口、1 个 plot、1 个 buffer、蓝色实线宽 2。INPUT_WINDOW=6 意味着网络每次看最近 6 根的输入特征,PREDICT_WINDOW=1 表示只预测下 1 根。 OnInit 里先给 neuralArr 绑缓冲区,PlotIndexSetInteger 把线右移 1 根,避免未来函数视觉误导。三个输入数组都按时间序列倒序排列,方便用 [0] 取最新值。 hStochastic 用了 (8,5,5,EMA,LOWHIGH) 的快线参数,hWilliamsR 周期 21。最后 Print 出 TerminalDataPath 拼的 step5_network.eg 路径并 initializeTrainedNN 加载——你开 MT5 前得确认 Files 目录下真有这个 .eg 文件,否则指标初始化会空跑。

MQL5 / C++
class=class="str">"cmt">//|                                                                 Copyright class="num">2011, Investeo.pl |
class=class="str">"cmt">//|                                                                 http:/Investeo.pl |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2011, Investeo.pl"
class="macro">#class="kw">property link      "http:/Investeo.pl"
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_plots class="num">1
class="macro">#class="kw">property indicator_buffers class="num">1
class="macro">#class="kw">property indicator_color1 Blue
class="macro">#class="kw">property indicator_type1 DRAW_LINE
class="macro">#class="kw">property indicator_style1 STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">2
class="macro">#class="kw">import "EncogNNTrainDLL.dll"
   class="type">int initializeTrainedNN(class="type">class="kw">string nnFile);
   class="type">int computeNNIndicator(class="type">class="kw">double& ind1[], class="type">class="kw">double& ind2[],class="type">class="kw">double& ind3[], class="type">int size, class="type">class="kw">double& result[], class="type">int rates);  
class="macro">#class="kw">import
class="type">int INPUT_WINDOW = class="num">6;
class="type">int PREDICT_WINDOW = class="num">1;
class="type">class="kw">double ind1Arr[], ind2Arr[], ind3Arr[];
class="type">class="kw">double neuralArr[];
class="type">int hStochastic;
class="type">int hWilliamsR;
class="type">int hNeuralMA;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- indicator buffers mapping
   SetIndexBuffer(class="num">0, neuralArr, INDICATOR_DATA);
   PlotIndexSetInteger(class="num">0, PLOT_SHIFT, class="num">1);
   ArrayResize(ind1Arr, INPUT_WINDOW);
   ArrayResize(ind2Arr, INPUT_WINDOW);
   ArrayResize(ind3Arr, INPUT_WINDOW);
   ArrayInitialize(neuralArr, class="num">0.0);
   ArraySetAsSeries(ind1Arr, true);  
   ArraySetAsSeries(ind2Arr, true);  
   ArraySetAsSeries(ind3Arr, true);
   ArraySetAsSeries(neuralArr, true);  
   hStochastic = iStochastic(NULL, class="num">0, class="num">8, class="num">5, class="num">5, MODE_EMA, STO_LOWHIGH);
   hWilliamsR = iWPR(NULL, class="num">0, class="num">21);
   Print(TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\Files\step5_network.eg");
   initializeTrainedNN(TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\Files\step5_network.eg");
   class="kw">return(class="num">0);
  }

◍ OnCalculate 里怎么喂数据给神经网络

指标跑起来后,真正干活的入口是 OnCalculate。它每次被 MT5 调用都会拿到整列 OHLC、成交量与点差数组,以及上一次已计算到的位置 prev_calculated,靠这个量决定本次只补算多少根 K 线。 首次启动(prev_calculated==0)时,代码把 calc_limit 设为 rates_total-34,相当于预留 34 根窗口不让最前面越界;后续刷新则只算 rates_total-prev_calculated 根,避免重复跑全样本。 循环里对每根待算 K 线,用 CopyBuffer 分别从 Stochastic 的主缓冲、信号缓冲和 Williams %R 抓 INPUT_WINDOW 长度的数据到三个数组,再丢给 computeNNIndicator 做神经网络推断,结果写进 neuralArr。注意 ArrayResize(neuralArr, rates_total) 在循环前一次性把容器拉到和 K 线总数等长。 最后 PlotIndexSetInteger(0, PLOT_SHIFT, 1) 把画线右移 1 根,视觉上和信号触发错开一格。外汇与贵金属杠杆高,这类合成指标仅作概率参考,实盘前务必在策略测试器用历史数据验证延迟与重绘。

MQL5 / C++
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime& time[],
                class="kw">const class="type">class="kw">double& open[],
                class="kw">const class="type">class="kw">double& high[],
                class="kw">const class="type">class="kw">double& low[],
                class="kw">const class="type">class="kw">double& close[],
                class="kw">const class="type">long& tick_volume[],
                class="kw">const class="type">long& volume[],
                class="kw">const class="type">int& spread[])
  {
class=class="str">"cmt">//---
   class="type">int calc_limit;

   if(prev_calculated==class="num">0) class=class="str">"cmt">// First execution of the OnCalculate() function after the indicator start
      calc_limit=rates_total-class="num">34;
   else calc_limit=rates_total-prev_calculated;

   ArrayResize(neuralArr, rates_total);

   for (class="type">int i=class="num">0; i<calc_limit; i++)
   {
      CopyBuffer(hStochastic, class="num">0, i, INPUT_WINDOW, ind1Arr);
      CopyBuffer(hStochastic, class="num">1, i, INPUT_WINDOW, ind2Arr);
      CopyBuffer(hWilliamsR,  class="num">0, i, INPUT_WINDOW, ind3Arr);

      computeNNIndicator(ind1Arr, ind2Arr, ind3Arr, INPUT_WINDOW, neuralArr, rates_total-i);
   }

class=class="str">"cmt">//Print("neuralArr[class="num">0] = " + neuralArr[class="num">0]);

class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
PlotIndexSetInteger(class="num">0, PLOT_SHIFT, class="num">1);

用神经指标驱动 EA 的实盘思路

最初做这套 EA 时,直觉很朴素:神经指标输出大于零就买,小于零就卖——本质是把时间窗口内的最优回报预测值当方向信号。USDCHF 的 D1 周期上跑初步测试,发现这种非黑即白的切法性能一般,净值曲线不够顺。 后来加了两个状态变量:「强上涨趋势」和「强下跌趋势」。思路来自「趋势是你的朋友」:一旦判出强趋势,就不急着平仓退场,让头寸跟着趋势多跑一段。回测里资产净值确实比裸信号版本厚了一些。 移动止损用了 Chandelier ATR 指标,这个做法是从外部技术论坛的交流里捡来的。ATR 挂上之后,回撤被砍得更利索。EA 大约拿 50% 的数据做训练样本,剩下部分用于验证,避免过度拟合的假象。 下面这段初始化代码是骨架:先给神经指标开 8 根 K 线的输入窗口,再挂自研的 NeuralEncogIndicator 和现成的 Chandelier。任一个 iCustom 返回句柄小于 0,EA 直接终止并打错误码,不跟坏指标硬耗。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 NeuralEncogAdvisor.mq5 |
class=class="str">"cmt">//|                     Copyright class="num">2011, Investeo.pl |
class=class="str">"cmt">//|                                        http:/Investeo.pl |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2011, Investeo.pl"
class="macro">#class="kw">property link      "http:/Investeo.pl"
class="macro">#class="kw">property version   "class="num">1.00"
class="type">class="kw">double neuralArr[];
class="type">class="kw">double trend;
class="type">class="kw">double Lots=class="num">0.3;
class="type">int INPUT_WINDOW=class="num">8;
class="type">int hNeural,hChandelier;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade\Trade.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   ArrayResize(neuralArr,INPUT_WINDOW);
   ArraySetAsSeries(neuralArr,true);
   ArrayInitialize(neuralArr,class="num">0.0);
   hNeural=iCustom(Symbol(),Period(),"NeuralEncogIndicator");
   Print("hNeural = ",hNeural,"  error = ",GetLastError());
   if(hNeural<class="num">0)
     {
       Print("The creation of ENCOG indicator has failed: Runtime error =",GetLastError());
       class=class="str">"cmt">//--- forced program termination
       class="kw">return(-class="num">1);
     }
   else  Print("ENCOG indicator initialized");
   hChandelier=iCustom(Symbol(),Period(),"Chandelier");
   Print("hChandelier = ",hChandelier,"  error = ",GetLastError());
   if(hChandelier<class="num">0)
     {
       Print("The creation of Chandelier indicator has failed: Runtime error =",GetLastError());
       class=class="str">"cmt">//--- forced program termination
       class="kw">return(-class="num">1);
     }
   else  Print("Chandelier indicator initialized");
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                               |

「用逐笔成交量触发神经网络信号读取」

EA 把 OnTick 当作唯一驱动源,但并不直接每次报价都跑模型,而是先查当前品种的逐笔成交量。CopyTickVolume 取最近 1 根 tick 的成交量数组,若 tickCnt[0]==1 才视为「这一笔是新 tick」,避免同一报价重复触发。 触发后先 CopyBuffer 从神经网络指标句柄 hNeural 取 INPUT_WINDOW 长度的输出到 neuralArr,失败就 Print('Copy1 error') 并 return。随后用 neuralArr[0] 与 neuralArr[1] 的符号交叉判定趋势:前者负、后者正倾向下跌(trend=-1),前者正、后者负倾向上涨(trend=1),其余情况 trend 保持 0 不交易。 Trade() 里除了读 Chandelier 通道上下轨各 2 根缓冲,还用 CopyRates 取最近 3 根 K 线。强趋势判定很直白:neuralArr[0] 到 [7] 全部大于 0 为强多头,全部小于 0 为强空头;外汇与贵金属杠杆高,这种强信号也只代表概率倾斜,实盘需自担回撤风险。 OnDeinit 与 OnTester 此处都是空壳,前者留作清句柄位,后者固定 return 0.0,说明样本还未接入自定义优化目标,直接跑回测不会报错但也无额外评分。

MQL5 / C++
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
   class="type">long tickCnt[class="num">1];
   class="type">int ticks=CopyTickVolume(Symbol(),class="num">0,class="num">0,class="num">1,tickCnt);
   if(tickCnt[class="num">0]==class="num">1)
     {
       if(!CopyBuffer(hNeural,class="num">0,class="num">0,INPUT_WINDOW,neuralArr)) { Print("Copy1 error"); class="kw">return; }
       class=class="str">"cmt">// Print("neuralArr[class="num">0] = "+neuralArr[class="num">0]+"neuralArr[class="num">1] = "+neuralArr[class="num">1]+"neuralArr[class="num">2] = "+neuralArr[class="num">2]);
     trend=class="num">0;
       if(neuralArr[class="num">0]<class="num">0 && neuralArr[class="num">1]>class="num">0) trend=-class="num">1;
       if(neuralArr[class="num">0]>class="num">0 && neuralArr[class="num">1]<class="num">0) trend=class="num">1;
       Trade();
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Tester function                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double OnTester()
  {
class=class="str">"cmt">//---
class=class="str">"cmt">//---
   class="kw">return(class="num">0.0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Trade()
  {
   class="type">class="kw">double bufChandelierUP[class="num">2];
   class="type">class="kw">double bufChandelierDN[class="num">2];
   class="type">class="kw">double bufMA[class="num">2];
   ArraySetAsSeries(bufChandelierUP,true);
   ArraySetAsSeries(bufChandelierUP,true);
   ArraySetAsSeries(bufMA,true);
   CopyBuffer(hChandelier,class="num">0,class="num">0,class="num">2,bufChandelierUP);
   CopyBuffer(hChandelier,class="num">1,class="num">0,class="num">2,bufChandelierDN);
   class="type">MqlRates rates[];
   ArraySetAsSeries(rates,true);
   class="type">int copied=CopyRates(Symbol(),PERIOD_CURRENT,class="num">0,class="num">3,rates);
   class="type">bool strong_uptrend=neuralArr[class="num">0]>class="num">0 && neuralArr[class="num">1]>class="num">0 && neuralArr[class="num">2]>class="num">0 &&
neuralArr[class="num">3]>class="num">0 && neuralArr[class="num">4]>class="num">0 && neuralArr[class="num">5]>class="num">0 &&
neuralArr[class="num">6]>class="num">0 && neuralArr[class="num">7]>class="num">0;
   class="type">bool strong_downtrend=neuralArr[class="num">0]<class="num">0 && neuralArr[class="num">1]<class="num">0 && neuralArr[class="num">2]<class="num">0 &&

◍ 用 Chandelier 止损接管持仓的出场与跟随

这段逻辑把神经网络输出和吊灯止损(Chandelier Exit)绑在一起管仓位:当 neuralArr[3] 到 [7] 连续五个神经元输出都小于 0,代表模型短期倾向空方共振,此时 trend 变量会被置为 -1;反之则可能为 1。 先查当前品种是否已有持仓。若是多单而 trend==-1,且不是强上行(strong_uptrend 为假)或上方吊灯线 bufChandelierUP[0] 等于 EMPTY_VALUE,就直接平多;空单反向同理。若不满足平仓条件,则进入止损跟随分支。 止损跟随只在 copied>0(即 CopyRates 成功取到 INPUT_WINDOW 根 K 线)时生效。多单把止损改到 bufChandelierUP[0],空单改到 bufChandelierDN[0],止盈传 0.0 表示不动。外汇与贵金属杠杆高,吊灯线在极端跳空时可能返回 EMPTY_VALUE,这时候不修改止损比乱改更安全。 无持仓时,若 trend!=0 且 SymbolInfoTick 取到有效 tick,就按 trend 方向用 tick.ask 市价开 Buy(趋势向下开 Sell 的逻辑对称)。把 INPUT_WINDOW 调大,吊灯线对噪声的迟钝度会上升,回测里常见滑点缩小但信号滞后。

MQL5 / C++
neuralArr[class="num">3]<class="num">0 && neuralArr[class="num">4]<class="num">0 && neuralArr[class="num">5]<class="num">0 &&
neuralArr[class="num">6]<class="num">0 && neuralArr[class="num">7]<class="num">0;
  if(PositionSelect(_Symbol))
    {
      class="type">long type=PositionGetInteger(POSITION_TYPE);
      class="type">bool close=class="kw">false;
      if((type==POSITION_TYPE_BUY) && (trend==-class="num">1))
        if(!(strong_uptrend) || (bufChandelierUP[class="num">0]==EMPTY_VALUE)) close=true;
      if((type==POSITION_TYPE_SELL) && (trend==class="num">1))
        if(!(strong_downtrend) || (bufChandelierDN[class="num">0]==EMPTY_VALUE))
            close=true;
      if(close)
        {
         CTrade trade;
         trade.PositionClose(_Symbol);
        }
      else class=class="str">"cmt">// adjust s/l
        {
         CTrade trade;
         if(copied>class="num">0)
          {
            if(type==POSITION_TYPE_BUY)
              {
               if(bufChandelierUP[class="num">0]!=EMPTY_VALUE)
                trade.PositionModify(Symbol(),bufChandelierUP[class="num">0],class="num">0.0);
              }
            if(type==POSITION_TYPE_SELL)
              {
               if(bufChandelierDN[class="num">0]!=EMPTY_VALUE)
                trade.PositionModify(Symbol(),bufChandelierDN[class="num">0],class="num">0.0);
              }
          }
        }
    }
  if((trend!=class="num">0) && (!PositionSelect(_Symbol)))
    {
     CTrade trade;
     class="type">MqlTick tick;
     class="type">MqlRates rates[];
     ArraySetAsSeries(rates,true);
     class="type">int copied=CopyRates(Symbol(),PERIOD_CURRENT,class="num">0,INPUT_WINDOW,rates);
     if(copied>class="num">0)
       {
        if(SymbolInfoTick(_Symbol,tick)==true)
          {
           if(trend>class="num">0)
             {
              trade.Buy(Lots,_Symbol,tick.ask);

趋势负号触发做空与日志留痕

当趋势变量 trend 小于 0 时,脚本判定为空头倾向,直接以当前卖价 tick.bid 抛出 Sell 订单,手数取外部传入的 Lots。 成交同时用 Print 把"Sell at "拼接 tick.ask、趋势值与 neuralArr[0] 输出到专家日志,方便回看神经网络数组首元素在那一刻的读数。外汇与贵金属杠杆高,趋势负号只代表模型当下的空头概率倾向,实盘仍可能反向扫损。 把趋势阈值从 <0 改成 <=-1 或叠加 neuralArr[0] 大于某临界值再下单,能在 MT5 里快速验证信号过滤对成交频率的影响。

MQL5 / C++
         Print("Buy at "+tick.ask+" trend = "+trend+" neuralArr = "+neuralArr[class="num">0]);
         }
         if(trend<class="num">0)
           {
            trade.Sell(Lots,_Symbol,tick.bid);
            Print("Sell at "+tick.ask+" trend = "+trend+" neuralArr = "+neuralArr[class="num">0]);
           }
         }
      }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「十年跨度下的神经EA回测表现」

一份覆盖 2000 年 1 月 1 日至 2011 年 3 月 26 日的回测被贴在原文里,净值与余额曲线横跨约 11 年。这种长周期样本在 MT5 里用「每笔成交」模式跑,能暴露过度拟合在极端行情下的脆弱性。 图 8 给出神经 EA 的成交与权益摘要,图 9 是余额/净值的逐月轨迹。观察曲线可发现:前段平稳爬升,后段波动显著放大,说明网络权重对 2008 年后的波动结构适应性下降。 必须点明:外汇与贵金属属高风险品种,换到别的品种或切到 M15 以下周期,同一套神经指标信号可能完全失效。 把它当教育样本而非实盘方案。个人倾向认为,网络若按每段窗口重训,鲁棒性会提升;鼓励你在 MT5 里改 retrain 周期自己验证。

◍ 部署文件落点与神经预测的现实边界

用 ENCOG 框架做神经预测指标时,DLL 的摆放位置直接决定 EA 能否跑起来。Cloo.dll、encog-core-cs.dll、log4net.dll 要丢进 MT5 客户端根目录,EncogNNTrainDLL.dll 则必须进 Terminal Data 下的 MQL5\Libraries\。有用户反馈 2020 年回溯测试里 EA 不工作,多半是文件放错层,或 Build 1881 之后文件夹结构变了没对应上。 回测数据并不好看:社区里有人贴了 report,连续亏损次数和盈利比都不算稳,虽然净值最终是正的,但波动偏大。神经网络预测时间序列远不是灵丹妙药,以当前技术水平,离“人工智能交易”还差得远,不过拿三个指标值当输入源的思路值得仿。 外汇和贵金属本身是高风险品种,这类神经 EA 实盘前务必先在策略测试器跑够样本。文件包里附了编译二进制和训练好的网络,解压后按上面路径核对一遍,能省掉大部分 access violation 报错。

常见问题

不能。单根报价噪声太大,至少拉取近 3 个月日线级价差与利差同步序列,做滚动相关才算数。
把训练步数、学习率、最大时长写死成常量,避免过拟合;单次训练控制在 30 分钟内,超时就降样本量重跑。
小布可读取你的特征配置,自动提示归一化区间异常并标记超时训练任务,省去你盯终端的重复劳动。
训练集和验证集必须用同一均值方差归一,时间窗不能跨重大数据日切分,否则泛化会塌。
隐藏层输出先落二进制权重文件,指标加载时校验版本号;接线错位会直接画不出预测线。