本文将介绍如何将 MetaTrader 5 连接到 Heaton Research 开发的 ENCOG - 高级神经网络和机器学习框架。我知道有一些以前讲述过的方法让 MetaTrader 能够使用机器学习技术:FANN、NeuroSolutions、Matlab 和 NeuroShell。我希望 ENCOG 将是一个补充解决方案,因为它是牢固可靠并设计良好的代码。
为什么我选择 ENCOG?有几个原因。
机器学习类型
- 前馈和简单递归 (Elman/Jordan)
- 遗传算法
- NEAT
- 概率神经网络/广义回归神经网络 (PNN/GRNN)
- 自组织映射 (SOM/Kohonen)
- 模拟退火算法
- 支持向量机
神经网络架构
训练技术
- ADALINE 神经网络
- 自适应谐振理论 1 (ART1)
- 双向联系记忆 (BAM)
- 玻尔兹曼机
- 反向传播神经网络 (CPN)
- 埃尔曼递归神经网络
- 前馈神经网络 (Perceptron)
- 霍普菲尔德神经网络
- 乔丹递归神经网络
- 增量拓扑神经演化 (NEAT)
- 径向基函数网络
- 递归自组织映射 (RSOM)
- 自组织映射 (Kohonen)
激活函数
- 反向传播
- 弹性传播 (RPROP)
- 量化共轭梯度 (SCG)
- 曼哈顿更新规则传播
- 竞争学习
- 霍普菲尔德学习
- 莱文贝格-马夸特算法 (LMA)
- 遗传算法训练
- 内星训练
- 外星训练
- ADALINE 训练
- 训练数据模型
- 有监督
- 无监督
- 时间(预测)
- 财经(从 Yahoo Finance 下载)
- SQL
- XML
- CSV
- 图像缩减采样
随机化技术
- 竞争函数
- Sigmoid 函数
- 双曲正切函数
- 线性函数
- SoftMax 函数
- 正切函数
- 正弦波函数
- 阶梯函数
- 双极函数
- 高斯函数
- 范围随机化
- 高斯随机数
- 扇入
- Nguyen-Widrow
计划具备的功能:
- HyperNEAT
- 受限波兹曼机 (RBN/Deep Belief)
- 脉冲神经网络
如您所见,这是一个相当长的功能列表。
本篇介绍性文章着重于采用弹性传播 (RPROP) 训练的前馈神经网络架构。它还涵盖数据编制基础 - 针对时间序列预测的时间定量和常态化。
让我能够撰写本文的知识是以可在 Heaton Research 网站上获得的教程以及有关在 NinjaTrader 中预测金融时间序列的最新文章为基础的。请注意,ENCOG 以 JAVA 和 C# 为基础。如果没有我以前的文章《使用非托管导出将 C# 代码运用到 MQL5》,就不可能写出本文。此解决方案让 C# DLL 作为 Metatrader 5 指标和 ENCOG 时间序列预测程序之间的桥梁使用成为可能。
人工神经网络是试图模拟大脑的神经网络的人体工程算法。
有各种各样的神经算法可用,并且存在各种各样的神经网络架构。研究领域是如此广泛,因此有专门的著作来分别介绍每种神经网络。因为此类详细程度超出了本文的范围,我只能建议阅读 Heaton Research 的教程或阅读相关主题的著作。我将专注于前馈神经网络的输入和输出,并且试图描述金融时间序列预测的实例。
为了开始预测金融时间序列,不得不思考我们应该向神经网络提供什么以及我们期待从中得到什么。在大多数抽象黑箱思维中,我们通过在指定有价证券的合约中做多或做空,并在一段时间后平仓来实现盈亏。
通过观察一个有价证券的过去价格和技术指标的值,我们尝试预测未来市场情绪以及价格的方向,以买入或卖出合约,并且确保我们的决策不是通过掷硬币的方式做出的。情况看起来或多或少与下图类似:
图 1. 使用技术指标预测金融时间序列
我们将尝试用人工智能实现相同的结果。神经网络将尝试识别指标值并决定价格是否存在上扬或下跌的机会。我们如何实现这一目的呢?由于我们将使用前馈神经网络架构来预测金融时间序列,我认为我们需要对其架构进行一次介绍。
前馈神经网络由按层分组的神经元组成。至少必须要有 2 层:包含输入神经元的输入层和包含输出神经元的输出层。在输入层和输出层之间还可以有隐藏层。输入层可被简单地视为一个双精度值数组,而输出层可以由一个或多个同时构成双精度值数组的神经元组成。请看下图:
图 2. 前馈神经网络层
为了简化绘图,没有绘制神经元之间的连接。输入层中的每个神经元都连接到隐藏层中的一个神经元。隐藏层中的每个神经元都连接到输入层中的一个神经元。
每个连接有其权重,该值也是一个双精度值,还有带阈值的激活函数,该函数负责激活神经元并将信息传递到下一神经元。这是为什么它被称为“前馈”网络的原因 - 基于激活神经元的输出的信息从神经元的一层向前传输到另一层。欲观看有关前馈神经网络的详细视频介绍,可以点击以下链接:
在您了解神经网络架构及其机制之后,您可能仍有迷惑。
主要的问题包括:
因为我们依据指标输出进行金融预测,因此我们应向网络提供指标输出值。对于本文,我选择随机指标 %K、随机慢速指标 %D 和威廉指标 %R 作为输入。
图 3. 用于预测的技术指标
为了提取指标的值,我们可以使用 iStochastic 和 iWPR MQL5 函数:
double StochKArr[], StochDArr[], WilliamsRArr[]; ArraySetAsSeries(StochKArr, true); ArraySetAsSeries(StochDArr, true); ArraySetAsSeries(WilliamsRArr, true); int hStochastic = iStochastic(Symbol(), Period(), 8, 5, 5, MODE_EMA, STO_LOWHIGH); int hWilliamsR = iWPR(Symbol(), Period(), 21); CopyBuffer(hStochastic, 0, 0, bufSize, StochKArr); CopyBuffer(hStochastic, 1, 0, bufSize, StochDArr); CopyBuffer(hWilliamsR, 0, 0, bufSize, WilliamsRArr);
在执行此代码之后,三个数组 StochKArr、StochDArr 和 WilliamsRArr 应被填以指标的输出值。视训练样本的大小而定,这可能多达几千个值。请记住,选择这两个指标仅仅是出于教育目的。
建议用您发现的适合预测的任何指标进行试验。您可能希望向网络提供黄金和原油价格以预测股指,或者您可能使用关联后的 forex 货币对来预测另一货币对。
从几个指标收集输入数据之后,我们需要在将其提供给神经网络之前对输入进行时间定量 (timebox)。时间定量 (Timeboxing) 是一种允许作为移动的数据片断向网络提供输入的技术。您可以想象一个在时间轴上向前移动的输入数据移动盒。此过程基本上涉及两个步骤:
1. 从各个指标缓存收集输入数据。我们需要从开始位置向将来位置复制 INPUT_WINDOW 个元素。输入窗口是用于预测的柱的数量。
图 4. 从指标缓存收集输入窗口数据
如以上示例所示,INPUT_WINDOW 等于 4 根柱,并且我们将元素复制到 I1 数组。I1[0] 是第一个元素,I1[3] 是最后一个元素。类似地,必须将数据从其他指标复制到大小为 INPUT_WINDOW 的数组。对于将 AS_SERIES 标记设置为 true 的时间序列数组,此数字是有效的。
2. 将 INPUT_WINDOW 个数组合并为要提供给神经网络输入层的一个数组。
图 5. 时间定量后的输入窗口数组
有 3 个指标,首先我们取每个指标的第一个值,接着是每个指标的各第二个值,直到输入窗口被填满,如上图所示。从指标输出合并的数组可被提供给神经网络的输入层。新的柱到来时,数据被一个元素接一个元素地分割,并且重复整个过程。如果您有兴趣详细了解为准备预测数据,还可以观看相关主题的视频。
为了使神经网络有效,我们必须对数据进行正态化。这是激活函数的正确计算所需要的。正态化是一种数学处理,将数据转换为 0..1 或 -1..1 的范围。正态化后的数据可以进行去正态化,即转换回原来的范围。
要将神经网络输出解码为人类可读的形式,需要对数据进行去正态化。谢天谢地,ENCOG 负责标准化和去标准化,因此不需要实施它。如果您对它的工作原理感到好奇,您可以分析以下代码:
/** * Normalize the specified value. * @param value The value to normalize. * @return The normalized value. */ public static double normalize(final int value) { return ((value - INPUT_LOW) / (INPUT_HIGH - INPUT_LOW)) * (OUTPUT_HIGH - OUTPUT_LOW) + OUTPUT_LOW; } /** * De-normalize the specified value. * @param value The value to denormalize. * @return The denormalized value. */ public static double deNormalize(final double data) { double result = ((INPUT_LOW - INPUT_HIGH) * data - OUTPUT_HIGH * INPUT_LOW + INPUT_HIGH * OUTPUT_LOW) / (OUTPUT_LOW - OUTPUT_HIGH); return result; }
以及阅读有关正态化的文章以获得更多信息。
对于本主题的菜鸟而言,选择正确的网络架构是一件困难的事情。在本文中,我将前馈神经网络架构限制为三层:一个输入层、一个隐藏层和一个输出层。您可以任意试验更多的层。
对于输入层和输出层,我们能够精确地统计需要的神经元的数量。对于隐藏层,我们将尝试通过前向选择算法最大程度地减少神经网络错误。建议您使用其他方法;可能有一些用于计算神经元数量的遗传算法。
ENCOG 使用的另一方法被称为后向选择算法或修剪法,它基本上是评估层与层的连接并删除连接权重为零的隐藏神经元,您也许会希望尝试一下。
5.1. 输入神经元层
由于时间定量,输入层中的神经元的数量应等于指标的数量乘以用于预测下一根柱的柱的数量。如果我们使用 3 个指标作为输入,并且输入窗口大小等于 6 根柱,则输入层将包含 18 个神经元。输入层被馈以时间定量准备的数据。
5.2. 隐藏神经元层
必须依据训练后神经网络的性能估计隐藏网络的数量。对于隐藏神经元的数量,没有直接的数学方程。在撰写本文之前,我使用过多种试错法,并且我在 Heaton Research 网站上找到一种有助于理解前向选择算法的算法:
图 6. 用于计算隐藏神经元数量的前向选择算法
5.3. 输出神经元层
出于我们的目的,输出神经元的数量等于我们尝试预测的柱的数量。请记住,隐藏神经元和输出神经元的数量越大,网络训练的时间就越长。在本文中,我尝试预测将来的一根柱,因此输出层包含一个神经元。
Encog 接受 CSV 文件用于神经网络训练。
我查看了从其他交易软件导出到 ENCOG 的文件格式并实施了准备相同的文件格式以供训练的 MQL5 脚本。我首先介绍导出一个指标,之后继续介绍多个指标。
数据的第一行是逗号分隔的标题:
DATE,TIME,CLOSE,Indicator_Name1,Indicator_Name2,Indicator_Name3
前三列包含日期、时间和收盘价,接下来的列包含指标名称。训练文件接下来的行应包含逗号分隔数据,且应以科学计数法表示指标值:
20110103,0000,0.93377000,-7.8970208860e-002
请观察下面针对一个指标的现成脚本。
//+------------------------------------------------------------------+ //| ExportToEncog.mq5 | //| Copyright 2011, Investeo.pl | //| http:/Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http:/Investeo.pl" #property version "1.00" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ // Export Indicator values for NN training by ENCOG extern string IndExportFileName = "mt5export.csv"; extern int trainSize = 400; extern int maPeriod = 210; MqlRates srcArr[]; double expBullsArr[]; void OnStart() { //--- ArraySetAsSeries(srcArr, true); ArraySetAsSeries(expBullsArr, true); int copied = CopyRates(Symbol(), Period(), 0, trainSize, srcArr); if (copied!=trainSize) { Print("Not enough data for " + Symbol()); return; } int hBullsPower = iBullsPower(Symbol(), Period(), maPeriod); CopyBuffer(hBullsPower, 0, 0, trainSize, expBullsArr); int hFile = FileOpen(IndExportFileName, FILE_CSV | FILE_ANSI | FILE_WRITE | FILE_REWRITE, ",", CP_ACP); FileWriteString(hFile, "DATE,TIME,CLOSE,BullsPower\n"); Print("Exporting indicator data to " + IndExportFileName); for (int i=trainSize-1; i>=0; i--) { string candleDate = TimeToString(srcArr[i].time, TIME_DATE); StringReplace(candleDate,".",""); string candleTime = TimeToString(srcArr[i].time, TIME_MINUTES); StringReplace(candleTime,":",""); FileWrite(hFile, candleDate, candleTime, DoubleToString(srcArr[i].close), DoubleToString(expBullsArr[i], -10)); } FileClose(hFile); Print("Indicator data exported."); } //+------------------------------------------------------------------+
可用于训练的结果文件应如以下输出所示:
DATE,TIME,CLOSE,BullsPower 20110103,0000,0.93377000,-7.8970208860e-002 20110104,0000,0.94780000,-6.4962292188e-002 20110105,0000,0.96571000,-4.7640374727e-002 20110106,0000,0.96527000,-4.4878854587e-002 20110107,0000,0.96697000,-4.6178012364e-002 20110110,0000,0.96772000,-4.2078647318e-002 20110111,0000,0.97359000,-3.6029181466e-002 20110112,0000,0.96645000,-3.8335729509e-002 20110113,0000,0.96416000,-3.7054869514e-002 20110114,0000,0.96320000,-4.4259373120e-002 20110117,0000,0.96503000,-4.4835729773e-002 20110118,0000,0.96340000,-4.6420936126e-002 20110119,0000,0.95585000,-4.6868984125e-002 20110120,0000,0.96723000,-4.2709941621e-002 20110121,0000,0.95810000,-4.1918330800e-002 20110124,0000,0.94873000,-4.7722659418e-002 20110125,0000,0.94230000,-5.7111591557e-002 20110126,0000,0.94282000,-6.2231529077e-002 20110127,0000,0.94603000,-5.9997865295e-002 20110128,0000,0.94165000,-6.0378312069e-002 20110131,0000,0.94414000,-6.2038328069e-002 20110201,0000,0.93531000,-6.0710334438e-002 20110202,0000,0.94034000,-6.1446445012e-002 20110203,0000,0.94586000,-5.2580791504e-002 20110204,0000,0.95496000,-4.5246755566e-002 20110207,0000,0.95730000,-4.4439392954e-002
回到原来采用随机指标和威廉指标的文章示例,我们需要导出三个逗号分隔列,每列包含单独的指标值,因此我们需要扩展文件并添加其他缓存:
//+------------------------------------------------------------------+ //| ExportToEncog.mq5 | //| Copyright 2011, Investeo.pl | //| http:/Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http:/Investeo.pl" #property version "1.00" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ // Export Indicator values for NN training by ENCOG extern string IndExportFileName = "mt5export.csv"; extern int trainSize = 2000; MqlRates srcArr[]; double StochKArr[], StochDArr[], WilliamsRArr[]; void OnStart() { //--- ArraySetAsSeries(srcArr, true); ArraySetAsSeries(StochKArr, true); ArraySetAsSeries(StochDArr, true); ArraySetAsSeries(WilliamsRArr, true); int copied = CopyRates(Symbol(), Period(), 0, trainSize, srcArr); if (copied!=trainSize) { Print("Not enough data for " + Symbol()); return; } int hStochastic = iStochastic(Symbol(), Period(), 8, 5, 5, MODE_EMA, STO_LOWHIGH); int hWilliamsR = iWPR(Symbol(), Period(), 21); CopyBuffer(hStochastic, 0, 0, trainSize, StochKArr); CopyBuffer(hStochastic, 1, 0, trainSize, StochDArr); CopyBuffer(hWilliamsR, 0, 0, trainSize, WilliamsRArr); int hFile = FileOpen(IndExportFileName, FILE_CSV | FILE_ANSI | FILE_WRITE | FILE_REWRITE, ",", CP_ACP); FileWriteString(hFile, "DATE,TIME,CLOSE,StochK,StochD,WilliamsR\n"); Print("Exporting indicator data to " + IndExportFileName); for (int i=trainSize-1; i>=0; i--) { string candleDate = TimeToString(srcArr[i].time, TIME_DATE); StringReplace(candleDate,".",""); string candleTime = TimeToString(srcArr[i].time, TIME_MINUTES); StringReplace(candleTime,":",""); FileWrite(hFile, candleDate, candleTime, DoubleToString(srcArr[i].close), DoubleToString(StochKArr[i], -10), DoubleToString(StochDArr[i], -10), DoubleToString(WilliamsRArr[i], -10) ); } FileClose(hFile); Print("Indicator data exported."); } //+------------------------------------------------------------------+
结果文件应包含所有指标值:
DATE,TIME,CLOSE,StochK,StochD,WilliamsR 20030707,0000,1.37370000,7.1743119266e+001,7.2390220187e+001,-6.2189054726e-001 20030708,0000,1.36870000,7.5140977444e+001,7.3307139273e+001,-1.2500000000e+001 20030709,0000,1.35990000,7.3831775701e+001,7.3482018082e+001,-2.2780373832e+001 20030710,0000,1.36100000,7.1421933086e+001,7.2795323083e+001,-2.1495327103e+001 20030711,0000,1.37600000,7.5398313027e+001,7.3662986398e+001,-3.9719626168e+000 20030714,0000,1.37370000,7.0955352856e+001,7.2760441884e+001,-9.6153846154e+000 20030715,0000,1.38560000,7.4975891996e+001,7.3498925255e+001,-2.3890784983e+000 20030716,0000,1.37530000,7.5354107649e+001,7.4117319386e+001,-2.2322435175e+001 20030717,0000,1.36960000,7.1775345074e+001,7.3336661282e+001,-3.0429594272e+001 20030718,0000,1.36280000,5.8474576271e+001,6.8382632945e+001,-3.9778325123e+001 20030721,0000,1.35400000,4.3498596819e+001,6.0087954237e+001,-5.4946524064e+001 20030722,0000,1.36130000,2.9036761284e+001,4.9737556586e+001,-4.5187165775e+001 20030723,0000,1.34640000,1.6979405034e+001,3.8818172735e+001,-6.5989159892e+001 20030724,0000,1.34680000,1.0634573304e+001,2.9423639592e+001,-7.1555555556e+001 20030725,0000,1.34400000,9.0909090909e+000,2.2646062758e+001,-8.7500000000e+001 20030728,0000,1.34680000,1.2264922322e+001,1.9185682613e+001,-8.2705479452e+001 20030729,0000,1.35250000,1.4960629921e+001,1.7777331716e+001,-7.2945205479e+001 20030730,0000,1.36390000,2.7553336360e+001,2.1035999930e+001,-5.3979238754e+001 20030731,0000,1.36990000,4.3307839388e+001,2.8459946416e+001,-4.3598615917e+001 20030801,0000,1.36460000,5.6996412096e+001,3.7972101643e+001,-5.2768166090e+001 20030804,0000,1.34780000,5.7070193286e+001,4.4338132191e+001,-8.1833910035e+001 20030805,0000,1.34770000,5.3512705531e+001,4.7396323304e+001,-8.2006920415e+001 20030806,0000,1.35350000,4.4481132075e+001,4.6424592894e+001,-7.1972318339e+001 20030807,0000,1.35020000,3.3740028156e+001,4.2196404648e+001,-7.7681660900e+001 20030808,0000,1.35970000,3.0395426394e+001,3.8262745230e+001,-6.1245674740e+001 20030811,0000,1.35780000,3.4155781326e+001,3.6893757262e+001,-6.4532871972e+001 20030812,0000,1.36880000,4.3488943489e+001,3.9092152671e+001,-4.5501730104e+001 20030813,0000,1.36690000,5.1160443996e+001,4.3114916446e+001,-4.8788927336e+001 20030814,0000,1.36980000,6.2467599793e+001,4.9565810895e+001,-2.5629290618e+001 20030815,0000,1.37150000,6.9668246445e+001,5.6266622745e+001,-2.1739130435e+001 20030818,0000,1.38910000,7.9908906883e+001,6.4147384124e+001,-9.2819614711e+000
您可以修改第二个示例以轻松生成满足您的需要的脚本。
Heaton Research 已经用 C# 准备好了网络的训练。ENCOG 2.6 实施 Encog.App.Quant 名称空间,该空间是金融时间序列预测的基础。训练脚本非常灵活,可以轻松调整到任意数量的输入指标。您应只在 DIRECTORY 常量中更改 MetaTrader 5 目录的位置。
可以通过更改以下变量轻松地自定义网络架构和训练参数:
/// <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> /// Next the entire file is normalized and stored into this file. /// </summary> public const String STEP3_FILENAME = DIRECTORY + "step3_norm.csv"; /// <summary> /// The file is time-boxed to create training data. /// </summary> public const String STEP4_FILENAME = DIRECTORY + "step4_train.csv"; /// <summary> /// Finally, the trained neural network is written to this file. /// </summary> public const String STEP5_FILENAME = DIRECTORY + "step5_network.eg"; /// <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; static void Main(string[] args) { // Step 1: Create future indicators Console.WriteLine("Step 1: Analyze MT5 Export & Create Future Indicators"); ProcessIndicators ind = new ProcessIndicators(); ind.Analyze(STEP1_FILENAME, true, CSVFormat.DECIMAL_POINT); int externalIndicatorCount = ind.Columns.Count - 3; ind.AddColumn(new BestReturn(RESULT_WINDOW,true)); ind.Process(STEP2_FILENAME); Console.WriteLine("External indicators found: " + externalIndicatorCount); //Console.ReadKey(); // Step 2: Normalize Console.WriteLine("Step 2: Create Future Indicators"); EncogNormalize norm = new EncogNormalize(); norm.Analyze(STEP2_FILENAME, true, CSVFormat.ENGLISH); norm.Stats[0].Action = NormalizationDesired.PassThrough; // Date norm.Stats[1].Action = NormalizationDesired.PassThrough; // Time norm.Stats[2].Action = NormalizationDesired.Normalize; // Close norm.Stats[3].Action = NormalizationDesired.Normalize; // Stoch K norm.Stats[4].Action = NormalizationDesired.Normalize; // Stoch Dd norm.Stats[5].Action = NormalizationDesired.Normalize; // WilliamsR norm.Stats[6].Action = NormalizationDesired.Normalize; // best return [RESULT_WINDOW] norm.Normalize(STEP3_FILENAME); // neuron counts int inputNeurons = INPUT_WINDOW * externalIndicatorCount; int outputNeurons = PREDICT_WINDOW; // Step 3: Time-box Console.WriteLine("Step 3: Timebox"); //Console.ReadKey(); TemporalWindow window = new TemporalWindow(); window.Analyze(STEP3_FILENAME, true, CSVFormat.ENGLISH); window.InputWindow = INPUT_WINDOW; window.PredictWindow = PREDICT_WINDOW; int index = 0; window.Fields[index++].Action = TemporalType.Ignore; // date window.Fields[index++].Action = TemporalType.Ignore; // time window.Fields[index++].Action = TemporalType.Ignore; // close for(int i=0;i<externalIndicatorCount;i++) window.Fields[index++].Action = TemporalType.Input; // external indicators window.Fields[index++].Action = TemporalType.Predict; // PredictBestReturn window.Process(STEP4_FILENAME); // Step 4: Train neural network Console.WriteLine("Step 4: Train"); Console.ReadKey(); INeuralDataSet training = (BasicNeuralDataSet)EncogUtility.LoadCSV2Memory(STEP4_FILENAME, inputNeurons, outputNeurons, true, CSVFormat.ENGLISH); BasicNetwork network = new BasicNetwork(); network.AddLayer(new BasicLayer(new ActivationTANH(), true, inputNeurons)); network.AddLayer(new BasicLayer(new ActivationTANH(), true, HIDDEN1_NEURONS)); network.AddLayer(new BasicLayer(new ActivationLinear(), true, outputNeurons)); network.Structure.FinalizeStructure(); network.Reset(); //EncogUtility.TrainToError(network, training, TARGET_ERROR); EncogUtility.TrainConsole(network, training, 3); // Step 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.TrainToError() 改为 EncogUtility.TrainConsole()
EncogUtility.TrainConsole(network, training, 3);
TrainConsole 方法指定训练网络的分钟数。在这个例子中,我训练网络三分钟。视网络的复杂性和训练数据的大小而定,训练网络可能需要几分钟、几小时甚至几天。我建议在 Heaton Research 网站上或任何其他相关主题的著作中更加详细地了解误差计算和训练算法。
EncogUtility.TrainToError() 方法在达到目标网络误差之后停止训练网络。您可以注释 EncongUtiliy.TrainConsole() 及取消 EncogUtility.TrainToError() 的注释以将网络训练到需要的误差,如原来的示例一样。
EncogUtility.TrainToError(network, training, TARGET_ERROR);
请注意,有时由于神经元的数量太少,网络可能不能被训练到某一误差。
训练后的网络可供一个尝试预测最佳投资回报的神经网络指标使用。
针对 MetaTrader 5 的 ENCOG 神经指标由两个部分组成。一个部分以 MQL5 编写,它采用的指标与训练网络所用的指标基本相同,并且向网络提供输入窗口指标值。第二个部分以 C# 编写,它对输入数据进行时间定量,并将神经网络输出返回到 MQL5。C# 指标部分以我先前关于“将 C# 代码运用到 MQL5”的文章为基础。
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, int len, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)] double[] result, int rates_total) { INeuralData input = new BasicNeuralData(3 * len); int index = 0; for (int i = 0; i <len; i++) { input[index++] = neuralnet.stats[3].Normalize(t1[i]); input[index++] = neuralnet.stats[4].Normalize(t2[i]); input[index++] = neuralnet.stats[5].Normalize(t3[i]); } INeuralData output = neuralnet.network.Compute(input); double d = output[0]; d = neuralnet.stats[6].DeNormalize(d); result[rates_total-1]=d; return 0; } } }
如果您喜欢使用三个指标以外的任意数量的指标,您需要更改 computeNNIndicator() 方法以满足您的需要。
[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, int len, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)] double[] result, int rates_total)
在这种情形中,前三个输入参数是包含指标输入值的表,第四个参数是输入窗口长度。
SizeParamIndex = 3 指向输入窗口长度变量,因为输入变量是从 0 开始向上计数的。第五个参数是一个包含神经网络结果的表。
MQL5 指标部分需要导入 C# EncogNNTrainDLL.dll,并且使用从 dll 导出的 initializeTrainedNN() 和 computeNNIndicator() 函数。
//+------------------------------------------------------------------+ //| NeuralEncogIndicator.mq5 | //| Copyright 2011, Investeo.pl | //| http:/Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http:/Investeo.pl" #property version "1.00" #property indicator_separate_window #property indicator_plots 1 #property indicator_buffers 1 #property indicator_color1 Blue #property indicator_type1 DRAW_LINE #property indicator_style1 STYLE_SOLID #property indicator_width1 2 #import "EncogNNTrainDLL.dll" int initializeTrainedNN(string nnFile); int computeNNIndicator(double& ind1[], double& ind2[],double& ind3[], int size, double& result[], int rates); #import int INPUT_WINDOW = 6; int PREDICT_WINDOW = 1; double ind1Arr[], ind2Arr[], ind3Arr[]; double neuralArr[]; int hStochastic; int hWilliamsR; int hNeuralMA; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0, neuralArr, INDICATOR_DATA); PlotIndexSetInteger(0, PLOT_SHIFT, 1); ArrayResize(ind1Arr, INPUT_WINDOW); ArrayResize(ind2Arr, INPUT_WINDOW); ArrayResize(ind3Arr, INPUT_WINDOW); ArrayInitialize(neuralArr, 0.0); ArraySetAsSeries(ind1Arr, true); ArraySetAsSeries(ind2Arr, true); ArraySetAsSeries(ind3Arr, true); ArraySetAsSeries(neuralArr, true); hStochastic = iStochastic(NULL, 0, 8, 5, 5, MODE_EMA, STO_LOWHIGH); hWilliamsR = iWPR(NULL, 0, 21); Print(TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\Files\step5_network.eg"); initializeTrainedNN(TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\Files\step5_network.eg"); //--- return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int& spread[]) { //--- int calc_limit; if(prev_calculated==0) // First execution of the OnCalculate() function after the indicator start calc_limit=rates_total-34; else calc_limit=rates_total-prev_calculated; ArrayResize(neuralArr, rates_total); for (int i=0; i<calc_limit; i++) { CopyBuffer(hStochastic, 0, i, INPUT_WINDOW, ind1Arr); CopyBuffer(hStochastic, 1, i, INPUT_WINDOW, ind2Arr); CopyBuffer(hWilliamsR, 0, i, INPUT_WINDOW, ind3Arr); computeNNIndicator(ind1Arr, ind2Arr, ind3Arr, INPUT_WINDOW, neuralArr, rates_total-i); } //Print("neuralArr[0] = " + neuralArr[0]); //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
请查看使用 USDCHF 每日数据和随机指标及威廉指标 %R训练得出的指标输出:
图 7. Encog 神经指标
指标显示下一根柱上的预测最佳投资回报。
您可能已经注意到我将指标往将来方向偏移了一根柱:
PlotIndexSetInteger(0, PLOT_SHIFT, 1);
这样做表明指标是一个预测值。因为我们构建一个神经指标,我们准备好依据该指标构建一个 EA 交易。
EA 交易采用神经指标输出并决定是买入还是卖出有价证券。我的第一印象是,在指标大于零时应买入,在指标小于零时应卖出,即在某个时间窗口的最佳回报预测值为正时买入,在最佳回报预测值为负时卖出。
在经过某些初步测试之后,发现性能应更好一些,因此我引入了“强上涨趋势”和“强下跌趋势”变量,即依据著名的“趋势是你的朋友”原则, 在强趋势中我们没有理由退出市场。
此外,我在 Heaton Research 论坛上获得了使用 ATR 进行移动止损的建议,因此使用了在 MQL5 论坛中找到的 Chandelier ATR 指标。在进行回测时,资产净值确实增加了。我在下面粘贴了 EA 交易的源代码。
//+------------------------------------------------------------------+ //| NeuralEncogAdvisor.mq5 | //| Copyright 2011, Investeo.pl | //| http:/Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http:/Investeo.pl" #property version "1.00" double neuralArr[]; double trend; double Lots=0.3; int INPUT_WINDOW=8; int hNeural,hChandelier; //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArrayResize(neuralArr,INPUT_WINDOW); ArraySetAsSeries(neuralArr,true); ArrayInitialize(neuralArr,0.0); hNeural=iCustom(Symbol(),Period(),"NeuralEncogIndicator"); Print("hNeural = ",hNeural," error = ",GetLastError()); if(hNeural<0) { Print("The creation of ENCOG indicator has failed: Runtime error =",GetLastError()); //--- forced program termination return(-1); } else Print("ENCOG indicator initialized"); hChandelier=iCustom(Symbol(),Period(),"Chandelier"); Print("hChandelier = ",hChandelier," error = ",GetLastError()); if(hChandelier<0) { Print("The creation of Chandelier indicator has failed: Runtime error =",GetLastError()); //--- forced program termination return(-1); } else Print("Chandelier indicator initialized"); //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- long tickCnt[1]; int ticks=CopyTickVolume(Symbol(),0,0,1,tickCnt); if(tickCnt[0]==1) { if(!CopyBuffer(hNeural,0,0,INPUT_WINDOW,neuralArr)) { Print("Copy1 error"); return; } // Print("neuralArr[0] = "+neuralArr[0]+"neuralArr[1] = "+neuralArr[1]+"neuralArr[2] = "+neuralArr[2]); trend=0; if(neuralArr[0]<0 && neuralArr[1]>0) trend=-1; if(neuralArr[0]>0 && neuralArr[1]<0) trend=1; Trade(); } } //+------------------------------------------------------------------+ //| Tester function | //+------------------------------------------------------------------+ double OnTester() { //--- //--- return(0.0); } //+------------------------------------------------------------------+ void Trade() { double bufChandelierUP[2]; double bufChandelierDN[2]; double bufMA[2]; ArraySetAsSeries(bufChandelierUP,true); ArraySetAsSeries(bufChandelierUP,true); ArraySetAsSeries(bufMA,true); CopyBuffer(hChandelier,0,0,2,bufChandelierUP); CopyBuffer(hChandelier,1,0,2,bufChandelierDN); MqlRates rates[]; ArraySetAsSeries(rates,true); int copied=CopyRates(Symbol(),PERIOD_CURRENT,0,3,rates); bool strong_uptrend=neuralArr[0]>0 && neuralArr[1]>0 && neuralArr[2]>0 && neuralArr[3]>0 && neuralArr[4]>0 && neuralArr[5]>0 && neuralArr[6]>0 && neuralArr[7]>0; bool strong_downtrend=neuralArr[0]<0 && neuralArr[1]<0 && neuralArr[2]<0 && neuralArr[3]<0 && neuralArr[4]<0 && neuralArr[5]<0 && neuralArr[6]<0 && neuralArr[7]<0; if(PositionSelect(_Symbol)) { long type=PositionGetInteger(POSITION_TYPE); bool close=false; if((type==POSITION_TYPE_BUY) && (trend==-1)) if(!(strong_uptrend) || (bufChandelierUP[0]==EMPTY_VALUE)) close=true; if((type==POSITION_TYPE_SELL) && (trend==1)) if(!(strong_downtrend) || (bufChandelierDN[0]==EMPTY_VALUE)) close=true; if(close) { CTrade trade; trade.PositionClose(_Symbol); } else // adjust s/l { CTrade trade; if(copied>0) { if(type==POSITION_TYPE_BUY) { if(bufChandelierUP[0]!=EMPTY_VALUE) trade.PositionModify(Symbol(),bufChandelierUP[0],0.0); } if(type==POSITION_TYPE_SELL) { if(bufChandelierDN[0]!=EMPTY_VALUE) trade.PositionModify(Symbol(),bufChandelierDN[0],0.0); } } } } if((trend!=0) && (!PositionSelect(_Symbol))) { CTrade trade; MqlTick tick; MqlRates rates[]; ArraySetAsSeries(rates,true); int copied=CopyRates(Symbol(),PERIOD_CURRENT,0,INPUT_WINDOW,rates); if(copied>0) { if(SymbolInfoTick(_Symbol,tick)==true) { if(trend>0) { trade.Buy(Lots,_Symbol,tick.ask); Print("Buy at "+tick.ask+" trend = "+trend+" neuralArr = "+neuralArr[0]); } if(trend<0) { trade.Sell(Lots,_Symbol,tick.bid); Print("Sell at "+tick.ask+" trend = "+trend+" neuralArr = "+neuralArr[0]); } } } } } //+------------------------------------------------------------------+
EA 交易使用 USDCHF 货币 D1 数据。大约 50% 的数据来自训练样本。
我在下面粘贴了回测结果。回测采用的数据从 2000 年 1 月 1 日至 2011 年 3 月 26 日。
图 8. 神经网络 EA 交易回测结果
图 9. 神经网络 EA 交易余额/资产净值回测图
请注意,对于其他时间框和其他有价证券,此性能可能完全不同。
请将此 EA 作为教育用途来对待,使其成为进一步研究的起点。我的个人观点是网络可在每段时间进行重新训练,从而使其更加强大,可能有人会找到,或者已经找到实现此目标的好方法。或许有更好的方式来依据神经指标做出买入/卖出预测。我鼓励读者进行试验。
在接下来的文章中,我提出了在 ENCOG 机器学习框架的帮助下构建神经预测指标并依据该指标构建 EA 交易的一种方法。本文还附带了所有源代码、编译后的二进制文件、DLL 和一个可仿效的经过训练的网络。
因为“.NET 中的双重 DLL 包装”,Cloo.dll、encog-core-cs.dll 和 log4net.dll 文件应位于客户端的文件夹中。
EncogNNTrainDLL.dll 文件应位于 \Terminal Data folder\MQL5\Libraries\ folder 中。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程