English Русский Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
使用带 ENCOG 机器学习框架的 MetaTrader 5 指标进行时间序列预测

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

MetaTrader 5示例 | 8 一月 2014, 09:56
6 012 2
investeo
investeo

简介

本文将介绍如何将 MetaTrader 5 连接到 Heaton Research 开发的 ENCOG - 高级神经网络和机器学习框架。我知道有一些以前讲述过的方法让 MetaTrader 能够使用机器学习技术:FANN、NeuroSolutions、Matlab 和 NeuroShell。我希望 ENCOG 将是一个补充解决方案,因为它是牢固可靠并设计良好的代码。

为什么我选择 ENCOG?有几个原因。

  1. ENCOG 在其他两个商业交易软件包中得到应用。其中一个基于 C#,另一个基于 JAVA。这意味着它已经被测试,用于预测金融时间序列数据。
  2. ENCOG 是免费的开源软件。如果您希望了解在一个神经网络内会发生什么事情,您可以浏览源代码。这是我为了理解时间序列预测问题而做的实际工作。C# 是清晰且易于理解的编程语言。
  3. ENCOG 有着完善的文档说明。Heaton Research 的创始人 Heaton 先生提供了有关神经网络、机器学习和使用 ENCOG 预测将来数据的免费在线课程。在撰写本文之前,我参加了他的很多课程。它们对我理解人工神经网络提供了很多帮助。此外,在 Heaton Research 网站上还有关于用 JAVA 和 C# 对 ENCOG 进行编程的电子书。可于在线获得完整的 ENCOG 说明文档。
  4. ENCOG 并不是一个已经停止维护的项目。在撰写本文时,ENCOG 2.6 仍然在开发中。最近发布了 ENCOG 3.0 路线图。
  5. ENCOG 非常强大。它设计出色,可以使用多个 CPU 内核和多线程来加快神经网络计算。部分代码开始专为 OpenCL(支持 GPU 的计算)而移植。
  6. ECNOG 目前支持的功能:

机器学习类型

神经网络架构

训练技术激活函数随机化技术
  • 范围随机化
  • 高斯随机数
  • 扇入
  • Nguyen-Widrow

计划具备的功能:

如您所见,这是一个相当长的功能列表。

本篇介绍性文章着重于采用弹性传播 (RPROP) 训练的前馈神经网络架构。它还涵盖数据编制基础 - 针对时间序列预测的时间定量和常态化。

让我能够撰写本文的知识是以可在 Heaton Research 网站上获得的教程以及有关在 NinjaTrader 中预测金融时间序列的最新文章为基础的。请注意,ENCOG 以 JAVA 和 C# 为基础。如果没有我以前的文章《使用非托管导出将 C# 代码运用到 MQL5》,就不可能写出本文。此解决方案让 C# DLL 作为 Metatrader 5 指标和 ENCOG 时间序列预测程序之间的桥梁使用成为可能。


1. 使用技术指标值作为神经网络的输入

人工神经网络是试图模拟大脑的神经网络的人体工程算法。

有各种各样的神经算法可用,并且存在各种各样的神经网络架构。研究领域是如此广泛,因此有专门的著作来分别介绍每种神经网络。因为此类详细程度超出了本文的范围,我只能建议阅读 Heaton Research 的教程或阅读相关主题的著作。我将专注于前馈神经网络的输入和输出,并且试图描述金融时间序列预测的实例。

为了开始预测金融时间序列,不得不思考我们应该向神经网络提供什么以及我们期待从中得到什么。在大多数抽象黑箱思维中,我们通过在指定有价证券的合约中做多或做空,并在一段时间后平仓来实现盈亏。

通过观察一个有价证券的过去价格和技术指标的值,我们尝试预测未来市场情绪以及价格的方向,以买入或卖出合约,并且确保我们的决策不是通过掷硬币的方式做出的。情况看起来或多或少与下图类似:

图 1. 使用技术指标预测金融时间序列

图 1. 使用技术指标预测金融时间序列 

我们将尝试用人工智能实现相同的结果。神经网络将尝试识别指标值并决定价格是否存在上扬或下跌的机会。我们如何实现这一目的呢?由于我们将使用前馈神经网络架构来预测金融时间序列,我认为我们需要对其架构进行一次介绍。

前馈神经网络由按层分组的神经元组成。至少必须要有 2 层:包含输入神经元的输入层和包含输出神经元的输出层。在输入层和输出层之间还可以有隐藏层。输入层可被简单地视为一个双精度值数组,而输出层可以由一个或多个同时构成双精度值数组的神经元组成。请看下图:

 图 2. 前馈神经网络层

图 2. 前馈神经网络层 

为了简化绘图,没有绘制神经元之间的连接。输入层中的每个神经元都连接到隐藏层中的一个神经元。隐藏层中的每个神经元都连接到输入层中的一个神经元。

每个连接有其权重,该值也是一个双精度值,还有带阈值的激活函数,该函数负责激活神经元并将信息传递到下一神经元。这是为什么它被称为“前馈”网络的原因 - 基于激活神经元的输出的信息从神经元的一层向前传输到另一层。欲观看有关前馈神经网络的详细视频介绍,可以点击以下链接:

在您了解神经网络架构及其机制之后,您可能仍有迷惑。

主要的问题包括:

  1. 我们应该向神经网络提供什么数据?
  2. 我们如何提供?
  3. 如何为神经网络准备输入数据?
  4. 如何选择神经网络架构?我们需要多少输入神经元、隐藏神经元和输出神经元?
  5. 如何训练网络?
  6. 期待有什么输出?

 

2. 要向神经网络提供什么数据

因为我们依据指标输出进行金融预测,因此我们应向网络提供指标输出值。对于本文,我选择随机指标 %K随机慢速指标 %D威廉指标 %R 作为输入。

图 3. 用于预测的技术指标

图 3. 用于预测的技术指标

为了提取指标的值,我们可以使用 iStochasticiWPR 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 货币对来预测另一货币对。

 

3. 时间定量输入数据

从几个指标收集输入数据之后,我们需要在将其提供给神经网络之前对输入进行时间定量 (timebox)。时间定量 (Timeboxing) 是一种允许作为移动的数据片断向网络提供输入的技术。您可以想象一个在时间轴上向前移动的输入数据移动盒。此过程基本上涉及两个步骤:

1. 从各个指标缓存收集输入数据。我们需要从开始位置向将来位置复制 INPUT_WINDOW 个元素。输入窗口是用于预测的柱的数量。

 图 4. 从指标缓存收集输入窗口数据

图 4. 从指标缓存收集输入窗口数据 

如以上示例所示,INPUT_WINDOW 等于 4 根柱,并且我们将元素复制到 I1 数组。I1[0] 是第一个元素,I1[3] 是最后一个元素。类似地,必须将数据从其他指标复制到大小为 INPUT_WINDOW 的数组。对于将 AS_SERIES 标记设置为 true 的时间序列数组,此数字是有效的。

2. 将 INPUT_WINDOW 个数组合并为要提供给神经网络输入层的一个数组。

图 5. 时间定量后的输入窗口数组 

图 5. 时间定量后的输入窗口数组

有 3 个指标,首先我们取每个指标的第一个值,接着是每个指标的各第二个值,直到输入窗口被填满,如上图所示。从指标输出合并的数组可被提供给神经网络的输入层。新的柱到来时,数据被一个元素接一个元素地分割,并且重复整个过程。如果您有兴趣详细了解为准备预测数据,还可以观看相关主题的视频

 

4. 对输入数据进行正态化

为了使神经网络有效,我们必须对数据进行正态化。这是激活函数的正确计算所需要的。正态化是一种数学处理,将数据转换为 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;
        }

以及阅读有关正态化的文章以获得更多信息。

 

5. 选择网络架构和神经元的数量

对于本主题的菜鸟而言,选择正确的网络架构是一件困难的事情。在本文中,我将前馈神经网络架构限制为三层:一个输入层、一个隐藏层和一个输出层。您可以任意试验更多的层。

对于输入层和输出层,我们能够精确地统计需要的神经元的数量。对于隐藏层,我们将尝试通过前向选择算法最大程度地减少神经网络错误。建议您使用其他方法;可能有一些用于计算神经元数量的遗传算法。

ENCOG 使用的另一方法被称为后向选择算法或修剪法,它基本上是评估层与层的连接并删除连接权重为零的隐藏神经元,您也许会希望尝试一下。

5.1. 输入神经元层

由于时间定量,输入层中的神经元的数量应等于指标的数量乘以用于预测下一根柱的柱的数量。如果我们使用 3 个指标作为输入,并且输入窗口大小等于 6 根柱,则输入层将包含 18 个神经元。输入层被馈以时间定量准备的数据。

5.2. 隐藏神经元层

必须依据训练后神经网络的性能估计隐藏网络的数量。对于隐藏神经元的数量,没有直接的数学方程。在撰写本文之前,我使用过多种试错法,并且我在 Heaton Research 网站上找到一种有助于理解前向选择算法的算法:

图 6. 用于计算隐藏神经元数量的前向选择算法 

图 6. 用于计算隐藏神经元数量的前向选择算法 

5.3. 输出神经元层

出于我们的目的,输出神经元的数量等于我们尝试预测的柱的数量。请记住,隐藏神经元和输出神经元的数量越大,网络训练的时间就越长。在本文中,我尝试预测将来的一根柱,因此输出层包含一个神经元。

 

6. 将训练数据从 MetaTrader 5 导出到 ENCOG

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

您可以修改第二个示例以轻松生成满足您的需要的脚本。


7. 神经网络训练

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);

请注意,有时由于神经元的数量太少,网络可能不能被训练到某一误差。


8. 使用训练后的神经网络建立 MetaTrader 5 神经指标

训练后的网络可供一个尝试预测最佳投资回报的神经网络指标使用。

针对 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 神经指标

图 7. Encog 神经指标

指标显示下一根柱上的预测最佳投资回报。

您可能已经注意到我将指标往将来方向偏移了一根柱:

PlotIndexSetInteger(0, PLOT_SHIFT, 1);

这样做表明指标是一个预测值。因为我们构建一个神经指标,我们准备好依据该指标构建一个 EA 交易。


9. 基于神经指标的 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% 的数据来自训练样本。


10. EA 交易回测结果

我在下面粘贴了回测结果。回测采用的数据从 2000 年 1 月 1 日至 2011 年 3 月 26 日。

图 8. 神经网络 EA 交易回测结果

图 8. 神经网络 EA 交易回测结果

图 9. 神经网络 EA 交易余额/资产净值回测图

图 9. 神经网络 EA 交易余额/资产净值回测图

请注意,对于其他时间框和其他有价证券,此性能可能完全不同。

请将此 EA 作为教育用途来对待,使其成为进一步研究的起点。我的个人观点是网络可在每段时间进行重新训练,从而使其更加强大,可能有人会找到,或者已经找到实现此目标的好方法。或许有更好的方式来依据神经指标做出买入/卖出预测。我鼓励读者进行试验。


总结

在接下来的文章中,我提出了在 ENCOG 机器学习框架的帮助下构建神经预测指标并依据该指标构建 EA 交易的一种方法。本文还附带了所有源代码、编译后的二进制文件、DLL 和一个可仿效的经过训练的网络。


因为“.NET 中的双重 DLL 包装”,Cloo.dllencog-core-cs.dlllog4net.dll 文件应位于客户端的文件夹中。
EncogNNTrainDLL.dll 文件应位于 \Terminal Data folder\MQL5\Libraries\ folder 中。


本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/252

附加的文件 |
encogcsharp.zip (2202.77 KB)
files.zip (270.14 KB)
libraries.zip (321.62 KB)
experts.zip (1.56 KB)
scripts.zip (1.03 KB)
indicators.zip (2.24 KB)
最近评论 | 前往讨论 (2)
Aipe Li
Aipe Li | 22 12月 2016 在 09:15

HI,nvesteo

I downloaded the sample code, with MT5 loading neuralencogindicator display exception, ask for help.


Zhiqiang Zhu
Zhiqiang Zhu | 12 7月 2018 在 10:54

从回测report来看。并不是非常好的EA。无论是连续亏损次数,盈利比,都不是很好。虽然是盈利,但不是很稳定。

结论是:神经网络算法预测时间序列不是灵丹妙药,以目前的技术水平,还远没有达到人工智能的水平。

不过,想法不错,特别是以三个指标值作为神经网络的输入源。

统计估计 统计估计
对某个序列的统计参数进行估计非常重要,因为大多数数学模型和方法均基于不同的假设。例如,正态分布规律或离差值(或其他参数)就是这样。因此,在分析和预测时间序列时,我们需要一个简单方便的工具,用于快速清晰地估计主要统计参数。本文简要说明了一个随机序列的最简单统计参数,以及其可视分析的几种方法。本文还说明了如何在 MQL5 中实现这些方法,以及使用 Gnuplot 应用程序对计算结果进行可视化的方法。
用于预测市场价格的通用回归模型 用于预测市场价格的通用回归模型
市场价格是缺乏需求和供应之间的稳定平衡而形成的,反之,又取决于各种各样的经济、政治和心理因素。这些因素的性质以及影响原因所存在的差异,使得直接考虑所有因素非常困难。本文提出一种依据精心设计的回归模型预测市场价格的尝试。
在 MetaTrader 5 中使用自组织特征映射(Kohonen 映射) 在 MetaTrader 5 中使用自组织特征映射(Kohonen 映射)
自组织特征映射(Kohonen 映射)最有趣的方面是,其在不受监督的情况下就可学会对数据进行分类。就其基本形式而言,该映射能够生成输入数据的一个类似映射(聚类)。可使用 SOM 映射实现高维数据的分类和可视化。在本文中,我们将讨论 Kohonen 映射的几个简单应用。
使用非托管导出将 C# 代码运用到 MQL5 使用非托管导出将 C# 代码运用到 MQL5
在本文中,我介绍了在 MQL5 代码和托管 C# 代码之间进行互动的不同方法。我还提供了几个例子来说明如何针对 C# 封送 MQL5 结构以及如何在 MQL5 脚本中调用导出的 DLL 函数。我相信提供的例子能用作以后研究用托管代码编写 DLL 的基础。本文也为 MetaTrader 使用已经在 C# 中实施了的多个库打开了大门。