English Русский Español Deutsch 日本語 Português
preview
MQL5中的范畴论(第22部分):对移动平均的不同看法

MQL5中的范畴论(第22部分):对移动平均的不同看法

MetaTrader 5交易系统 | 2 五月 2024, 11:05
675 0
Stephen Njuki
Stephen Njuki

概述

范畴论对金融的应用一直是这些系列文章的主要内容。我们已经详细讨论了很多时间序列预测,因为它与大多数交易员有关,而且他们是这个平台上的大多数成员。然而,除此之外的其他相关应用程序确实包括估值、风险、投资组合分配和许多其他应用程序。也许为了快速总结一个估值例子,有很多方法可以应用范畴论来获得股票的估值。例如,如果我们将关键股票指标作为一个类别中的每个对象,那么链接到这些不同指标(如收入、债务等)的态射(或图路径)可以归因于不同的估值类别(如A+、A、B等)。有了这个,一旦我们有了特定股票的指标,我们就可以通过它属于特定类别的程度来量化。这是一种简化的方法,只是为了提示在这一范围内可以做些什么。

然而,坚持时间序列,移动平均虽然被一些人忽视为过于简单,但在技术分析中非常重要,主要是因为它们的概念是许多其他指标的基础,例如布林带、MACD等。它们可以被认为是对价格走势的一种波动性较小的观点,考虑到市场中白噪声的数量,强调“波动性较小”是很重要的。

在本文中,我们将继续上一篇文章中介绍的自然变换主题,探索自然变换弥合不同维度的相关数据集之间差距的能力。此处的“Dimensions(维度)”用于表示数据集中的列数。因此,和以前一样,我们面临两类问题,一类是原始价格的“简单”系列,另一类是移动平均价格的“复合”系列。我们的目的是展示在只有三个函子的时间序列预测中的应用。


背景

所以,首先让我们回顾一下我们在这里考虑的一些基本定义。函子是映射两个范畴的函数。我们将在这里处理三个问题,这些问题可以被认为是下图中的组成部分:


我们的域范畴具有以设定的时间间隔具有价格值的对象,而移动平均价格值的域内对象也以类似的时间间隔。我们强调了三个函子,即A、B和C。在最近的文章中,我们已经表示了连接范畴的函子。这里的新内容是增加了第三函子。这符合我们的目的,因为它以“垂直”排列显示了自然变换。“垂直”设置如图所示,但为了避免未来的歧义,“垂直”是指共享相同域和共域类别的函子之间的自然转换。这意味着,如果范畴以图解的方式垂直定位,并且我们的自然变换似乎指向水平方向,那么自然变换仍将处于“垂直”排列中。

继续看,移动平均线通常通过观察它们的交叉点被用作交易指标。这些可能是它们与基础价格交叉的地方,因此向上交叉表示看跌,因为它代表阻力屏障,而向下交叉表示看涨,因为它表示支撑。更常见的情况是,这些交叉点位于不同周期数的两个移动平均线之间。因此,通常情况下,当较短周期数的平均值高于较长周期数时,我们现在有看涨的设置,因为较长且因此波动较小的移动平均值表示支撑,而相反,如果较短周期数平均值低于较长周期数,则为看跌。为此,该模型使每个函子代表一个特定的平均周期。因此,函子A将是所有三个中最短的,而函子B的周期数将介于两者之间,函子C的平均周期数最长。

如果我们看看我们正在考虑的范畴,也许有助于概述为什么这两个时间序列满足范畴公理。概括一下,我们已经讨论了(与时间序列更相关)如何被解释为范畴,尽管如此,范畴的公理是对象、态射、身份态射和关联。域和共域范畴都是价格时间序列,因此其中一个的视图很容易在另一个中推断出来。因此,如果我们关注原始价格的范畴(域),则每个价格点都是由两个元素组成的对象,时间和价格。态射是价格点之间的顺序映射,因为每个价格点都跟随另一个价格点。每个价格点的恒等态射可以被认为是一个周期的移动平均,这与价格序列相同,但一个周期平均提供的是恒等关系。最后,由于给定和三个连续的价格点L、M和N,因此很容易隐含具有关联的组合

L o (M o N) = (L o M) o N

当与从L的态射相关时,从M到N的态射之后的价格与当与从L到M的态射的结果相关时在N处的价格相同。


范畴和函子

我们的范畴1将是所有函子的域范畴,如前所述,它将以原始价格时间序列为特征,函子从中映射其平均值。

因此,这一范畴总共有4个函子,其中只有3个是我们感兴趣的。第一个值得一提的是身份函子。这是因为范畴和函子的行为非常像对象和态射,所以身份需要存在。如前所述,表示不同移动平均实现的其他三个函子的每个周期将由整数输入参数设置,即分别用于函子a、b和c的“m_functor_a”、“m_funtor_b”和“m_funtors_c”。

构成我们三个函子的共域的第二范畴将有三个对象,每个对象在时间序列中具有移动平均价格。因此,范畴1中原始价格序列的每个函子都将映射到范畴2中自己的对象。

范畴2将有一个单原点函子,如前所述,它将是身份函子。根据我们在上一篇文章中看到的定义,这些对象之间的态射将等效于自然变换。这一切可以用下面稍微详细一点的图表来概括:



我们在本文中的函子映射没有像我们在最近的文章中那样使用第三方算法,如多层感知器、随机分布森林或线性判别分析。相反,它是移动平均算法,自然变换甚至会更简单,因为它们将是所考虑的两个函子的最终值之间的算术差。


自然变换

在金融的背景下定义自然变换就像踏入无人区。没有太多的材料或参考资料来支持这一点。通常使用或考虑的是其他方法,如具有两个主要实现的相关性皮尔逊(Pearson)斯皮尔曼(Spearman)。还有其他方法可以用于比较,但在本文中,我们将使用Spearman的相关性作为我们的信号生成算法的一部分。

因此,两个函子之间的自然变换是函子的目标对象的净差。在我们的例子中,我们有3个函子,这意味着3个可能的自然变换。我们将只关注两个变换,即函子A和函子B之间的变换,我们可以称之为AB;以及在函子B和函子C之间,我们称之为BC。在使用这两个函数时,我们希望为每个函数创建两个数据缓冲区。因此,我们将跟踪移动平均值的各自差异,并将其记录在各自的数组中,从而创建两个数据缓冲区。我们对这些缓冲器所做的就是跟踪它们的相关系数。追踪这一数值是我们交易信号的主题。正值应表明市场正在形成趋势,而负值则表明市场处于震荡状态。

因此,具体地说,我们的信号将寻求将移动平均值中所有增量变化的总和与所指示的信号相匹配。因此,如果这个值是正的,则表明多头,反之,如果它是负的,则表示空头。如上所述,将通过两个自然变换缓冲区之间的相关性对该信号进行滤波,该信号必然总是在每个柱上产生一个值。换言之,我们的系统询问,鉴于我们已经建立了每个移动平均线的趋势,跟踪自然变换是否有任何补充好处。


自然变换的垂直组合

在范畴论中,自然转换的短语“垂直组合”及其对立面“水平组合”偶尔会根据作者的上下文和惯例进行意义交换。发现一些文献将共享域和共域类别的函子之间的自然变换称为“水平组合”并不奇怪。这不是我们将要使用的定义。在本文中需要明确的是,可能那些遵循垂直组合的函数将涉及共享域和共域类别的函子之间的自然转换。

那么,垂直组合的意义是什么呢?从我们采用的惯例来看,这是两者中比较简单的一个。它提供了一种查看两个相对简单的数据集(可以推断为类别)的方法,并推导出它们之间的特殊关系,当在更复杂的环境中处理多个范畴时,这些关系很容易被忽略。

这方面的一个很好的例子应该是我们为本文选择的价格序列数据集和移动平均数据集。但除了交易之外,另一个更有见地的例子可能是烹饪领域。考虑烹饪配料列表(域范畴)和菜单集合(共域范畴)。我们的函子只需将配料与各自的菜单配对即可。像函子和态射这样的自然变换确实保留了它们起初的定义和结构。这看似微不足道,但却是范畴论中的一个基石概念。因此,有了对象的元素和结构集(即菜单及其项目),菜单之间的自然变换可以帮助测量和评估准备每道菜所需的相对时间或每道菜的成本,以及取决于厨师/餐厅重点的许多其他感兴趣的领域。有了这些信息,人们可以利用同构(isomorphism),并在给定一组不同菜单的情况下探索衍生成分。虽然这可以用其他方法和系统来实现,但范畴论提供了一种保持结构和可量化的方法。


预测价格变化

利用这种垂直组合,通过记录和分析我们两个自然变换缓冲区的相关系数来预测价格变化。这些数组需要初始化,因为一旦 EA 首次开始运行,就没有加载足够的数据来执行关联。此处理方式如下所示:

//+------------------------------------------------------------------+
//| Get Direction function from Natural Transformations.             |
//+------------------------------------------------------------------+
void CSignalCT::Init(void)
   {
      if(!m_init)
      {
         m_close.Refresh(-1);
         
         int _x=StartIndex();
         
         m_o_prices.Cardinality(m_functor_c+m_functor_c);
         for(int i=0;i<m_functor_c+m_functor_c;i++)
         {
            m_e_price.Let();m_e_price.Cardinality(1);m_e_price.Set(0,m_close.GetData(_x+i));m_o_prices.Set(i,m_e_price);
         }
         
         m_o_average_a.Cardinality(m_transformations+1);
         m_o_average_b.Cardinality(m_transformations+1);
         m_o_average_c.Cardinality(m_transformations+1);
         
         for(int i=0;i<m_transformations+1;i++)
         {
            double _a=0.0;
            for(int ii=i;ii<m_functor_a+i;ii++)
            {
               _a+=m_close.GetData(_x+ii);
            }
            _a/=m_functor_a;
            m_e_price.Let();m_e_price.Cardinality(1);m_e_price.Set(0,_a);m_o_average_a.Set(i,m_e_price);
            //
            double _b=0.0;
            for(int ii=i;ii<m_functor_b+i;ii++)
            {
               _b+=m_close.GetData(_x+ii);
            }
            _b/=m_functor_b;
            m_e_price.Let();m_e_price.Cardinality(1);m_e_price.Set(0,_b);m_o_average_b.Set(i,m_e_price);
            //
            double _c=0.0;
            for(int ii=i;ii<m_functor_c+i;ii++)
            {
               _c+=m_close.GetData(_x+ii);
            }
            _c/=m_functor_c;
            m_e_price.Let();m_e_price.Cardinality(1);m_e_price.Set(0,_c);m_o_average_c.Set(i,m_e_price);
         }
         //
         ArrayResize(m_natural_transformations_ab,m_transformations);ArrayInitialize(m_natural_transformations_ab,0.0);
         ArrayResize(m_natural_transformations_bc,m_transformations);ArrayInitialize(m_natural_transformations_bc,0.0);
         
         for(int i=m_transformations-1;i>=0;i--)
         {
            double _a=0.0;
            m_e_price.Let();m_e_price.Cardinality(1);m_o_average_a.Get(i,m_e_price);m_e_price.Get(0,_a);
            double _b=0.0;
            m_e_price.Let();m_e_price.Cardinality(1);m_o_average_b.Get(i,m_e_price);m_e_price.Get(0,_b);
            double _c=0.0;
            m_e_price.Let();m_e_price.Cardinality(1);m_o_average_c.Get(i,m_e_price);m_e_price.Get(0,_c);
            
            m_natural_transformations_ab[i]=_a-_b;
            m_natural_transformations_bc[i]=_b-_c;
         }
         
         m_init=true;
      }
   }

我们已经为每个函子声明了三个移动平均句柄,在读取它们的任何值之前,我们需要刷新它们。每个移动平均的周期数都是一个输入参数,因此我们分别为句柄“m_ma_a”、“m_ma_b”和“m_ma_c”设置了“m_functor_a”,“m_funtor_b”,和“m_functor_c”。对于每个自然变换,我们还有两个缓冲区,即“m_natural_transformations_ab”和“m_nature_transformtions_bc”。这些数组的大小由输入参数“m_transforms”设置,因此在开始时,需要将两个数组的大小调整为其值。共域范畴中的对象,即:“m_o_average_a”、“m_o_average_b”和“m_o_saverage_c”,也需要调整大小为该值加一,以便能够获得所有更改增量。

因此,如果当自然变换缓冲区的相关性大于零时,由移动平均值的匹配变化产生的信号,则将评估不同移动平均周期对预测能力的影响。这是由下面列出的“GetDirection”函数直接捕获的:

//+------------------------------------------------------------------+
//| Get Direction function from Natural Transformations.             |
//+------------------------------------------------------------------+
double CSignalCT::GetDirection()
   {
      double _r=0.0;
      
      Refresh();
      
      MathCorrelationSpearman(m_natural_transformations_ab,m_natural_transformations_bc,_r);
      
      return(_r);
   }

以及计算我们的“trend”的每个“CheckOpenLong”和“CheckOpenShort”函数。这一 trend (趋势)设定了信号方向,“direction”(方向)充当了盘整市场的过滤器。这里列出功能的实现:

      m_ma_a.Refresh(-1);
      m_ma_b.Refresh(-1);
      m_ma_c.Refresh(-1);
      
      int _x=StartIndex();
      
      double _trend= (m_ma_a.GetData(0,_x)-m_ma_a.GetData(0,_x+1))+
                     (m_ma_b.GetData(0,_x)-m_ma_b.GetData(0,_x+1))+
                     (m_ma_c.GetData(0,_x)-m_ma_c.GetData(0,_x+1));


我们还需要一个与“Init”函数非常相似的函数来刷新这些值。完整的源代码附在文章的末尾。

从1999.01.01到2020.01.01,在每日时间框架内对外汇对USDJPY进行了回溯测试,我们得到了以下报告和一些理想设置:

r1


如果我们尝试从2020.01.01到2023.08.01继续进行这些设置,我们会得到以下报告:

r2

正向的进展并不意味着我们有一个可以工作的系统,而是我们可以有一些可以在更长时间内进行测试的东西,以证实我们在这里的早期发现。


交易系统开发试验台

由于MQL5向导可以无缝地将此信号类集成到新的或现有的交易系统中。我们所要做的就是用这个信号类组建一个新的EA交易,作为交易者总是使用或想要尝试的其他信号之一。“Signal_XXX_Weight”的典型输入参数(其中XXX是信号类的名称)的值通常在0.0到1.0之间,可以在所有信号中进行优化,以确定每个信号的适当权重。

对这种交易系统的评估应该根据交易者的需求来进行。通常情况下,这是增长与资本保全之间的拉锯战。经过简单评估,我能说AHPR是增长的最佳指标,而恢复系数是安全的吗?这都是粗略的估计,所以读者和严肃的交易员需要进行更多的努力才能找到适合他们的方法。

有了性能评估系统,下一步,也许同样重要的是,在不影响其前提和继续前进的情况下,逐步改进策略的方法。有了这一点,就没有现成的方法了,而不是在系统中的每次迭代或修改中不断检查回测和前瞻性能。

还有一种可能性是,始终有一个基于该模型的跟踪类的实例。我们将不得不做出一些改变,但总体要点是一样的。首先,我们的移动平均线不是使用收盘价作为应用价格,而是使用倾向于波动的东西——这可能是典型的价格,甚至可能更好的是中间价。其次,预测方向仍然指向两个自然变换缓冲区之间的相关性,但在这种情况下,我们将跟踪柱形范围的当前变化,作为我们的“趋势”。因此,该范围内的减小需要正相关(值大于零)的支持。这同样适用于不断增加的波动性。如果相关性为零或为负,则这意味着我们的模型表明当前波动性的任何变化都可以忽略。这方面的实现也附在本文的末尾。


结论

总之,垂直组合中自然变换的影响听起来过于学术化和过时,无法引起大多数人的兴趣。然而,它可以说是有见地的,因为通过查看具有常见指标的示例,例如本文的移动平均值,我们揭示了一些模式,从中我们能够在预测时间序列时读取确认信号。

利用这一点来开发一个稳健的交易系统总是橡胶与道路交汇的地方,需要交易员做更多的工作才能实现。尽管如此,可以说网上有足够的支持材料让坚定的交易员能够看穿这一点。

我敦促在时间序列预测之外进一步探索这些概念,并深入到交易和金融的其他领域,如估值或风险,以便读者对这里分享的内容更加满意。


参考

本文的引文大多来自维基百科

下文附有实现本文中范畴论概念的资源。它们是范畴论上名为“ct_22.mq5”的类文件和跟踪类文件“TrailingCT_22_r1.mqh”。类文件应放在“include”文件夹中,而跟踪文件应放置在“include\Expert\Trailing”文件夹中。不熟悉MQL5向导的读者可能想参考这篇文章,了解如何使用向导构建 EA 交易,因为此信号文件旨在通过在向导中组装它来使用。


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

附加的文件 |
ct_22.mqh (29.34 KB)
TrailingCT_22.mqh (13.97 KB)
开发回放系统(第29部分):EA 交易项目——C_Mouse类(三) 开发回放系统(第29部分):EA 交易项目——C_Mouse类(三)
在改进了C_Mouse类之后,我们可以专注于创建一个类,该类旨在为我们的分析创建一个全新的框架。我们不会使用继承或多态性来创建这个新类。相反,我们将改变,或者更好地说,在价格线中添加新的对象。这就是我们在这篇文章中要做的。在下一节中,我们将研究如何更改分析。所有这些都将在不更改C_Mouse类的代码的情况下完成。实际上,使用继承或多态性会更容易实现这一点。然而,还有其他方法可以达到同样的结果。
为 MetaTrader 5 开发MQTT客户端:TDD方法——第3部分 为 MetaTrader 5 开发MQTT客户端:TDD方法——第3部分
本文是一系列文章的第三部分,介绍了我们为MQTT协议开发本机MQL5客户端的步骤。在这一部分中,我们详细描述了如何使用测试驱动开发来实现CONNECT/CONNACK数据包交换的操作行为部分。在这一步骤结束时,我们的客户端必须能够在处理连接尝试可能产生的任何服务器结果时表现得正常。
MQL5中的范畴论(第23部分):对双重指数移动平均的不同看法 MQL5中的范畴论(第23部分):对双重指数移动平均的不同看法
在这篇文章中,我们继续我们的主题,最后是从“新”的角度处理日常交易指标。我们正在为这篇文章处理自然变换的水平组合,而这方面的最佳指标是双重指数移动平均(DEMA),它扩展了我们刚刚涵盖的内容。
开发回放系统 — 市场模拟(第 25 部分):为下一步做准备 开发回放系统 — 市场模拟(第 25 部分):为下一步做准备
在本文中,我们将会完结开发回放和模拟系统的第一阶段。尊敬的读者,有了这样的成就,我确认该系统已经达到了高级水平,为引入新功能铺平了道路。目标是进一步丰富该系统,将其转变为研究和开发市场分析的强力工具。