在算法交易中 KOHONEN 神经网络的实际应用 第二部分优化和预测

1 三月 2019, 07:17
Stanislav Korotky
0
429

在本文中,我们继续探讨把 Kohonen 网络作为交易者的工具。在第一部分中, 我们修正和改进了公开的神经网络类,增加了必要的算法。现在,是时候在实际应用中使用它们了。在本文中,我们将在解决问题中使用 Kohonen 图,例如选择最优的 EA 参数以及预测时间序列。


搜索最优的 EA 参数

共同原则

在很多交易平台中都解决了机器人优化的问题,包括 MetaTrader。嵌入的测试器提供了各种工具、高级算法、分布式计算和细粒度统计评估。然而,从用户的角度来看,在优化过程中,总是有一个更为关键的最后阶段,即在分析程序生成的大量信息的基础上,选择最终工作参数的阶段。在之前处理 Kohonen 图并在本网站上发布的文章中,提供了可视化分析优化结果的示例。但是,这意味着用户要自己执行专家分析。理想情况下,我们希望从神经网络得到更具体的建议。总之,算法交易是通过程序进行交易,而不涉及用户。

完成优化后,我们通常会收到一份包含许多选项的长测试报告。根据要排序的列,我们从其深度提取绝对不同的设置,这意味着在相关标准上的最佳性,如利润、夏普比率等。即使我们已经确定了我们最信任的标准,系统通常也会提供几个具有相同结果的设置。如何选择呢?

有些交易者采用自己的综合标准,将几个标准指数纳入计算中——使用这种方法,在报告中获得相同序列的可能性实际上更小。然而,事实上,他们将问题转化为上述标准的元优化领域(如何正确选择其公式?),而这是一个单独的主题。因此,我们将返回到分析标准优化结果。

在我看来,选择最佳的EA参数集必须基于搜索目标函数值范围内最长持续的“高原”,而不是基于搜索此类函数的最大值。在交易环境中,“高原”的水平可与平均盈利能力进行比较,而其长度可与可靠性进行比较,即系统的稳健性和稳定性。

在我们有目的地考虑了第一部分中的一些数据分析技术之后,我们建议可以使用聚类来搜索这样的“高原”。

不幸的是,没有统一或通用的方法来获得具有所需特征的集群。特别是,如果集群的数量“太大”,它们就会变小,并显示出重新学习的所有症状——它们很难概括信息。如果有“太少”的集群,那么它们是相当少的训练,所以他们接收到自己的样本是根本不同的。“太”这个词没有任何明确的定义,因为每个任务、数据数量和数据结构都有一个特定的阈值。所以,通常建议进行几次实验。

集群的数量在逻辑上与图的大小和应用的任务相关。在我们的例子中,前一个因素只在一个方向上起作用,因为我们以前决定通过公式(7)设置大小。因此,知道了这个大小,我们就得到了集群数量的上限——它们中的任何一个都不能超过一侧的大小。另一方面,根据应用的任务,只有一对集群可能适合我们:“好”和“坏”设置。这是可以进行实验的范围。所有这些都只适用于基于清楚指示集群数量的算法,如 K-Means。我们的替代算法没有这样的设置,但是,由于按质量排列集群,我们可以从我们的考虑中排除数字高于给定集群的所有集群,

然后我们将尝试使用Kohonen网络执行集群化。然而,在我们开始练习之前,我们必须讨论一个更好的观点。

许多机器人在很大的参数空间内进行了优化,因此,采用遗传算法进行优化,它节省了时间和资源。然而,它有一个特殊的特点,即“陷入”盈利领域。原则上,这就是目的。然而,就 Kohonen 图而言,它不是很好。问题是,Kohonen映射对输入空间中的数据分布很敏感,并实际反映在生成的拓扑中。由于早期遗传算法排除了参数的错误版本,因此它们发生的几率比遗传的好版本要小得多。因此,Kohonen网络可能忽略了在所发现的良好版本附近的目标函数的危险谷。由于市场特征总是波动的,因此避免此类参数是至关重要的,在这些参数中,左或右的步骤会导致损失。

以下是解决问题的方法:

  1. 放弃遗传优化而偏向于全优化,由于不可能完全实现,可以采用层次化的方法,即先大步进行遗传优化,对感兴趣的区域进行局部化,然后在其中进行全优化(然后使用Kohonen网络进行分析),这也是一种想法,要优化的参数列表的过度扩展为系统提供了自由度,这是因为它变得不稳定;第二,优化转化为拟合;因此,建议根据物理平均值为较大部分的参数选择永久值。例如,在基础分析上(如根据策略类型选择时间段:日内策略一天,中期策略一周等),可以减少优化空间,放弃遗传;
  2. 要多次重复遗传算法优化,使用最大值和最小值以及目标函数的零作为标准;例如,可以执行三次优化:
    • 根据获利因子 (PF), 与通常一样;
    • 根据反向质量,即 1/PF;
    • 根据公式 (min(PF, 1/PF) / max(PF, 1/PF)) ,它会收集1附近的统计数据;
    然后,整合所有优化的结果,并将其作为一个统一的整体进行分析;
  3. 这是一个值得研究的半度量:用一个不包括优化指标的度量来构造Kohonen图(实际上,所有的经济指标都不是EA参数);换句话说,在网络教学中,神经元权重和输入之间的相似性的度量必须由选择的组件来计算。与EA参数相关;逆向度量也很有趣,因为接近度量仅由经济指标计算,我们可能会看到参数平面中的拓扑分散,这提供了系统不稳定的证据;在这两种情况下,神经元的权重都是以一种完全的方式进行拟合的 - 根据所有组件。

最后一个版本 N3 意味着网络拓扑将只反映EA参数的分布(或经济指标,取决于方向)。事实上,当我们在优化表的整行上训练网络时,诸如利润、提取和交易数量等列构成了神经元的总分布,其程度不小于EA参数的分布。这有助于对视觉分析问题有一个全面的了解,并得出哪些参数对哪些指标影响最大的结论。但这不利于分析我们的参数,因为它们的实际分布会被经济指标所改变。

原则上,在分析优化结果的情况下,将输入向量分为两个逻辑上分离的组件:EA输入及其索引(输出)。在全矢量的基础上训练一个Kohonen网络,我们试图确定“输入”和“输出”(双向无条件关系)的依赖性。当网络只在一部分功能上学习时,我们可以尝试看到有向关系:如何根据“输入”对“输出”进行聚集,反之亦然,如何根据“输出”对“输入”进行聚集。我们将考虑这两个选项。


SOM-Explorer 的进一步开发

为了实现这一训练模式,需要对 CSOM 和 CSOMNode 类进行进一步的开发。

距离在 CSOMNode 类内部计算,对于类中的所有对象,将特定的组件排除在计算之外是统一的,因此我们将对相关变量进行统计:

    static int dimensionMax;
    static ulong dimensionBitMask;

dimensionMax 可以设置将要计算的神经元权重的最大值,例如,如果空间维数是 5, 而 dimensionMax 等于 4 就表示向量中的最后一个组件在计算中被排除在外。

dimensionBitMask 可以使用位掩码来排除随机位置的组件: 如果第i位等于1, 就处理第i位的组件;如果它等于0,就不处理。

让我们添加静态方法来放置变量:

static void CSOMNode::SetFeatureMask(const int dim = 0, const ulong bitmask = 0)
{
  dimensionMax = dim;
  dimensionBitMask = bitmask;
}

现在,我们将使用新变量更改距离计算:

double CSOMNode::CalculateDistance(const double &vector[]) const
{
  double distSqr = 0;
  if(dimensionMax <= 0 || dimensionMax > m_dimension) dimensionMax = m_dimension;
  for(int i = 0; i < dimensionMax; i++)
  {
    if(dimensionBitMask == 0 || ((dimensionBitMask & (1 << i)) != 0))
    {
      distSqr += (vector[i] - m_weights[i]) * (vector[i] - m_weights[i]);
    }
  }
  return distSqr;
}

现在,我们只需要确保CSOM类以适当的方式对神经元施加限制,让我们在 CSOM 中加入一个类似的公有方法:

void CSOM::SetFeatureMask(const int dim, const ulong bitmask)
{
  m_featureMask = 0;
  m_featureMaskSize = 0;
  if(bitmask != 0)
  {
    m_featureMask = bitmask;
    Print("启用了特性掩码:");
    for(int i = 0; i < m_dimension; i++)
    {
      if((bitmask & (1 << i)) != 0)
      {
        m_featureMaskSize++;
        Print(m_titles[i]);
      }
    }
  }
  CSOMNode::SetFeatureMask(dim == 0 ? m_dimension : dim, bitmask);
}

在测试 EA 中, 我们将创建字符串参数 FeatureMask,用户可以在其中设置一个功能掩码,并对其进行分析以获得“1”和“0”字符:

    ulong mask = 0;
    if(FeatureMask != "")
    {
      int n = MathMin(StringLen(FeatureMask), 64);
      for(int i = 0; i < n; i++)
      {
        mask |= (StringGetCharacter(FeatureMask, i) == '1' ? 1 : 0) << i;
      }
    }
    KohonenMap.SetFeatureMask(0, mask);

所有这些都是在启动 Train 方法之前完成的,因此,在学习期间以及在计算 U-Matrix 矩阵和集群的阶段都会影响距离的计算。然而,在某些情况下,通过其他规则执行集群化对我们来说是很有趣的,例如在没有任何掩码的情况下对网络进行教学,并应用掩码仅查找集群。为此,我们还将在默认情况下引入控制参数ApplyFeatureMaskAfterTraining等于'false'。但是,如果设置为“true”,我们将在 Train 方法后调用 SetFeatureMask。

当我们再次改进我们的工具时,我们将涉及到另外一点,即我们将使用交易机器人的工作设置作为输入向量,

将EA参数的值上传到网络中,直接从设置的文件中进行分析,会更加方便。为了这个目的,我们写下后面的函数

bool LoadSettings(const string filename, double &v[])
{
  int h = FileOpen(filename, FILE_READ | FILE_TXT);
  if(h == INVALID_HANDLE)
  {
    Print("FileOpen 错误 ", filename, " : ",GetLastError());
    return false;
  }
  
  int n = KohonenMap.GetFeatureCount();
  ArrayResize(v, n);
  ArrayInitialize(v, EMPTY_VALUE);
  int count = 0;

  while(!FileIsEnding(h))
  {
    string line = FileReadString(h);
    if(StringFind(line, ";") == 0) continue;
    string name2value[];
    if(StringSplit(line, '=', name2value) != 2) continue;
    int index = KohonenMap.FindFeature(name2value[0]);
    if(index != -1)
    {
      string values[];
      if(StringSplit(name2value[1], '|', values) > 0)
      {
        v[index] = StringToDouble(values[0]);
        count++;
      }
    }
  }
  
  Print("Settings loaded: ", filename, "; features found: ", count);
  
  ulong mask = 0;
  for(int i = 0; i < n; i++)
  {
    if(v[i] != EMPTY_VALUE)
    {
      mask |= (1 << i);
    }
    else
    {
      v[i] = 0;
    }
  }
  if(mask != 0)
  {
    KohonenMap.SetFeatureMask(0, mask);
  }
 
  FileClose(h);
  return count > 0;
}

在其中,我们分析设置文件的字符串,并且在它们之中寻找对应着已训练网络特性的参数名称。相符的设置保存在矢量中,而不相符的设置则被跳过。功能掩码中仅填充可用组件的单位,函数返回设置已被读取这一事实的逻辑特性。

现在,当我们有了需要的这个函数之后,就可以从设置文件中下载参数了,我们将把下面的代码插入到检查是否有 DataFileName != "" 的 if 操作符分支:

      // 以特定模式方式处理 .set 文件
      if(StringFind(DataFileName, ".set") == StringLen(DataFileName) - 4)
      {
        double v[];
        if(LoadSettings(DataFileName, v))
        {
          KohonenMap.AddPattern(v, "SETTINGS");
          ArrayPrint(v);
          double y[];
          CSOMNode *node = KohonenMap.GetBestMatchingFeatures(v, y);
          Print("Matched Node Output (", node.GetX(), ",", node.GetY(),
            "); Hits:", node.GetHitsCount(), "; Error:", node.GetMSE(),
            "; Cluster N", node.GetCluster(), ":");
          ArrayPrint(y);
          KohonenMap.CalculateOutput(v, true);
          hasOneTestPattern = true;
        }
      }

设置将会以 SETTINGS 来做标记。


工作 EA

现在,我们正逐渐接近选择最佳参数的问题。

为了检验聚类优化结果的理论,首先,我们必须创建一个有效的EA。我使用MQL5向导和一些标准模块生成了它,并将其命名为WizardTest(它的源代码附在本文末尾)。这里是输入参数列表:

input string             Expert_Title                 ="WizardTest"; // 文档名称
ulong                    Expert_MagicNumber           =17897;        // 
bool                     Expert_EveryTick             =false;        // 
//--- 主信号的输入参数
input int                Signal_ThresholdOpen         =10;           // open [0...100] 的信号阈值
input int                Signal_ThresholdClose        =10;           //  close [0...100] 的信号阈值
input double             Signal_PriceLevel            =0.0;          // 执行交易的价格水平
input double             Signal_StopLevel             =50.0;         // 止损水平 (点数)
input double             Signal_TakeLevel             =50.0;         // 获利水平 (点数)
input int                Signal_Expiration            =4;            // 挂单的过期时间 (柱数)
input int                Signal_RSI_PeriodRSI         =8;            // 相对强弱指数 RSI (8,...)的计算周期数
input ENUM_APPLIED_PRICE Signal_RSI_Applied           =PRICE_CLOSE;  // 相对强弱指数 RSI(8,...) 价格序列
input double             Signal_RSI_Weight            =1.0;          // 相对强弱指数 RSI(8,...) 权重 [0...1.0]
input int                Signal_Envelopes_PeriodMA    =45;           // Envelopes(45,0,MODE_SMA,...) 平均周期数
input int                Signal_Envelopes_Shift       =0;            // Envelopes(45,0,MODE_SMA,...) 时间偏移
input ENUM_MA_METHOD     Signal_Envelopes_Method      =MODE_SMA;     // Envelopes(45,0,MODE_SMA,...) 平均的方法
input ENUM_APPLIED_PRICE Signal_Envelopes_Applied     =PRICE_CLOSE;  // Envelopes(45,0,MODE_SMA,...) 价格序列
input double             Signal_Envelopes_Deviation   =0.15;         // Envelopes(45,0,MODE_SMA,...) 偏移
input double             Signal_Envelopes_Weight      =1.0;          // Envelopes(45,0,MODE_SMA,...) 权重 [0...1.0]
input double             Signal_AO_Weight             =1.0;          // AO指标权重 [0...1.0]
//--- 用于跟踪止损的输入参数
input double             Trailing_ParabolicSAR_Step   =0.02;         // 速度增加值
input double             Trailing_ParabolicSAR_Maximum=0.2;          // 最大比率
//--- 用于资金管理的输入参数
input double             Money_FixRisk_Percent=5.0;          // 风险百分比

对于我们研究的目标,我们将会只优化几个参数,而不是它们的全部。

Signal_ThresholdOpen
Signal_ThresholdClose
Signal_RSI_PeriodRSI
Signal_Envelopes_PeriodMA
Signal_Envelopes_Deviation
Trailing_ParabolicSAR_Step
Trailing_ParabolicSAR_Maximum

我在从 2018 年 1月到6月 (20180101-20180701) 在 EURUSD D1, M1 OHLC, 根据利润,对 EA 进行了使用遗传算法的优化,包含着要被优化的参数设置的 set-文件附加在本文的末尾 (WizardTest-1.set)。优化结果被保存到wizard2018plus.csv文件中(也附在本文件末尾),其中排除了异常值,特别是交易量低于5且具有超高而不相关的夏普系数的异常值。此外,由于遗传算法优化产生了一个转变,从定义上讲,为盈利的通道,我决定完全排除亏损的部分,只保留利润至少10000的条目。

我还删除了一对索引列,因为它们实际上依赖于其他列,例如余额或预期回报,还有一些其他候选列,例如PF(利润因子)、RF(反弹因子)和夏普因子-高度相互关联,加上,RF与下降呈负相关,但我保留它们以证明它对图的依赖

选择具有能承受最大信息量的最小独立分量集的输入结构是有效利用神经网络的关键条件。

打开包含优化结果的csv文件后,我们将看到我上面提到的有问题的事情:大量字符串包含相同的盈利索引和不同的参数。让我们试着做一个合理的选择。

为了有一个参考点来评估我们将来选择的选项,让我们从优化结果的第一个字符串中查看参数集的正向测试(参见WizardTest-1.set)。测试日期是从2018年7月1日到2018年12月1日。

在首先列出所选设置中的测试报告

在首先列出所选设置中的测试报告

记住这些,实际上,不是很好的数字,我们要带到网络上。


神经网络分析

csv文件中的条目数约为2000,因此,由公式(7)计算的Kohonen网络大小为15。

我们启动CSOM-Explorer,在DataFileName中输入数据文件名(Wizard2018plus.csv),在CellsX和CellsY中输入15,并将EpochNumber保持为100。要同时查看所有平面,我们选择小图像:每一个的ImageW和ImageH为210,而MaxPictures为6。由于执行EA操作,我们将获得以下实质性成果:

关于EA优化结果的Kohonen网络训练

关于EA优化结果的Kohonen网络训练

上排完全由经济指标图组成,第二排和第三排第一张图是EA的工作参数,最后5张图是第一部分构建的特殊图。

让我们仔细看看这些图:

利润, PF, RF, 和工作 EA 的前三个参数

利润, PF, RF, 和工作 EA 的前三个参数

夏普参数,回撤,交易数,以及EA中的后面三个参数

夏普参数,回撤,交易数,以及EA中的后面三个参数

EA的最后一个参数,以及命中计数器、U矩阵、量化误差、集群和网络输出

EA的最后一个参数,以及命中计数器、U矩阵、量化误差、集群和网络输出

让我们人工对获得的图形进行专家分析。

在利润方面,我们可以看到右上角和左上角两个近似相等的区域(红色),右上角更大,突出程度更高。然而,平面的平行分析,与PF,RF,和夏普因子,使我们相信,右上角是最好的选择。这也被较少的回撤所证实。

让我们来看看与EA参数相关的组件,注意右上角。在前五个参数的图形中,该点颜色稳定,允许我们自信地命名最佳值(用鼠标光标指向每个图的右上角神经元,以查看提示中的相关值):

Signal_ThresholdOpen = 40
Signal_ThresholdClose = 85
Signal_RSI_PeriodRSI = 73
Signal_Envelopes_PeriodMA = 20
Signal_Envelopes_Deviation = 0.5

其余两个参数的图会在右上角主动更改颜色,因此不清楚要选择哪个值。从触发计数器的平面来看,最右上方的神经元更可取,在U矩阵和误差图上,确保它是一个“安静的气氛”也是合理的。由于这里一切正常,我们选择以下值:

Trailing_ParabolicSAR_Step = 0.15
Trailing_ParabolicSAR_Maximum = 1.59

让我们以那些参数在相同的时间段、直到2018年12月1日来运行EA (参见 WizardTest-plus-allfeatures-manual.set),我们将取得如下的结果。

在根据 Kohonen 图外观分析基础上所选设置的测试报告

在根据 Kohonen 图外观分析基础上所选设置的测试报告

结果明显优于从第一个字符串中随机选择的结果。然而,它仍然会让专业人士失望,因此值得在这里发表评论。

这是一个测试EA,而不是一个圣杯。它的任务是生成源数据来演示基于神经网络的算法是如何工作的。文章发表后,将有可能在大量实际交易系统上测试这些工具,并可能对它们进行调整以获得更好的使用效果。

让我们将当前结果与可以从相同日期范围内的 csv 文件中获得的所有设置选项的主值进行比较。

所有正向测试的结果

所有正向测试的结果

在正向测试中利润分部的统计

在正向测试中利润分部的统计

统计如下:均值1007,标准差1444,中位数1085,最小值-3813.78,最大值-4202.82。因此,通过专家评估,我们获得了比平均值加标准差更高的利润。

我们的任务是学习如何自动做出相同或定性相似的选择,为此,我们将使用群集化。由于集群按照它们的优先使用顺序进行编号,我们将考虑集群0(不过,基本上,集群1-5可以用于与设置组合进行交易)。

图上的集群的颜色始终与它们的索引编号相关,如下所示:

{clrRed, clrGreen, clrBlue, clrYellow, clrMagenta, clrCyan, clrGray, clrOrange, clrSpringGreen, clrDarkGoldenrod}

这可能有助于在难以区分数字的较小图像上识别它们。

SOM-Explorer 在日志中显示群集中心的坐标,例如,当前图的坐标:

Clusters [20]:
[ 0] "Profit"                        "Profit Factor"                 "Recovery Factor"               "Sharpe Ratio"                 
[ 4] "Equity DD %"                   "Trades"                        "Signal_ThresholdOpen"          "Signal_ThresholdClose"        
[ 8] "Signal_RSI_PeriodRSI"          "Signal_Envelopes_PeriodMA"     "Signal_Envelopes_Deviation"    "Trailing_ParabolicSAR_Step"   
[12] "Trailing_ParabolicSAR_Maximum"
N0 [3,2]
[0] 18780.87080     1.97233     3.60269     0.38653    16.76746    63.02193    20.00378
[7]    65.71576    24.30473    19.97783     0.50024     0.13956     1.46210
N1 [1,4]
[0] 18781.57537     1.97208     3.59908     0.38703    16.74359    62.91901    20.03835
[7]    89.61035    24.59381    19.99999     0.50006     0.12201     0.73983
...

在开始的时候,有一些特性的说明。然后,对于每个集群,都有其编号、X和Y坐标以及特征值。

这里,对于第0个集群,我们有以下工作EA的参数:

Signal_ThresholdOpen=20
Signal_ThresholdClose=66
Signal_RSI_PeriodRSI=24
Signal_Envelopes_PeriodMA=20
Signal_Envelopes_Deviation=0.5
Trailing_ParabolicSAR_Step=0.14
Trailing_ParabolicSAR_Maximum=1.46

我们将使用这些参数(WizardTest-plus-allfeatures-auto-nomask.set)运行正向测试,并得到了报告:

测试器报告从 Kohonen 图群中自动选择的设置,不带功能掩码

测试器报告从 Kohonen 图群中自动选择的设置,不带功能掩码

结果并不比第一次随机试验好多少,明显比专家试验差。这都是关于集群化的质量。目前,所有功能都在同时执行,包括经济指标和EA参数。然而,我们应该寻找“高原”,仅从交易数据来看。让我们应用于仅考虑它们的特性掩码的集群化,即前六个特性掩码。为此,设置:

FeatureMask=1111110000000
FeatureMaskAfterTraining=true

然后重新开始学习。在 SOM-Explorer 中,集群化是在学习的最后阶段进行的,而取得的集群会保存在网络文件中。这对于稍后加载的网络立即用于“识别”新样本是必要的。这些样本可能包含的索引少于所有的索引(即,向量可能不完整),例如在分析集合文件时——它只包含EA的参数,但没有经济索引(而且它们没有意义)。因此,对于这些样本,他们自己的特征掩码是在函数 LoadSettings 中构建的,该掩码对应于向量的现有组件,然后将该掩码应用于网络。因此,为了不与要“识别”的向量的掩码冲突,必须在网络中隐式地存在集群掩码。

但是让我们回到使用新掩码的学习上来。它将改变U矩阵和集群的平面。集群的模式将发生显著变化(左图像在应用掩码之前,右图像在应用掩码之后)。

在 Kohonen 的图上没有(左)和(右)有根据经济指数应用掩码的集群

在 Kohonen 的图上没有(左)和(右)有根据经济指数应用掩码的集群

现在,0集群的数值有所不同了。

N0 [14,1] 
[0] 17806.57263     2.79534     6.78011     0.48506    10.70147    49.90295    40.00000
[7]    85.62392    73.51490    20.00000     0.49750     0.13273     1.29078

如果您还记得,我们在专家评估中选择了位于右上角的神经元,即具有坐标[14,0]。现在,系统为我们提供了相邻的神经元[14,1]。它们的权重没有很大差别,让我们把提出的设置转到工作 EA 的参数中。

Signal_ThresholdOpen=40
Signal_ThresholdClose=86
Signal_RSI_PeriodRSI=74
Signal_Envelopes_PeriodMA=20
Signal_Envelopes_Deviation=0.5
Trailing_ParabolicSAR_Step=0.13
Trailing_ParabolicSAR_Maximum=1.29

我们将会取得以下结果:

使用经济指标页面从Kohonen图群中自动选择设置的测试器报告

使用经济指标页面从Kohonen图群中自动选择设置的测试器报告

不管参数有什么不同,它们都与专家结果相同。

为了便于将集群化结果移动到EA设置中,我们将编写一个助手函数,该函数将生成带有所选集群特性的名称和值的集合文件(默认为零)。函数名为 SaveSettings,如果新参数 SaveClusterAsSettings 包含要导出的群集的索引,则该函数将进入操作。默认情况下,此参数包含-1,这意味着不需要生成设置文件。很明显,这个函数并不“知道”这些特性的应用意义,并将它们全部保存在集合文件中,因此也会有诸如利润、利润因素等交易指数。用户只能从生成的文件中复制与EA的实际参数相对应的功能。参数值保存为实数,因此必须针对整型参数对其进行更正。

既然我们能够以可移植的形式保存找到的设置,那么让我们为集群0(Wizard2018plusCluster0.set)创建设置,并将它们加载回 SOM-Explorer(应该提醒我们的实用程序已经能够读取设置文件)。在参数 NetFileName中,需要指定在上一学习阶段创建的网络的名称(必须是Wizard2018plus.som,因为我们使用了Wizard2018plus.csv的数据-在每个学习周期中,它都保存在文件中,文件名是输入的名称,但扩展名为.som)。在参数 DataFileName中,我们将指定生成的集合文件的名称。标记 SETTINGS 将与集群C0的中心重叠。

我们将用网络重命名SOM文件,因为它在随后的实验中不会被重新写入。

第一个实验如下。我们将通过EA参数(排除交易数据)来教网络“反向”掩码。为此,我们将在参数 DataFileName 中再次指定文件 Wizard2018plus.csv,清除参数 NetFileName 并设置 

FeatureMask=0000001111111

请注意,FeatureMaskAftertraining仍设置为“true”,即该遮罩仅影响群集化。

学习完成后,我们将加载教学网络并在其上测试设置的文件。为此,我们将把创建的网络文件 Wizard2018plus.som 的名称移到参数 NetFileName,并再次将 Wizard2018pluscluster0.set 复制到 DataFileName。总的图集将保持不变,而U矩阵以及集群将变得不同。

聚类结果如下图左侧所示:

Kohonen网络上的集群在EA参数上用一个掩码构建:仅在集群阶段(左侧)和网络学习和集群阶段(右侧)

Kohonen网络上的集群在EA参数上用一个掩码构建:仅在集群阶段(左侧)和网络学习和集群阶段(右侧)

设置的选择是通过它们到达零集群并且规模更大的事实来确认的。

作为第二个实验,我们将使用相同的掩码(通过EA参数)再次教授网络。但是,我们也将在学习阶段进行扩展:

FeatureMaskAfterTraining=false

下面显示了完整的图集,而在上图右侧,集群中的变化显示为更大的比例。在这里,神经元只通过参数的相似性聚集在一起。这些图应理解为:在选定的参数下,预期会出现哪些经济指标?虽然测试设置在集群2中有所下降(它比2差,但不多),但它的大小是最大的,这是一个积极的特性。

由EA参数用掩码构造的Kohonen图

由EA参数用掩码构造的Kohonen图

一位感兴趣的读者会注意到,具有经济指标的图不再具有这样的拓扑性质。但是,现在不需要,因为检查的本质是根据参数识别集群的结构,并确保所选设置位于集群系统中。

一个“反向”的研究将是完全学习和用经济指标掩盖的集群化:

FeatureMask=1111110000000
FeatureMaskAfterTraining=false

这是结果:

用经济指数的掩码构造的 Kohonen 图

用经济指数的掩码构造的 Kohonen 图

现在,对比色集中在经济指数的前六个平面,而参数图则相当无定形。特别是,我们可以看到,参数 Trailing_ParabolicSAR_Step 和 Trailing_ParabolicSAR_Maximum 的值实际上没有改变(图是单调的),这意味着它们可以从优化中排除,分别基于平均值0.11和1.14。在参数图中,Signal_ThresholdOpen 与之形成鲜明对比,从中可以看出,选择 Signal_ThresholdOpen 等于0.4是成功交易的必要条件。U-Matrix 图明显分为上下两个“池”,上下两个“池”代表成功,下两个“池”代表失败。触及图是非常稀缺和不均匀的(空间的最大部分是间隙,而活跃的神经元有一个很大的计数器价值),因为利润是由研究中的EA中的几个明显水平分组的。

这些图应该理解为:在选定的经济指标中,预期会出现哪些参数?

最后,对于上一个实验,我们将稍微开发 SOM-Explorer,使其能够合理地命名集群。让我们编写函数 SetClusterLabels,它将分析每个集群中代码向量的特定组件的值,并将它们与这些组件的值范围进行比较。一旦神经元中的权重值接近最高或最低值,这就是用相关组件的名称标记它的原因。例如,如果第0个关系(对应于利润)的权重超过了其他集群的权重,则表示该集群具有高利润的特点。应该注意的是,极值的符号(最大值或最小值)是由指示的含义决定的:对于利润而言,值越高越好;对于回撤而言,则是相反的。在这方面,我们引入了一个特殊的输入,即FeatureDirection,其中我们将用“+”符号标记具有正效果的指示,用“-”符号标记具有负效果的指示,而那些不重要或没有意义的指示将用“,”符号跳过。请注意,在我们的情况下,只标记经济指标是合理的,因为EA的工作参数值可以是任何值,并且根据其接近定义范围的边界,不会被解释为好或坏。因此,我们只为前六个功能设置FeatureDirection的值:

FeatureDirection=++++-+

这就是标签如何查找在全向量上学习并按经济指标分类的网络(在左侧),以及在EA参数上带有掩码的学习和分类的网络。

集群标签给出了以下情况:在集群化阶段(左侧)使用经济指标掩码,在学习和集群化阶段(右侧)使用参数掩码。

集群标签给出了以下情况:在集群化阶段(左侧)使用经济指标掩码,在学习和集群化阶段(右侧)使用参数掩码。

我们可以看到,在这两种情况下,所选的设置都属于利润因素集群,因此我们可以期望在这一指标中的最高效率。但是,应该考虑到我们这里所有的标签都是阳性的,因为源数据是从遗传优化中获得的,遗传优化是选择可接受选项的过滤器。如果对完全优化的数据进行了分析,那么我们就可以根据集群的性质来识别它们是负的。

因此,我们在一般情况下考虑了使用 Kohonen 图上的聚类搜索EA的最优参数。因为这种方法是灵活的,所以它是复杂的。它意味着使用各种聚类和网络教学方法,以及对输入进行预处理。寻找最佳选择也在很大程度上取决于工作机器人的特殊性。所提供的工具 SOM-Explorer 允许我们开始研究这一部分的数据处理,并对其进行扩展和改进。


优化结果的彩色分类

由于聚类方法和质量严重影响对EA最佳设置的正确搜索,因此,让我们尝试用更简单的方法解决相同的问题,而不评估神经元的接近度。

让我们在RGB颜色模型中显示 Kohonen 图上的特征权重,然后选择最浅的点。由于RGB颜色模型由三个平面组成,因此我们可以用这种方式精确地显示3个特征。如果它们或多或少的存在,那么我们将用灰色渐变来显示亮度,而不是用RGB颜色,但是仍然会找到最浅的点。

需要提醒的是,在选择功能时,最好选择最独立的功能(前面已经提到)。

在实现新方法的过程中,我们将同时演示用我们自己的方法扩展类CSOM的可能性。让我们创建类 CSOMDisplayRGB,在这个类中,我们只重写父类 CSOMDisplay 的一些虚拟方法,并通过这个方法来实现将显示 RGB 映射而不是最后一个 DIM_OUTPUT 平面。

class CSOMDisplayRGB: public CSOMDisplay
{
  protected:
    int indexRGB[];
    bool enabled;
    bool showrgb;

    void CalculateRGB(const int ind, const int count, double &sum, int &col) const;
  
  public:
    void EnableRGB(const int &vector[]);
    void DisableRGB() { enabled = false; };
    virtual void RenderOutput() override;
    virtual string GetNodeAsString(const int node_index, const int plane) const override;
    virtual void Reset() override;
};

完整的代码在附件中。

为了使用这个版本,让我们创建一个 SOM-Explorer-RGB 的修改版,并做以下改动。

增加一个输入参数来启用 RGB 模式:

input bool ShowRGBOutput = false;

图对象将变为派生类:

CSOMDisplayRGB KohonenMap;

我们将在单独的代码分支中实现RGB平面的直接显示:

  if(ShowRGBOutput && StringLen(FeatureDirection) > 0)
  {
    int rgbmask[];
    ArrayResize(rgbmask, StringLen(FeatureDirection));
    for(int i = 0; i < StringLen(FeatureDirection); i++)
    {
      rgbmask[i] = -(StringGetCharacter(FeatureDirection, i) - ',');
    }
    KohonenMap.EnableRGB(rgbmask);
    KohonenMap.RenderOutput();
    KohonenMap.DisableRGB();
  }

它使用了我们已经熟悉的 FeatureDirection 参数,使用它,我们将能够选择要包含在RGB空间中的特定功能(整个功能集的功能)。例如,对于 Wizard2018plus 的示例,只需编写:

FeatureDirection=++,,-

对于前两个特征,即利润和PF,直接进入地图(它们对应于“+”的符号),而第五个特征,即回撤,是一个倒过来的值(它对应于“-”的符号)。对应着 ',' 的功能就被跳过了。所有后续的也不考虑。文件 WizardTest-rgb.set 及其设置随附于此(网络文件 Wizard2018plus.som应自上一阶段起可用)。

这是这些设置的RGB空间显示方式(在左侧)。

功能的RGB空间(利润,PF 和回撤)和(PF,回撤和交易)

功能的RGB空间(利润,PF 和回撤)和(PF,回撤和交易)

颜色与优先顺序中的特征相关:利润为红色,PF为绿色,回撤为蓝色。最亮的神经元标有“RGB”。

记录中显示了“最浅色的”神经元的坐标及其特征值,这些特征值可以作为最佳设置的选项。

由于利润和PF有很强的相关性,我们将用另一个掩码来代替它:

FeatureDirection=,+,,-+

在这种情况下,跳过利润,但增加了交易数量(一些经纪商提供交易量奖金)。结果显示在上图(右侧)。在这里,颜色匹配是不同的:PF是红色,回撤是绿色,交易数量是蓝色。

如果我们只选择功能PF和回撤,我们将获得灰度地图(在左侧):

RGB空间的功能(PF和回撤)和(利润)

RGB空间的功能(PF和回撤)和(利润)

为了进行检查,我们只能选择一个功能,例如利润,并确保黑白模式与第一个平面对应(见上图右侧)。


正向测试的分析

据了解,MetaTrader测试器允许对所选设置进行正向测试来补充EA优化。如果不尝试使用此信息使用 Kohonen 图进行分析,并以这种方式为设置选择特定选项,那将是愚蠢的。从概念上讲,过去和未来高利润地区的集群或彩色区域必须包括提供最稳定收入的设置。

为此,让我们将 WizardTest的优化结果与转发测试结合起来(我应该提醒您,在20180101-20180701 期间对利润进行了优化,而正向测试则相应地在20180701-20181201期间进行了优化),删除其中的所有指示,除了过去和将来的利润,并获取网络的新输入参数文件 - Wizard2018-with-forward.csv(随附)。还附加了设置为wizardtest-with-forward.set的文件。

通过所有功能(包括EA参数)对网络进行教学可得出以下结果:

Kohonen 图,通过所有特征进行正向测试分析,也就是说,利润因子和EA参数

Kohonen 图,通过所有特征进行正向测试分析,也就是说,利润因子和EA参数

我们可以看到,未来利润的面积明显小于过去利润的面积,并且零集群(红色)包含了这个位置。此外,我还包括了两个指示(FeatureDirection=++)的掩码颜色分析,最后一张图上的最浅色点也进入了这个聚类。然而,无论是集群中心还是苍白点都不包含任何超级有利可图的设置——尽管测试结果是正的,但它们比之前获得的结果差两倍。

让我们试着在前两个特征上使用掩码构建一个Kohonen地图,因为只有那些是指示。为此,让我们应用FeatureMask=1100000的其他设置(请参阅 WizardTest-with-forward-mask11.set)并重新连接网络。结果如下。

基于利润因素的正向检验分析的Kohonen图

基于利润因素的正向检验分析的Kohonen图

在这里,我们可以看到前两个平面已经得到了最好的表达空间结构,并跨越了左下角的高值(利润)区域,这在最后一张地图中也被突出显示。从该神经元获取设置后,我们将获得以下结果:

根据“利润稳定”原则选择的设置的测试器报告

根据“利润稳定”原则选择的设置的测试器报告

这同样比之前获得的利润更糟糕。

因此,这个实际的实验不允许我们找到最佳的设置。同时,将正向测试结果纳入神经网络分析似乎是正确的,应该成为一种值得推荐的方法。我们建议在评论中讨论,在任何情况下,成功都缺少哪些细微差别,以及成功是否有保证。或许,所得结果只是证明我们的测试EA不适合稳定的交易。


时间序列预测

概览

ohonen网络最常用于对数据进行可视化分析和聚类。但是,它们也可以用于预测。这个问题意味着输入数据向量代表tic标记,并且它的主要部分通常大小(n-1),n是向量大小,应该用来预测结束,这通常是最新的tic标记。从本质上讲,这个问题类似于恢复噪声或部分丢失数据的问题。然而,它有自己的特异性。

使用Kohonen网络进行预测有不同的方法,这里是其中的几种。

一个Kohonen网络(A)学习具有不完全向量大小(n-1)的数据,即没有最后一个组件。对于第一个网络的每个神经元i,将为一组大小为n的截断全矢量(与网络a的神经元i中显示的不完整矢量相对应)设定一个额外的Kohonen映射(Bi>)处理。之后,在运行阶段,未来的 tick 标记是要被预测的矢量一进入网络A中的神经元i,就根据从网络Bi中获得的概率来模拟的。

另一个选项。在Kohonen图上,A是关于全矢量的。第二个Kohonen图(B)是关于修改向量的,在修改向量中取增量而不是值,即,

yk = xk+1 - xk, k = 1 .. n - 1

其中 yk 和 xk 分别是初始向量和修改向量的分量。显然,修正向量的大小比初始向量小1倍。然后,将教学数据输入两个网络,计算第一个网络中神经元i和第二个网络中神经元j同时显示的全矢量的数目。因此,我们得到了y矢量进入神经元j(在网络B中)的条件概率pij,前提是与之相关的向量x已经进入神经元i(在网络A中)。在操作阶段,网络A由不完整的向量x(没有最后一个分量)馈入,找到最近的神经元i*by(n-1)分量,然后根据概率pi*生成电位增量。与其他方法一样,该方法与蒙特卡罗方法有关,蒙特卡罗方法是根据从教学选择中获得的数据填充神经元的统计数据,生成待预测成分的随机值,并找出最可能的结果。

这个列表可以持续更长的时间,因为它包括了SOM神经元上所谓的参数化SOM和自回归模型,甚至包括嵌入空间内随机吸引子的聚集。

然而,它们或多或少都是基于表征Kohonen网络的矢量量化现象。随着网络的学习,神经元的权重逐渐趋向于它们所代表的类(输入子集)的平均值。这些平均值中的每一个都以与教学选择相同的统计规律,对新数据中缺少的(或预测的未来)值提供了最佳评估。类越紧凑,划分越清楚,评估就越精确。

我们将使用基于矢量量化的最简单预测方法,它涉及到Kohonen网络的单个实例。然而,即使是这一个也会带来一些困难。

我们将向网络输入提供时间序列的全矢量。但是,仅计算第一个(n-1)组件的距离。权重将完全适应所有部件。然后,为了预测的目的,我们将用不完全向量输入网络输入,找到最适合分量(n-1)的神经元,并读取最后一个n个突触的权重值。这将就是预测。

在CSOM类中,我们已经准备好了一切。这里有方法 SetFeatureMask,它在计算距离所涉及的特征空间的尺寸上设置掩码。然而,在实现该算法之前,我们应该决定要预测什么。


聚类指标 Unity

在交易者中,一系列的报价被认为是一个非常重要的时间过程。它包含许多随机性、频繁的相位变化和大量的影响因素,原则上这是一个开放的系统。

为了简化问题,我们将选择一个更大的时间段进行分析,即每日时间段。在那个时间段上,噪音对较小时间段的影响较小。此外,我们还将选择一种工具进行预测,在这种工具上,能够不可估量地推动市场的强劲基本消息相对较少出现。在我看来,最好的是金属,即金和银。它们不是任何特定国家的支付工具,既作为原材料又作为保护性资产,一方面波动性较小,另一方面与货币有着千丝万缕的联系。

理论上,他们未来的动作应该考虑当前的报价,并对外汇报价作出反应。

因此,我们需要一种方法来获得金属和基本货币价格的同步变化,以总和表示。在任何特定时间,这必须是一个包含n个分量的向量,每个分量都对应于一种货币或金属的相对成本。预测的目标在于通过这样一个向量预测其中一个分量的下一个值。

为此,创建了原始的集群指示器统一(其源代码附在此处)。其操作的本质用以下算法描述。让我们考虑尽可能简单地举例说明这一点,即通过一对货币(EURUSD)和黄金(XAUUSD)。

每个tic 标记(当天开始/结束时的当前价格)用明显的公式描述:

EUR / USD = EURUSD

XAU / USD = XAUUSD

其中变量 EUR,USD和XAU是某种资产的独立“成本”,而EURUSD和XAUUSD是常数(已知报价)

为了找到变量,让我们再给系统添加一个方程,将变量的平方和限制为一个单位:

EUR*EUR + USD*USD + XAU*XAU = 1

所以指标的名字叫做 Unity.

通过简单的替换,我们得到:

EURUSD*USD*EURUSD*USD + USD*USD + XAUUSD*USD*XAUUSD*USD = 1

我们在其中找 USD:

USD = sqrt(1 / (1 + EURUSD*EURUSD + XAUUSD*XAUUSD))

以及所有其它变量.

或者,更加通用的表现:

x0 = sqrt(1 / (1 + sum(C(xi, x0)**2))), i = 1..n

xi = C(xi, x0) * x0, i = 1..n

其中n是变量的数量, C(xi,x0) 是第i对的报价,包含相关变量。注意变量的数目比仪器的资产数目高1。

由于计算中涉及的系数c是通常变化很大的引号;在指标中,它们额外乘以合同规模:因此,得出的值或多或少具有可比性(或至少具有相同的数量级)要在指示器窗口中查看它们(仅供参考),需要将名为AbsoluteValues的输入设置为true。默认情况下,它当然等于假,并且指示器总是计算变量的增量:

yi = xi0 / xi1 - 1,

其中 xi0 和 xi1 分别是最后的和倒数第二个柱的值。

我们不会在这里考虑指示器实现的技术细节——您可以独立地研究它的源代码。

因此,我们得到以下结果:

集群 (多币种) 指标 Unity, XAUUSD

集群 (多币种) 指标 Unity, XAUUSD

构成当前图表工作工具的资产行(在本例中为XAU和USD)显示得很宽,而其他行的厚度正常。

指标的输入参数中应当提到以下这些:

  • Instruments — 以逗号分隔的工作工具名称的字符串;所有工具的基础货币或报价货币必须相同;
  • BarLimit — 用于计算的柱数;一旦我们进行神经网络教学,这将成为教学选择的大小;
  • SaveToFile — csv文件的名称,指标将要随后加载到神经网络中的值导出到该文件;文件结构简单:第一列是日期,所有后续列都是相关指标缓冲区的值;
  • ShiftLastBuffer - 切换模式的标志,在该模式下生成csv文件;如果选项为false,则在文件中的每个字符串中显示相同条的数据,列数等于仪器数加上一个(由于将勾号器分为多个组件),再加上一个(第一个)列用于日期;列名c与货币和金属相关;如果选项为真,则添加一个名为forecast的附加列,其中保存最后一项资产的列的值向前移动一天;因此,在每个字符串中,我们都可以看到最后一项工具中当前日和明天的所有数据。

例如,要用数据准备一个文件来预测黄金价格的变化,您应该在参数 Instruments 中指定:“EURUSD, GBPUSD,USDCHF,USDJPY,AUDUSD,USDCAD,NZDUSD,XAUUSD(重要的是将XAUUSD指定为最后一个),而“true”必须在ShiftLastBuffer中指定。我们将获得一个基本上具有以下结构的csv文件:

           datetime;      EUR;     USD;      GBP;      CHF;      JPY;      AUD;      CAD;      NZD;      XAU; FORECAST
2016.12.20 00:00:00; 0.001825;0.000447;-0.000373; 0.000676;-0.004644; 0.003858; 0.004793; 0.000118;-0.004105; 0.000105
2016.12.21 00:00:00; 0.000228;0.003705;-0.001081; 0.002079; 0.002790;-0.002885;-0.003052;-0.002577; 0.000105;-0.000854
2016.12.22 00:00:00; 0.002147;0.003368;-0.003467; 0.003427; 0.002403;-0.000677;-0.002715; 0.002757;-0.000854; 0.004919
2016.12.23 00:00:00; 0.000317;0.003624;-0.002207; 0.000600; 0.002929;-0.007931;-0.003225;-0.003350; 0.004919; 0.004579
2016.12.27 00:00:00;-0.000245;0.000472;-0.001075;-0.001237;-0.003225;-0.000592;-0.005290;-0.000883; 0.004579; 0.003232

请注意,最后两列包含相同的数字,并按一行进行移位。因此,在2016年12月20日的字符串中,我们可以在FORECAST列中看到XAU在当天的增量及其在12月21日的增量。


SOM-Forecast

是时候实现基于Kohonen网络的预测引擎了。首先,为了理解它是如何工作的,我们将基于已经知道的 SOM-Explorer,并使其适应预测问题。

修改是围绕着输入的,让我们删除与设置掩码相关的所有内容:FeatureMask、ApplyFeatureMask、 Aftertraining 和 FeatureDirection,因为预测掩码是已知的-如果矢量大小的n tic标记可用,则只有第一个(n-1)标记应参与计算距离。但是我们要添加一个特殊的逻辑选项 ForecastMode,它允许我们在必要时禁用这个掩码,并使用Kohonen网络的经典分析功能。我们需要这一点来探索市场,或者更确切地说,在静态状态下统一指定的工具系统,即在同一天内看到相关性。

在 ForecastMode等于'true'的情况下,我们放置掩码,如果它是 'false', 就没有掩码。

    if(ForecastMode)
    {
      KohonenMap.SetFeatureMask(KohonenMap.GetFeatureCount() - 1, 0);
    }

一旦网络已经设定,并且在输入数据文件名中指定了包含测试数据的csv文件,那么我们将在 ForecastMode中检查预测质量,如下所示:

      if(ForecastMode)
      {
        int m = KohonenMap.GetFeatureCount();
        KohonenMap.SetFeatureMask(m - 1, 0);
        
        int n = KohonenMap.GetDataCount();
        double vector[];
        double forecast[];
        double future;
        int correct = 0;
        double error = 0;
        double variance = 0;
        for(int i = 0; i < n; i++)
        {
          KohonenMap.GetPattern(i, vector);
          future = vector[m - 1]; // 为将来保留
          vector[m - 1] = 0;      // 使网络的未来未知(由于掩码,它无论如何都不使用)
          KohonenMap.GetBestMatchingFeatures(vector, forecast);
          
          if(future * forecast[m - 1] > 0) // 检查方向是否匹配
          {
            correct++;
          }
          
          error += (future - forecast[m - 1]) * (future - forecast[m - 1]);
          variance += future * future;
        }
        Print("Correct forecasts: ", correct, " out of ", n, " => ", DoubleToString(correct * 100.0 / n, 2), "%, error => ", error / variance);
      }

这里,每个测试向量都使用 GetBestMatchingFeatures呈现给网络,响应是“forecast”向量。将其最后一个分量与测试向量的正确值进行比较。匹配方向计算在“correct”变量中,总的预测“error”是累积的,与数据本身的分散有关

如果是验证集合(ValidationSetPercent参数被填充),方法被设置为方法并且 ReframeNumber 大于零,就在操作中使用 CSOM 的新函数,TrainAndReframe。它允许我们以分阶段的方式增加网络大小,并跟踪验证集中学习错误的变化,以便在错误停止下降并开始增长时立即停止此过程。此时此刻,由于增强了网络的计算能力,使得权重适应特定的向量,从而导致它失去了泛化和处理未知数据的能力。

选择了大小后,我们将 ValidationSetPercent 和 ReframeNumber 重置为0,并像往常一样使用 Train 方法对网络进行教学。

最后,我们将稍微改变集群 SetClusterLabels中标记的功能。由于资产将是特征,并且每一个都可以显示正极端和负极端,因此我们将在标签中包含移动标志。因此,在地图上可以找到两次相同的特征,加上和减上。

void SetClusterLabels()
{
  const int nclusters = KohonenMap.GetClusterCount();

  double min, max, best;
  
  double bests[][3]; // [][0 - value; 1 - feature index; 2 - direction]
  ArrayResize(bests, nclusters);
  ArrayInitialize(bests, 0);
  
  int n = KohonenMap.GetFeatureCount();
  for(int i = 0; i < n; i++)
  {
    int direction = 0;
    KohonenMap.GetFeatureBounds(i, min, max);
    if(max - min > 0)
    {
      best = 0;
      double center[];
      for(int j = nclusters - 1; j >= 0; j--)
      {
        KohonenMap.GetCluster(j, center);
        double value = MathMin(MathMax((center[i] - min) / (max - min), 0), 1);
        
        if(value > 0.5)
        {
          direction = +1;
        }
        else
        {
          direction = -1;
          value = 1 - value;
        }
        
        if(value > bests[j][0])
        {
          bests[j][0] = value;
          bests[j][1] = i;
          bests[j][2] = direction;
        }
      }
    }
  }

  // ...
  
  for(int j = 0; j < nclusters; j++)
  {
    if(bests[j][0] > 0)
    {
      KohonenMap.SetLabel(j, (bests[j][2] > 0 ? "+" : "-") + KohonenMap.GetFeatureTitle((int)bests[j][1]));
    }
  }
}

这样,让我们考虑准备好 SOM-Forecast,现在我们将尝试用它来输入 Unity 指标的值。


分析

首先,让我们尝试在静态的,或者更确切地说,在统计的环境下分析整个市场(所选资产的集合),也就是说,在指标单位严格按天导出的数据上-每个字符串与一个d1条上的指示相对应,没有来自“未来”的任何附加列。

为此,我们将在指示器中指定一组外汇工具、黄金和白银,以及 SaveToFile中的文件名,同时将ShiftLastBuffer设置为“false”。一个以这种方式取得的文件,unity500-noshift.csv,就附加在本文的末尾。

使用 SOM-Forecast(请参见som-forecast-unity500-noshift.set)向网络教授了这些数据后,我们将获得以下的图:

用 D1上的 Unity 指标在 Kohonen 图上的外汇市场、黄金和白银进行可视化分析

用 D1上的 Unity 指标在 Kohonen 图上的外汇市场、黄金和白银进行可视化分析

上面两行是资产图。通过对它们的比较,我们可以确定至少在过去500天内一直工作的永久链接。特别是,两个中心的彩色斑点吸引了眼球:蓝色代表GBP,黄色代表AUD。这意味着这些资产通常处于反向阶段,这只是指强劲的波动,即在市场平均方差水平突破时,英镑兑澳元的销售占主导地位。这种趋势可能会持续下去,并提供了一个向这个方向下待定订单的选项。

XAG 的增长在右上角被观察到,而CHF也位于那里。而在左下角就相反: CHF 上涨, 而 XAG 下跌。这些资产总是在强劲的波动中出现分歧,因此我们可以通过在两个方向上的突破进行交易。和期待的一样,EUR和CHF是类似的。

图上还可以找到其他一些特定的功能。事实上,甚至在我们开始预测之前,我们就有机会在某种程度上预见未来。

让我们仔细看看集群。

根据D1的 Unity 指标,Kohonen图上的外汇、黄金和银团集群

根据D1的 Unity 指标,Kohonen图上的外汇、黄金和银团集群

它们还提供了一些信息,例如,关于通常不会发生的情况(或很少发生这种情况),即:欧元不会随着日元或加元的下跌而增长。英镑通常与瑞士法郎同时增长,反之亦然。

从集群规模来看,欧元、英镑和瑞士法郎的波动性最大。也就是说,它们的变动比其它货币的变化更加频繁,而美元在这方面出人意料地输给了日元和加元(需要提醒的是,我们将变化计算为%)。如果我们假设集群数量也很重要(我们已经对集群进行了排序),那么前三个是+CHF, -JPY, 和 +GBP, 显然,描述最频繁的日常运动(不是趋势,但确切地说,频率是指,因为即使在“平稳”时期,向上的大量“阶梯”也可以通过向下的一个大“阶梯”进行补偿)。

现在,我们将讨论预测问题。


预测

让我们在指示器中指定一组工具,包括外汇和黄金("EURUSD,GBPUSD,USDCHF,USDJPY,AUDUSD,USDCAD,NZDUSD,XAUUSD"),BarLimit中的柱数(默认为500),SaveToFile中的文件名(如unity500xau.csv),并将ShiftLastBuffer标志设置为“true”(创建额外列进行预测的模式)。由于它是一个多货币指标,可用数据的数量仅限于所有仪器中最短的历史记录。在MetaQuotes-Demo 服务器上,有足够数量的柱,至少2000条,用于时间段D1。因此,在这里,考虑另一个问题会很有帮助:将网络教育到如此深度是否合理,因为市场很可能发生了很大的变化。2-3年的历史,甚至1年的历史(在d1上为250个柱),可能更适合于确定当前的规律。

本文件末尾附有Unity500xau.csv文件示例。

要将其加载到SOM-Forecast中,我们将设置以下输入:DataFileName-Unity500Xau、ForecastMode-true、ValidationSetPercent-10,以及重要的:Reframenumber-10。通过这种方式,我们将运行10*10网络大小的教学(默认值),并启用验证选择上的错误检查,并继续通过逐渐增加网络大小进行教学,同时错误减小。在TrainAndReframe方法中,大小将增加10个,每个维度有2个神经元。因此,我们将检测输入的最佳网络大小。含有设置的文件 (som-forecast-unity500xau.set) 在附件中。

在逐步教学和增加网络的同时,日志中显示了以下内容(以节略形式提供):

FileOpen OK: unity500xau.csv
HEADER: (11) datetime;EUR;USD;GBP;CHF;JPY;AUD;CAD;NZD;XAU;FORECAST
Training 10*10 hex net starts
...
Exit by validation error at iteration 104; NMSE[old]=0.4987230270708455, NMSE[new]=0.5021707785446128, set=50
Training stopped by MSE at pass 104, NMSE=0.384537545433749
Training 12*12 hex net starts
...
Exit by validation error at iteration 108; NMSE[old]=0.4094350709134669, NMSE[new]=0.4238670029035179, set=50
Training stopped by MSE at pass 108, NMSE=0.3293719049246978
...
Training 24*24 hex net starts
...
Exit by validation error at iteration 119; NMSE[old]=0.3155973731785412, NMSE[new]=0.3177587737459486, set=50
Training stopped by MSE at pass 119, NMSE=0.1491464262340352
Training 26*26 hex net starts
...
Exit by validation error at iteration 108; NMSE[old]=0.3142964426509741, NMSE[new]=0.3156342534501801, set=50
Training stopped by MSE at pass 108, NMSE=0.1669971604289485
Exit map size increments due to increased MSE
...
Map file unity500xau.som saved

就这样,进程在大小为26*26的网络上停止。表面上,我们应该选择24*24误差最小的位置。但实际上,当我们使用神经网络时,我们总是利用机会。如果您还记得,其中一个参数RandomSeed负责初始化用于在网络中设置初始权重的随机数据生成器。每次改变随机种子,我们都会得到一个具有新特性的新网络。而且,所有其他因素都相等,它会比其他情况更好或更糟。因此,要选择网络大小,以及其他设置,包括教学选择的大小,我们通常要做很多尝试,犯很多错误。然后,我们将遵循这个原则来减少教学数据的选择,并将网络大小减少到15*15。此外,我有时会使用所描述的向量时间量化方法的非规范化修改。修改包括在禁用标志预测模式的情况下对网络进行教学,以及仅在预测时启用该模式。在某些情况下,这会产生积极的影响。与神经网络合作从来没有建议即食餐,只是食谱。实验是不被禁止的。

在教学之前,源文件(在我们的例子中,unity500xau.csv)应该分为2个。问题是,在教学过程中,我们需要对一些数据进行预测质量验证。在网络用于学习的相同数据上做这件事没有任何意义(更确切地说,你只需自己看到正确答案的百分比非常高就行了——你可以检查一下)。因此,让我们复制50个向量,在一个单独的文件中测试它们,同时使用静止的向量来教新的网络。您可以找到所附的相关文件,unity500xau-training.csv和unity500xau-validation.csv。

让我们在参数DataFileName(extension.csv是隐含的)中指定unity500xau training,CellsX和CellsY的值应为24,ValidationSetPercent为0,ReframeNumber为0,ForecastMode仍为“true”(请参见som-forecast-unity500xau-training.set)。 报错经过培训,我们将得到以下图:

用Kohonen网络预测D1上的黄金运动

用Kohonen网络预测D1上的黄金运动

这还不是预测,而是一个视觉确认网络已经学会了一些东西。确切地说,我们将检查是否已将输入与验证文件一起提供。

为此,让我们输入以下设置。文件名unity500xau training现在将传输到参数NetFileName(这是已设定的网络文件名,该文件的隐含扩展名为.som)。在参数DataFileName中,我们指定unity500xau-validation。网络将预测所有向量并在日志中显示统计信息:

Map file unity500xau-training.som loaded
FileOpen OK: unity500xau-validation.csv
HEADER: (11) datetime;EUR;USD;GBP;CHF;JPY;AUD;CAD;NZD;XAU;FORECAST
Correct forecasts: 24 out of 50 => 48.00%, error => 1.02152123166208

唉,预测精度是在随机猜测的水平上。这么简单的输入选择能产生一个圣杯结果吗?可能不行。此外,我们只是猜测历史的深度,而这一问题也必须加以调查。我们还应该使用一套工具,例如添加白银,一般来说,还要澄清哪些资产更依赖于其他资产,以便于预测。下面我们将考虑一个使用白银的例子。

为了在输入中引入更多的上下文,我在指标 Unity 中添加了一个选项,即只根据一天和最后几天形成一个向量。为此,添加了参数BarLookback,默认等于1,与前一个模式相对应。但是,如果我们在这里输入5,那么向量将包含5天内所有资产的tic标记(从根本上讲,一周是基本周期之一)。如果有9项资产(8种货币和黄金),则在csv文件的每个字符串中存储46个值。加起来相当多,计算速度变慢,分析图变得更加困难:即使是小图也很难在屏幕上显示,而较大的图可能比图表上的历史记录要短。

注意,当以对象模式查看如此多的图时,例如OBJ_BITMAP(MaxPictures=0),图表上可能没有足够的条形图:它们是隐藏的,但仍用于链接图像。

为了演示该方法的性能,我决定在3天的回顾时间和15*15大小的网络上进行查看。这里附加了 unity1000xau3-training.csv 和 unity1000xau3-validation.csv 文件,在第一个文件上对网络进行了训练,并在第二个文件上对其进行了验证之后,我们将获得58%的精确预测。

载入 unity1000xau3-training.som 文件
FileOpen OK: unity1000xau3-validation.csv
HEADER: (29) datetime;EUR3;USD3;GBP3;CHF3;JPY3;AUD3;CAD3;NZD3;XAU3;EUR2;USD2;GBP2;CHF2;JPY2;AUD2;CAD2;NZD2;XAU2;EUR1;USD1;GBP1;CHF1;JPY1;AUD1;CAD1;NZD1;XAU1;FORECAST
Correct forecasts: 58 out of 100 => 58.00%, error => 1.131192104823076

这已经足够好了,但是我们不应该忘记网络中进程的随机性:有了其他输入和其他初始化,我们将得到很好的结果,而且可能会更糟。整个系统需要多次重新检查和重新配置。实际上,会生成多个实例,然后选择最佳实例。此外,在做出决定时,我们可以使用一个委员会,而不仅仅是一个网络。


Unity-Forecast

为了进行预测,不需要在屏幕上显示图。在诸如 EA 或指标的MQL程序中,我们可以使用类CSOM而不是CSOMDsplay。让我们创建一个类似于Unity的指标Unity-Forecast,但有一个额外的缓冲区来显示预测。用于获取“未来”值的Kohonen网络可以单独设定(在SOM预测中),然后加载到指标中。或者,我们可以直接在指标中“随时随地”训练网络。让我们实现这两种方式。

为了载入网络,让我们加上输入参数 NetFileName. 为了教授网络,让我们添加类似于SOM-Forecast中包含的参数组:CellsX、CellsY、HexagonalCell、UseNormalization、EpochNumber、ShowProgress 和 RandomSeed。

这里不需要参数 AbsoluteValues,所以让我们用“false”常量替换它。ShiftLastBuffer也没有意义,因为新的指标总是意味着预测。导出到csv文件被排除在外,所以让我们删除参数SaveToFile。相反,我们将添加标记SaveTrainedNetworks:如果为“true”,则指标将把所教网络保存到文件中,以便我们在 SOM-Forecast 中研究它们的图。

参数 BarLimit 将用作开始时显示的柱数和用于训练网络的柱数。

新参数 RetrainingBars允许指定在网络必须重新传输的间隔之后的柱数。

让我们包含一个头文件:

#include <CSOM/CSOM.mqh>

在分时处理函数中,让我们检查一个新的柱的可用性(所有资产中的柱同步都是必需的-在这个演示项目中没有完成),如果是时候更改网络,请从文件 NetFileName 加载它,或在函数 TrainSOM 中的指标数据上进行教学。之后,我们将使用函数 ForecastBySOM进行预测。

  if(LastBarCount != rates_total || prev_calculated != rates_total)
  {
    static int prev_training = 0;
    if(prev_training == 0 || prev_calculated - prev_training > RetrainingBars)
    {
      if(NetFileName != "")
      {
        if(!KohonenMap.Load(NetFileName))
        {
          Print("图载入失败: ", NetFileName);
          initDone = false;
          return 0;
        }
      }
      else
      {
        TrainSOM(BarLimit);
      }
      prev_training = prev_calculated > 0 ? prev_calculated : rates_total;
    }
    ForecastBySOM(prev_calculated == 0);
  }

函数 TrainSOM 和 ForecastBySOM 在下面提供 (以简化的形式),完整的代码附加在文章后。

CSOM KohonenMap;

bool TrainSOM(const int limit)
{
  KohonenMap.Reset();

  LoadPatterns(limit);
  
  KohonenMap.Init(CellsX, CellsY, HexagonalCell);
  KohonenMap.SetFeatureMask(KohonenMap.GetFeatureCount() - 1, 0);
  KohonenMap.Train(EpochNumber, UseNormalization, ShowProgress);

  if(SaveTrainedNetworks)  
  {
    KohonenMap.Save("online-" + _Symbol + CSOM::timestamp() + ".som");
  }
  
  return true;
}

bool ForecastBySOM(const bool anew = false)
{
  double vector[], forecast[];
  
  int n = workCurrencies.getSize();
  ArrayResize(vector, n + 1);
  
  for(int j = 0; j < n; j++)
  {
    vector[j] = GetBuffer(j, 1); // 第一个柱是最近完成的
  }
  vector[n] = 0;
  
  KohonenMap.GetBestMatchingFeatures(vector, forecast);
  
  buffers[n][0] = forecast[n];
  if(anew) buffers[n][1] = GetBuffer(n - 1, 1);
  
  return true;
}

请注意,我们实际上是在预测第0柱开启时的收盘价。因此,与其他缓冲区相关,指标中的“未来”缓冲区没有移动。

这次,让我们尝试预测白银的行为,并在测试器中可视化整个过程。指标将在过去250天(1年)内在“进行中”学习,每20天(1个月)重新培训一次。本文最后给出了带有设置的文件unity-forecast-xag.set。重要的是要注意资产工具集已经扩展了: EURUSD,GBPUSD,USDCHF,USDJPY,AUDUSD,USDCAD,NZDUSD,XAUUSD,XAGUSD,因此,我们根据外汇报价和银本身以及黄金预测XAG。

这里是从2018年7月1日到2018年12月1日这段时间测试的结果。

Unity-Forecast: 在 MetaTrader 5 测试器中根据外汇和黄金集群预测白银价格变化

Unity-Forecast: 在 MetaTrader 5 测试器中根据外汇和黄金集群预测白银价格变化

在这一时间段,准确率达到了 60 %. 我们可以得出这样的结论:基本上,如果这种方法需要从根本上选择预测对象,准备输入参数,以及长期细致的配置,那么它是可行的。

注意!以“随时随地”的方式培训网络会阻挡指标(以及具有相同交易品种/时间段的其他指标),这是不推荐的。在实际应用中,这些代码应当在EA交易中执行。在这里把它放到指标中,只是为了演示的目的。要更新指标中的网络,可以使用助手EA按计划生成图,而该指标将按 NetFilenName 中指定的名称重新加载图。

可以考虑以下选项来提高预测质量:在输入参数中添加外部因素,例如周-日数,或者基于多个Kohonen网络或Kohonen网络以及其他类型的网络实施更复杂的方法。


结论

本文讨论了 Kohonen 网络在解决交易者问题中的实际应用。基于神经网络的技术是一个强大而灵活的工具,允许您涉及各种数据处理方法。同时,它们需要仔细选择输入变量、历史深度和参数组合,因此它们的成功使用在很大程度上取决于用户的专业知识和技能。本文所讨论的类允许您在实践中测试Kohonen网络,获得使用它们的必要经验,并使它们适应您自己的任务。


本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/5473

附加的文件 |
Kohonen2MQL5.zip (302.62 KB)
使用 MQL5 和 MQL4 开发的选择与导航工具: 增加自动模式搜索和显示侦测到的交易品种 使用 MQL5 和 MQL4 开发的选择与导航工具: 增加自动模式搜索和显示侦测到的交易品种

在本文中, 我们继续扩展用于收集和在交易品种之间导航工具的功能。这一次,我们将创建新的选项卡,只显示满足一些必需参数的交易品种,并且研究如何根据所需的挑选规则简单添加自定义选项卡。

利用 HTML 报告分析交易结果 利用 HTML 报告分析交易结果

MetaTrader 5 平台具有保存交易报告,以及智能交易系统测试和优化报告的功能。 交易和测试报告可以按照两种格式保存:XLSX 和 HTML,而优化报告可以保存为 XML。 在本文中,我们将研究 HTML 测试报告,XML 优化报告和 HTML 交易历史报告。

蒙特卡罗方法在强化学习中的应用 蒙特卡罗方法在强化学习中的应用

在本文中,我们将应用强化学习来开发可以自主学习的EA交易。在前一篇文章中,我们考虑了随机决策森林算法,并编写了一个简单的基于强化学习的自学习EA,概述了这种方法的主要优点(交易算法的开发简单和“培训”速度快)。强化学习(RL)可以很容易地融入到任何交易EA中,并加速其优化。

MеtaTrader5 图表上的水平示意图 MеtaTrader5 图表上的水平示意图

水平示意图在终端图表上并不常见,但在很多任务中仍然会用到它们,例如在开发显示特定周期的交易量或价格分布的指标时,当创建各种版本的市场深度时。 本文研究构建和管理水平示意图作为图形基元的数组。