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

MQL5中的范畴论(第23部分):对双重指数移动平均的不同看法

MetaTrader 5交易系统 | 7 五月 2024, 14:27
323 0
Stephen Njuki
Stephen Njuki

概述

范畴论在MQL5中的即时实现似乎是一项艰巨的努力。这是一个没有很多容易阅读或自学材料的主题。市面上有很多书,但它们都是为学术环境中的硕士或博士生设计的。因此,试图让这个话题更容易理解并不是一件轻而易举的事,特别是因为背诵学术论点和理论不是目的,而是将其理念解释和应用于交易员。为此,我们正在扩展我们的主题,即以不同的方式看待简单的日常指标。

本文的目的是强调自然变换的水平组合的概念。我们在上一篇文章中探讨了它的反义词,我们在文章中看到了如何在两个类别之间检索三个函子,这意味着当类别是价格时间序列和相同价格的移动平均时间序列这样简单的数据集时,可以推断出垂直组合中的两个自然变换。在这篇文章中,我们通过添加移动平均的第三类移动平均,即双重指数移动平均,来水平扩展移动平均时间序列。我们对这一著名指标的变体并没有真正使用既定的公式,而是出于我们的目的,它只是通过成为移动平均的移动平均来平滑移动平均线。函子关系与我们在上一篇文章中的类似,但我们在类别之间只有两个函子,而不是上一篇中的三个。然而,与上一篇文章一样,任何两个类别之间的每个函子都有自己的移动平均周期,因此每个函子对之间的自然变换可以帮助我们形成时间序列缓冲区进行分析。

预测交易波动性的重要性可能不如首先确定一个人应该持有的头寸类型(无论是多头还是空头)那么关键。尽管如此,它确实为我们提供了一个机会,来研究其他现有进场信号策略的任何潜在用途和改进,甚至是利用其思想创建新策略。这是我们在过去的文章中探讨过的,所以在这里重新回顾并不陌生。我们将把我们的波动率预测与内置的动量振荡指标(AO)信号类配对,波动率预测将在EA跟踪类的例子中处理。一如既往,欢迎读者用其他信号或他们自己的私人策略来测试这门课,以找出最适合他们的;就本文而言,我们将坚持使用动量振荡指标。


背景

为了简单回顾范畴论的概念,我们已经涵盖了这些概念,这些概念是我们所处位置的关键,我们将阶视为范畴,函子,态射视为特殊情况下的函子,最后是证明自然变换存在的自然性四边形。在介绍这些概念之前,我们还研究了其他早期概念,但此列表应该涵盖我们为本文打开包装的基础。

理解本文中自然变换的“水平”组合是什么意思至关重要,因为我们不是简单地指二维自然变换的图解表示。请考虑以下图表:

这里的水平性是指函子排列在一个链中,使得不同的对(或集合)链接到不同的域和共域类别,如上面所示,其中前两个函子A和C链接范畴-1和范畴-2,而函子B和D链接范畴-2和范畴-3。这与我们在上一篇文章中看到的情况形成了鲜明对比,在这篇文章中,所有函子都链接了同一对范畴。因此,需要明确的是,即使在上一篇文章中,这两个类别是垂直绘制的,这意味着自然变换(水平)贯穿始终,这仍然是一个垂直的组合。同样,即使范畴是按垂直顺序绘制的,也意味着自然变换似乎是按“垂直”顺序绘制的。

因此,我们考虑的类别是价格时间序列、移动平均时间序列和双重指数移动平均时间系列。当将这一范畴与价格时间序列类别进行比较时,使用双重指数时间序列可能会造成一些混乱。实际上,第3个范畴是范畴1上的双重指数移动平均值。如果我们简单地将函子B和D作为第2范畴对象的双重指数移动平均,那么结果对我们来说就不是很有用了。我的意思是什么是MA的DEMA?因此,通过编码,这些函子似乎是从范畴1映射而来的,即原始价格序列,然而,这应该简单地视为类范畴2的移动平均映射,这就是它的实际情况。读者可以修改这一点,并相应地执行自己的测试,因为完整的源代码附在本文末尾,但要记住这一区别很重要。


范畴和函子

所以我们的第一个范畴,范畴-1有价格时间序列。我们确实在这篇早期文章中研究了顺序如何被解释为范畴,因此读者可能希望参考它。我们这里的时间序列与前面的文章中指出的线性顺序非常相似。该类别中的对象将是每个间隔的价格和时间值,这意味着每个对象将至少有两个元素;价格和日期时间值。类别的态射将由序列的时间间隔定义,它们只是将价格值按升序与时间关联。我们将在本文中研究其时间序列的交易品种将是 EURUSD。

价格的移动平均线将是我们的第二范畴,与第一范畴非常相似,它是一个时间序列,也可以被视为线性顺序,因此也是一个范畴。它将主要以两个对象为特征,尽管每个对象都是一个时间序列。第一个将由函子A映射到,并表示在函子A定义的周期内价格序列的移动平均值。这个模型基本上有两个移动平均周期,函子A使用的移动平均周期将是这两个周期中较短的一个。标记为C的第二个函子也将从价格时间序列映射到范畴-2,并且它将比函子A具有更长的移动平均周期。我们在范畴-2中的每个对象都是时间序列。

第三个也是最后一个范畴,范畴-3,将有四个对象是时间序列,尽管我们只考虑其中两个。每对对象(4个对象是2对)将由单个函子映射到,该函子计算移动平均值的移动平均值,以得出双重指数移动平均值(DEMA)。如上所述,代码中的DEMA似乎映射到价格系列的第1范畴,但这样做只是为了简化计算,而不必提出自定义指标。由于它是移动平均的移动平均,因此可以将其视为范畴2的映射。现在范畴-2有两个移动平均的时间序列,可以分为快速移动平均和慢速移动平均。然而,这意味着我们的范畴-3将有4个对象,因为范畴-2中的两个函子分别映射到这两个对象,从而链接到特定的对象或时间序列。


函子与自然变换

这两个函子对的总自然变换可能是6,但由于我们只使用范畴-3中4个对象中的2个,所以这个数字将是2。概括一下,自然变换是函子的两个共域对象之间的差,因此,我们可以通过减去两个移动平均对象来轻松地捕捉到这一点。正如我们在上一篇文章中所做的那样,每个自然转换都将被捕获为一个时间序列缓冲区,这些缓冲区的相关性将有助于我们决定调整未平仓合约的跟踪止损点。然而,在上一篇文章中,这些自然变换缓冲区之间的这种相关性被用作白噪声滤波器,对于这篇文章,由于我们只关注跟踪止损调整,如果应该在移动平均趋势的方向上对止损进行调整,它将再次用作滤波器。因此,这些缓冲区之间正相关的负趋势将为降低空头头寸的止损开绿灯。类似地,具有足够相关性的正趋势(我们的阈值是零以上的任何值)将表明止损向上移动到多头头寸,如果它们存在的话。因此,在很多时候,我们肯定会因为负相关性而“没有信号”,或者因为开启的仓位类型与产生的信号不兼容,因为我们的止损移动决定是基于特定仓位的。因此,正如我们在上一篇文章中所看到的,这个模型可以重新构建为专家信号类的实例,并通过向导在专家顾问中进行组装,在那里可以对其信号进行更多的测试,以评估其性能和结果。


预测波动性

因此,预测波动性就是我们的专家跟踪类实例正在做的事情,就像本系列中的几篇文章中的情况一样。我们在这里使用过滤器进行此操作;移动平均趋势需要通过自然转换缓冲之间的显著相关性来确认。但我们本可以采取的另一种更倾向于波动性的方法是将范畴1作为一系列价格区间(高点减去低点)。这个系列的平均值将在第2范畴中,第3类将是它的DEMA,就像我们在所附代码中对收盘价所做的那样。这个过程基本相同,但这种方法可以产生更协调和更敏感的波动结果。


MQL5中的实际实现

为该模型设置MQL5环境将与我们在本系列中看到的非常相似。再次,专家跟踪类的实例作为MQL5向导中专家顾问的一部分进行组装,为了使其工作或进行测试,我们需要在向导中首先选择专家信号类的实例。在这篇文章中,使用了内置的动量振荡指标信号类。这里的“内置”是指IDE中提供的信号库中已经存在的内容。

我们在这里不一定要为函子编写代码,因为我们可以使用移动平均类的内置实例。“CiMA”和“CiDEMA”类可以轻松处理我们的移动平均和双重指数移动平均需求。尽管如此,我们还是声明并使用'CObjects'类的实例来处理这些平均值,它们的声明如下:

...

   CObjects<double>        m_o_average_a;
   CObjects<double>        m_o_average_b;
   CObjects<double>        m_o_average_c;
   CObjects<double>        m_o_average_d;
...

与上一篇文章一样,我们需要初始化自然变换缓冲区,因为读取信号的能力从一开始就很重要。它们的大小和以前一样是一个输入参数“m_transforms”,就像上一篇文章中的情况一样,因此这一重要步骤的处理方式与我们在上一篇中实现的方式几乎相同,主要区别是我们有4个移动平均实例用于缓冲区。刷新这些值也有点类似于初始化,按照下面的列表进行处理:

//+------------------------------------------------------------------+
//| Refresh function from Natural Transformations.                   |
//+------------------------------------------------------------------+
void CTrailingCT::Refresh(void)
   {
      if(!m_init)
      {
         Init();
      }
      else
      {
         m_close.Refresh(-1);
         
         int _x=StartIndex();
         
         for(int i=m_functor_ab+m_functor_ab-1;i>0;i--)
         {
            m_e_price.Let();m_e_price.Cardinality(1);m_o_prices.Get(i,m_e_price);m_o_prices.Set(i-1,m_e_price);
         }
         
         double _p=m_close.GetData(_x);
         m_e_price.Let();m_e_price.Cardinality(1);m_e_price.Set(0,_p);m_o_prices.Set(0,m_e_price);
         
         for(int i=0;i<m_transformations+1;i++)
         {
            double _a=0.0;
            for(int ii=i;ii<m_functor_ab+i;ii++)
            {
               _a+=m_close.GetData(_x+ii);
            }
            _a/=m_functor_ab;
            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_cd+i;ii++)
            {
               m_e_price.Let();m_e_price.Cardinality(1);m_o_average_a.Get(i,m_e_price);
               double _b_i=0.0;m_e_price.Set(0,_b_i);
               _b+=_b_i;
            }
            _b/=m_functor_cd;
            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_ab+i;ii++)
            {
               _c+=m_close.GetData(_x+ii);
            }
            _c/=m_functor_ab;
            m_e_price.Let();m_e_price.Cardinality(1);m_e_price.Set(0,_c);m_o_average_c.Set(i,m_e_price);
            //
            double _d=0.0;
            for(int ii=i;ii<m_functor_cd+i;ii++)
            {
               m_e_price.Let();m_e_price.Cardinality(1);m_o_average_c.Get(i,m_e_price);
               double _d_i=0.0;m_e_price.Set(0,_d_i);
               _d+=_d_i;
            }
            _d/=m_functor_cd;
            m_e_price.Let();m_e_price.Cardinality(1);m_e_price.Set(0,_d);m_o_average_d.Set(i,m_e_price);
         }
         
         
         for(int i=m_transformations-1;i>0;i--)
         {
            m_natural_transformations_ac[i]=m_natural_transformations_ac[i-1];
            m_natural_transformations_bd[i]=m_natural_transformations_bd[i-1];
         }
         //
         double _a=0.0;
         m_e_price.Let();m_e_price.Cardinality(1);m_o_average_a.Get(0,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(0,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(0,m_e_price);m_e_price.Get(0,_c);
         double _d=0.0;
         m_e_price.Let();m_e_price.Cardinality(1);m_o_average_d.Get(0,m_e_price);m_e_price.Get(0,_d);
         
         m_natural_transformations_ac[0]=_a-_c;
         m_natural_transformations_bd[0]=_b-_d;
      }
   }


刷新函数有点复杂。我们从刷新收盘价格指针开始,因为它是第1范畴的基础。在将最新值指定给该范畴的单个对象之前,我们需要移动该对象中的所有现有元素。然后,我们对范畴2的两个对象做几乎相同的事情。这里的区别是,我们需要首先计算每个相应周期的移动平均值,一旦完成,我们就在像在范畴-1中那样移动两个对象的值后分配值。在这之后,我们必须处理第3范畴对象。它们是4个,但如前所述,我们只利用其中的2个进行预测。我们将从刚刚填充的两个对象中获得这些对象的值,并对DEMA公式进行轻微修改,该公式从移动平均值的两倍中减去移动平均值,我们将简单地计算前者。移动平均的移动平均。

一旦我们的专家跟踪类实例与MQL5向导组装好,我们的模型与测试运行的EURUSD每日价格数据的集成就完成了。和往常一样,这里有一个关于如何实现这一点的指南。如前所述,我们用动量振荡指标组装了这个 EA 交易,作为我们的专家信号类实例。

2020.01.01至2023.01.01期间对 EURUSD 历史数据进行了回溯测试和分析。我们的最佳优化结果测试报告如下:

r1

报告的关键值得注意的方面通常是MAE利润相关性,以及持有期和利润相关性。这两个通常在完整报告的最底部用图形表示。上面分享的是一个总结,详细介绍了每个人都会注意的常见指标。


测试和前瞻运行

如果我们从2023.01.01到2023.08.01进行前瞻运行,并使用回溯测试运行的最佳设置,我们将收到以下报告:

r2

我们没有得到我们想要的结果,这通常意味着两件事的其中之一。我们的模型(使用的跟踪类)的论点是随机的,在交易系统中不能依赖,或者我们在跟踪类的代码中错误地实现了这个想法,比如控制流错误。不过,需要记住的重要一点是,即使我们取得了积极的进展,我们仍需要在更长的时间内进行更全面的测试,对于外汇来说,这可能意味着长达20年,这些测试应该包括真正的分时过程。事实上,为了强调这一点,我们的 EA 交易产生的独特的跟踪止损调整“信号”并不经常发生,因为移动平均线趋势需要朝着开启仓位的方向。正因为如此,跟踪类的功效没有在短的测试窗口内得到充分的测试,你可以说,这3年的时期就是其中之一。此外,这些结果是基于我们的跟踪类与动量振荡指标配对,但当与其他输入信号配对时,性能会是什么样子?在进行评估时,需要考虑到这一点和其他问题。

在部署该 EA 交易或其包含我们在这里介绍的内容的变体之前,对真实数据进行测试是另一个必要的步骤。这方面的第一步是与您想要的经纪商建立一个模拟账户,最好是使用复制您将要实时运行的账户类型的账户。然后将EA连接到要交易的交易品种的图表上。使用MetaQuote的VPS服务可能是一个好主意,因为它涉及的设置问题较少,而且价格有竞争力,但无论做出什么选择,都需要监控专家日志,以确保预期的性能和交易结果至关重要。MetaQuote的VPS和典型的VPS服务器都可以轻松实现这一点。此外,如果测试结果是正面的,实时测试账户应从计划部署的资金数额开始。最后,个人交易日志也可能是一个好主意,尤其是如果这里提出的想法被添加到另一个以某种形式依赖新闻的交易系统中。请记住,MQL5策略测试器还不允许在阅读经济日历事件时进行测试,因此自己记录这些事件可能会很有帮助,这样在审查时,您可以很容易地评估每个新闻项目在长期内的相对重要性。

解释结果和决定下一步该做什么是非常主观的,但作为指导,人们可以认为,对于一个盈利能力足以满足目标要求的系统,利润和MAE的相关性应该是正的,持有期以及每笔交易的利润应该是正相关的。最后一个指标通常被称为期望值,但不幸的是,策略测试器的报告并没有计算出这个值。


结论

总之,我们已经看到了自然变换的水平组合如何在预测中有用。我们在本文中探讨的预测是针对价格柱范围的,可以将其视为波动性的衡量标准。正如这些文章系列中多次出现的情况一样,这些预测是通过调整未平仓合约的跟踪止损来使用的。虽然样本内训练结果是正面的,但样本外或前瞻测试的结果不是这样。尽管这是一个挫折,但并不能否定在对该系统的疗效做出裁决之前,在更长的时间内进行更全面测试的必要性。

这些结果对交易系统开发的影响是,它可以与不同的进入信号类别配对,以获得改进的结果,甚至可以对我们的输入数据集进行更改,以呈现不同的测试场景。

一如既往,我邀请读者探索这些以及任何其他途径,以修改和测试所附的代码,使其满意。


参考

参考文献通常主要来自维基百科,但这次也来自Investopedia

关于在 EA 交易中组装和使用所附代码的其他资源,可以在此处此处找到。


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

附加的文件 |
ct_22.mqh (29.34 KB)
TrailingCT_23.mqh (16.88 KB)
神经网络变得简单(第 56 部分):利用核范数推动研究 神经网络变得简单(第 56 部分):利用核范数推动研究
强化学习中的环境研究是一个紧迫的问题。我们之前已视察过一些方式。在本文中,我们将讲述另一种基于最大化核范数的方法。它允许智能体识别拥有高度新颖性和多样性的环境状态。
开发回放系统(第29部分):EA 交易项目——C_Mouse类(三) 开发回放系统(第29部分):EA 交易项目——C_Mouse类(三)
在改进了C_Mouse类之后,我们可以专注于创建一个类,该类旨在为我们的分析创建一个全新的框架。我们不会使用继承或多态性来创建这个新类。相反,我们将改变,或者更好地说,在价格线中添加新的对象。这就是我们在这篇文章中要做的。在下一节中,我们将研究如何更改分析。所有这些都将在不更改C_Mouse类的代码的情况下完成。实际上,使用继承或多态性会更容易实现这一点。然而,还有其他方法可以达到同样的结果。
开发回放系统(第30部分):EA交易项目——C_Mouse类(四) 开发回放系统(第30部分):EA交易项目——C_Mouse类(四)
今天,我们将学习一种技术,它可以在程序员职业生涯的不同阶段对我们有很大帮助。通常,受到限制的不是平台本身,而是谈论限制的人的知识。这篇文章将告诉你,凭借常识和创造力,你可以让 MetaTrader 5 平台变得更加有趣和通用,而无需创建疯狂的程序或类似的东西,并创建简单但安全可靠的代码。我们将利用我们的创造力修改现有代码,而不删除或添加源代码中的任何一行。
MQL5中的范畴论(第22部分):对移动平均的不同看法 MQL5中的范畴论(第22部分):对移动平均的不同看法
在本文中,我们尝试通过只关注一个指标来简化对这些系列中所涵盖概念的说明,这是最常见的,可能也是最容易理解的。它就是移动平均。在这样做的时候,我们会探讨垂直自然变换的意义和可能的应用。