English Русский Español Deutsch 日本語 Português
preview
MQL5中的范畴论(第21部分):使用LDA的自然变换

MQL5中的范畴论(第21部分):使用LDA的自然变换

MetaTrader 5交易系统 | 29 四月 2024, 13:03
368 0
Stephen Njuki
Stephen Njuki

概述

到目前为止,我们已经涵盖了范畴论中的许多主题,这些主题很容易倾向于在学术环境之外的适用性。如果我可以列出其中的一些,它们包括:集合和态射、交换、本体日志、乘积、协积、极限、共线性、单半群、群、图、阶、函子,以及现在的自然变换。范畴论比我们在这里考虑的要广泛得多,选择这些主题是因为它们易于在其他与数学相关的学科中应用。如果你有兴趣对这个主题进行更彻底的回顾,这本应该很有趣,有些人认为这本书在这些问题上是圣经。

随着我们继续研究自然变换,我们将在本文中研究时间序列预测中的更多应用。自然变换通常可以在相关的数据集中推断出来,这是我们想在本文中开始研究的内容。

所以,问题来了。一家初创公司为其客户创建了一个数据库,以跟踪他们在一段时间内的购买情况,最初它有3列。主键、产品名称列和支付金额列。随着时间的推移,该公司注意到产品栏中有很多重复,这意味着某个特定的产品被大量购买。因此,为了应对这种情况,我们决定开始记录更多与产品相关的信息,以便更好地区分客户的偏好,并可能探索开发他们产品组合中可能缺失的新产品。为此,产品列被分为3列,即:版本、订阅模式、构建名称。或者,公司可能需要更多的支付信息颜色,并决定将支付列划分为3列,例如支付模式、货币(或地区)和支付金额。同样,这样的拆分并不是详尽无遗的,因为在未来阶段,根据客户的购买和偏好,可能需要更多的拆分。

这些新创建的列中的每一个都将映射到旧的产品列。例如,如果我们在旧的单一产品列和支付金额列或数据库中表中的任何其他列之间建立了一些关键相关性,那么在新的表结构中重新建立这些相关性将是一个繁琐的过程。这家初创公司肯定有很多方法来解决这个问题,但自然变换确实提供了一个无缝的替代方案。

为了理解这一点,让我们首先将其分为两个范畴。在域范畴中,我们将在其数据库中有初创公司的表列表,在共域范畴中我们将有两个版本的客户信息表。为了简单起见,如果我们将列表作为域中的单个对象,并将我们的每个表作为同域中的独立对象,那么从列表中的两个函子(一个到每个表)确实意味着两个表之间的自然变换。因此,一个函子将映射到旧表,在我们的情况下是简单的3列表,而第二个函子则映射到表结构中的修订。如果这是修订版1,那么第二个函子映射到5列表。

自然变换的含义不仅意味着我们可以通过算法映射函数量化这两个表之间的差异,例如:线性方程、二次方程、多层感知器、随机分布森林或线性判别分析;这意味着我们可以使用这些权重来重新建立与旧表的先前相关性,并为创建的列开发新的相关性。


背景

因此,简单回顾一下自然变换是两个函子的目标对象之间的区别。这种差异的使用在这些带有自然性四边形的级数中得到了强调,自然性四边形是这些函子的同域范畴中对象的交换组合。我们在第18部分中介绍了这一点,并在第19部分中用自然性四边形归纳法研究了进一步的例子。

时间序列数据的概念对大多数交易员来说并不陌生,因为我们中的许多人不仅熟悉MetaTrader终端的价格图表和内置指标,而且许多交易员确实编写了自己的自定义指标代码,并开发了他们的 EA 交易。因此,像这些这些这样的主题的一些基础知识已经被大多数人所涵盖。尽管如此,比如说,从图表上看,外汇对的价格是一个离散时间序列,因为我们在每个区间都有一个确定的价格,尽管事实上,当市场在一周的大部分时间开放时,这个价格总是在变化,这意味着它实际上是一个连续的时间序列。因此,离散时间序列视图用于帮助进行分析。

因此,进行分析的能力植根于在特定时间对给定证券的“一致”价格。当尝试进行大多数预测的目标预测时,研究不同时间段的不同序列变得很重要。这就是为什么在有时间滞后的情况下查看和比较数据可以被视为在获得准确结果方面更具建设性。

因此,在本文中,我们将研究两个数据集,正如引言中所暗示的那样,它们将是相似的,其中一个比另一个更详细。有了这两个集合,我们将使它们处于滞后的自然转换中,以帮助进行预测。我们的两个数据集可以表示为表,非常简单。第一个将以移动平均值为特征,而第二个将具有该平均值的组成价格。


数据集描述

所以,这个简单的表只有两列,时间戳列和移动平均值列。此表和复合表中的行数将由用户使用输入参数“m_data”设置。这个简单的表格将按照所使用的移动平均周期的大小在时间上交错排列在复合表格之前。所以,如果我们的移动平均周期是5,那么这个表中的值将是复合表的5个时间柱。

落后的复合表也将有一个时间戳列和更多列,每个列都有不同时间点的价格。时间戳之外的这些额外列的数量将由移动平均周期设置,因此,如果我们的移动平均值超过5个价格柱,则此表将具有一个时间戳列和5个价格列。

这两个具有自然变换映射的数据集可以通过介绍中已经列出的多种方式进行定义。在本文中,我们将使用一种尚未在这些系列中探讨的方法,即线性判别分析(linear discriminant analysis,LDA)。我已经在这篇文章中展示了如何将其与MQL5向导和Alglib库一起使用,不过在这里进行回顾可能会有所帮助。

更具体的定义可以在这里找到,但广义上LDA是一个分类器。如果我们观察任何典型的训练数据集,它总是有自变量(假设影响最终结果的值)和作为“最终结果”的分类器变量。使用LDA,我们可以将这个最终结果归类为最多n个类,其中n是自然数。该算法由Ronald Fisher开发,输出有助于定义每个分类器的主质心的权重向量,还可以估计未知质心(新的或未知的数据点)的位置。有了这些信息,人们可以简单地将未知质心的位置与已知质心的位置进行比较,以知道它更接近哪个质心,从而知道它的分类是什么。为了说明这一点,“权重向量”可以被认为是一条线的方程,该线将只有两个分类器的点分开。如果分类器是三个,那么它就是一个平面的方程。如果是一个,那么它就是一条数字线上的坐标。在任何一种情况下,您都可以通过给每个数据点一组坐标来区分训练数据集。


时间序列预测的自然变换

因此,正如我们在之前的文章18和19中所提到的,在研究自然性四边形时,通常域对象都是重要的,这就是为什么在展示两个不同的价格点和移动平均数据集如何共享本文的自然变换时,我们没有说任何关于函子源类别或对象的内容。它们不是关键。但是,如果我们在源类别中有多个对象,那么您会期望简单数据集和复合数据集都有多个按比例的实例。当我们在第19部分中看到自然性四边形归纳法时,这一点得到了强调。

因此,从图表映射的角度来看,我们的自然变换不应该过于复杂。数据集的时间戳列将被链接,并且复合数据集中的所有价格列将映射到简单数据集中的移动平均列。出于说明和逻辑原因,这通常在MQL5代码中表示,为此,我们将简单数据集和复合数据集的实例分别声明为“m_o_s”和“m_o_c”。这些类实例现在被命名为“对象”,而不是“域”,因为术语“域”是态射链接对象之一的属性,本身不一定是名词。(如果有道理的话)。在我们之前的大多数文章中,我所说的域通常被称为对象。为了避免与MQL5的内置类混淆,我避免使用“object”。这种方法不太容易出错——如果我们更直接地将价格数据直接复制到映射函数中,则很容易出现逻辑错误。这些错误即使在测试 EA 时也不会出现,因为它可以正常编译,因此我们在这里演示它只是为了展示什么是可能的。

因此,自然变换是使用LDA实现的,关键映射将是从滞后价格点到未来移动平均价格值。时间戳列将不被使用,但为了完整性而被提及,以便读者能够了解数据的结构。如上所述的时间滞后将等于移动平均周期的长度。因此,在训练时,我们将使用从起始索引向后n个柱的价格,其中n是移动平均期的长度。这也意味着,在预测时,我们显然使用了最新的价格,但我们的预测将是未来的n个柱。不是即时移动平均。


自然变换在预测中的应用

因此,这方面的代码将主要由信号类中的两个函数处理,因为如前所述,我们不是用通常概述类的整个类结构进行编码,这些类是态射或函子,就像我们过去一样;相反,我们将仅使用对象和元素类来映射共域范畴中的内容。最终结果应该与这里选择的方法相同——在计算机资源上更高效,因此更容易在策略测试器中进行测试。然而,这需要更加小心地避免类别及其函数的逻辑错误,因为所犯的错误在编译或运行策略测试时不会显示出来。因此,我们的 Refresh 函数如下:

//+------------------------------------------------------------------+
//| Refresh function to update objects.                              |
//+------------------------------------------------------------------+
void CSignalCT::Refresh(int DataPoints=1)
   {
      m_time.Refresh(-1);
      m_close.Refresh(-1);
      
      for(int v=0;v<DataPoints;v++)
      {
         m_e_s.Let(); m_e_s.Cardinality(2);
         m_e_c.Let(); m_e_c.Cardinality(m_independent+1);
         
         m_e_s.Set(0,TimeToString(m_time.GetData(v)));
         m_e_c.Set(0,TimeToString(m_time.GetData(v)));
      
         double _s_unit=0.0;
         //set independent variables..
         for(int vv=0;vv<m_independent;vv++)
         {
            double _c_unit=m_close.GetData(StartIndex()+v+vv+m_independent);
            
            m_e_c.Set(vv+1,DoubleToString(_c_unit));
         }
         
         m_o_c.Set(0,m_e_c);
         
         //get dependent variable, the MA..
         for(int vv=v;vv<v+m_independent;vv++)
         {
            _s_unit+=m_close.GetData(StartIndex()+vv);
         }
         
         _s_unit/=m_independent;
         
         m_e_s.Set(1,DoubleToString(_s_unit));
         
         m_o_s.Set(0,m_e_s);
      }
   }


此 Refresh 函数将由“GetDirection”函数调用,其源代码如下所示:

//+------------------------------------------------------------------+
//| Get Direction function from implied naturality square.           |
//+------------------------------------------------------------------+
double CSignalCT::GetDirection()
   {
      double _da=0.0;
      
      int _info=0;
      CMatrixDouble _w,_xy,_z;
      _xy.Resize(m_data,m_independent+1);
      
      double _point=0.00001;
      if(StringFind(m_symbol.Name(),"JPY")>=0){ _point=0.001; }
      
      for(int v=0;v<m_data;v++)
      {
         Refresh(v+1);
         
         ...
         
         //training classification
         _xy.Set(v,m_independent,(fabs(_ma-_lag_ma)<=m_regularizer*_point?1:(_ma-_lag_ma>0.0?2:0)));
      }
      
      m_lda.FisherLDAN(_xy,m_data,m_independent,__CLASSES,_info,_w);
      
      if(_info>0)
      {
         double _centroids[__CLASSES],_unknown_centroid=0.0; ArrayInitialize(_centroids,0.0);
         
         _z.Resize(1,m_independent+1);
         
         m_o_c.Get(0,m_e_c);
         
         for(int vv=0;vv<m_independent;vv++)
         {
            string _c="";
            m_e_c.Get(vv+1,_c);
            
            double _c_value=StringToDouble(_c);
            _z.Set(0,vv,_c_value);
         }
         
         for(int v=0;v<m_data;v++)
         {
            for(int vv=0;vv<m_independent;vv++)
            {
               _centroids[int(_xy[v][m_independent])]+= (_w[0][vv]*_xy[v][vv]);
            }
         }
         
         // best vector is the first 
         for(int vv=0;vv<m_independent;vv++){ _unknown_centroid+= (_w[0][vv]*_z[0][vv]); }
         
         
... 
      }
      else
      {
         
... 
      }
      
      return(_da);
   }

我们的预测是未来移动平均价格的变化,因此负变化将表明空头,正变化将表明多头,“没有变化”将表明市场盘整。最后一点是,为了量化“无变化”,我们有一个regulater参数“m_regulater”,它设置了最小预测幅度,将其视为看跌或看涨的变化。这是一个整数,我们通过将其与交易品种的点大小相乘来量化它。

这样,我们实现变换的代码就结束了。关键变量的声明在类清单中一如既往地完成。除了信号类的典型声明之外,我们还为我们的特殊类添加了简单元素、复合元素、简单元素、组合元素、线性判别类实例的声明。

然后,在每个新的柱上,我们通过 refresh 函数更新这些元素的值,从而更新它们的对象。这涉及到分配自变量,也就是简单地分配一个数量等于输入移动平均周期长度的价格。因此,我们将这些价格传递给复合元素和对象。我们对LDA使用了3个分类器,其中2用于多头,1个用于盘整市场,0用于空头。因此,根据当前移动平均值(基于训练集中的指数)和滞后移动平均值之间的差异,为每个训练数据点分配一个分类。两个平均值都是在相等的长度上取的,这是已经提到的输入,并且滞后也等于这个长度。

分类器的分配相当于在线性判别分析的Alglib实现下的训练。也许也值得一提的是我们的正则化机制,它只是确定要忽略哪些信号,即什么是白噪声?因此,在回答这个问题时,我们取两个移动平均值之间的任何差值,该差值小于输入参数“m_regularizer”,这是一个整数,我们将其与交易品种的点大小相乘,使其与价格移动平均值的价差相当。

这样,我们运行fisher函数来输出一个系数(或权重)矩阵“w”,正如所讨论的,它形成了我们分类器之间定义平面的方程。

用最新的价格数组填充代表下一次预测的当前价格点的Z矩阵,然后从fisher函数中给出与“w”矩阵的点积,以获得其由“w”阵定义的质心值。这个值是我们未知的质心。

同样,我们的3个分类器的质心值也填充了该矩阵与自变量矩阵的点积。

有了所有3个分类器的质心值和我们未知的质心值,现在的问题是将这个未知与3个分类器进行比较,看看我们的未知最接近哪个。


真实世界应用程序

为了进行“案例研究”,我们从今年年初到6月1日对GBPUSD进行了测试。这为我们提供了以下报告:

r1


展望8月,我们得到了以下报告中的负面结果:

r2


基于前瞻性报告的预测准确性似乎存在疑问,这可能是由于优化运行不完整(仅针对前3代运行),或者测试窗口太小,因为我们只看了三年,而可靠的系统需要更长的时间。附件中有源代码,这样读者就可以解决这个问题。然而,与本系列的所有文章一样,这里展示的是开发交易系统的潜力。


结论

总之,在日常生活中,我们的数据库表或存储数据的格式往往不仅在大小上,而且在复杂性上都会增长。最后一点已经在这里通过观察一个随着数据列的添加而变得更加复杂的数据集来证明。我们希望通过考虑时间交错的数据集来进行预测,从而利用这一点。虽然优化结果显示了仅运行3代的潜力,但这些运行无法向前推进。这可以通过将该信号类别与另一信号类别配对来补救,或者可以在更长的周期上进行更广泛的测试。

然而,就文章主题而言,自然变换非常善于处理各种数据结构——不仅是在数据集因业务或分析需求而不断发展的情况下,而且可能是在需要进行比较的情况下——默认情况下,两个数据集的维度(列数)不相同。这一功能肯定会在几个原则中派上用场。


参考

参考文献一如既往大多是在维基百科中,请参阅文章中的链接。

通常需要注意的是,所附的信号文件需要与MQL5向导一起组装。如果有人不熟悉向导类,这篇文章可以作为一个方向。


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

附加的文件 |
ct_21.mqh (31.39 KB)
SignalCT_21_r2.mqh (11.52 KB)
开发回放系统 — 市场模拟(第 25 部分):为下一步做准备 开发回放系统 — 市场模拟(第 25 部分):为下一步做准备
在本文中,我们将会完结开发回放和模拟系统的第一阶段。尊敬的读者,有了这样的成就,我确认该系统已经达到了高级水平,为引入新功能铺平了道路。目标是进一步丰富该系统,将其转变为研究和开发市场分析的强力工具。
开发回放系统 — 市场模拟(第 24 部分):外汇(V) 开发回放系统 — 市场模拟(第 24 部分):外汇(V)
今天,我们将去除阻止基于最后成交价进行模拟的限制,并将专门针对这类模拟引入一个新的切入点。整个操作机制将基于外汇市场的原则。该过程的主要区别在于出价(Bid)和最后成交价(Last)模拟的分离。不过,重点要注意,用于随机化时间,并将其调整为与 C_Replay 类兼容的方法在两类模拟中保持雷同。这很好,因为一种模式的变化会导致另一种模式的自动改进,尤其遇到处理跳价之间的时间。
为 MetaTrader 5 开发MQTT客户端:TDD方法——第3部分 为 MetaTrader 5 开发MQTT客户端:TDD方法——第3部分
本文是一系列文章的第三部分,介绍了我们为MQTT协议开发本机MQL5客户端的步骤。在这一部分中,我们详细描述了如何使用测试驱动开发来实现CONNECT/CONNACK数据包交换的操作行为部分。在这一步骤结束时,我们的客户端必须能够在处理连接尝试可能产生的任何服务器结果时表现得正常。
神经网络变得轻松(第五十五部分):对比内在控制(CIC) 神经网络变得轻松(第五十五部分):对比内在控制(CIC)
对比训练是一种无监督训练方法表象。它的目标是训练一个模型,突显数据集中的相似性和差异性。在本文中,我们将谈论使用对比训练方式来探索不同的扮演者技能。