English Русский Español Deutsch 日本語 Português
preview
MQL5 中的范畴论 (第 17 部分):函子与幺半群

MQL5 中的范畴论 (第 17 部分):函子与幺半群

MetaTrader 5交易系统 | 22 四月 2024, 09:51
279 0
Stephen Njuki
Stephen Njuki

概述

我们继续研究范畴论,针对函子进行更多研究。到目前为止,我们已经见识过了范畴论在实现智能尾随类和智能信号类的自定义实例中的应用,故此我们将在本文中研究其在智能资金类中的应用。所有这些类都出自 Meta Editor IDE,并与 MQL5 向导一起使用,从而可以最少的编码量来拼装智能交易系统。

在交易系统设计方面,持仓规模是热点问题之一。在任何智能系统的初步测试中得到的结果对它都非常敏感,通常建议将其全部排除在外(使用固定保证金或固定手数),或者如果您必须要用它,请您在已经拥有相应的入场信号时将其添加到最后,这样就会与您的退出方法达良好的平衡。尽管如此,我们将尝试基于智能系统资金类的自定义实例所预测的止损设置理想的持仓规模。

函子是范畴之间的桥梁,它不仅捕获了每个范畴中对象之间的差异,还捕获了它们的态射差异。我们已经能够见识过,在查看图论和线性序范畴时,如何使用这些捕获的信息来预测波动率和市场趋势的变化。回顾图论和线性序本身不是范畴,但因为存在关键范畴公理故而被视作范畴。

第 14 篇第 15 篇文章中曾用简单的线性方程实现了函子,其中映射只需要一个斜率系数和一个 y 截距来判定协域范畴对象和态射。从第16篇文章开始,我们开始将函子看作一种称为多层感知器的简单神经网络。这个网络源于 Warren Sturgis、McCullochWalter Pitts 的工作,并且已被证明可在 -1 到 +1 间近似任何连续函数,并且一段时间以来,人们也知道可以复制任何 XOR 函数,如果它是多层的。

在本文中,当我们汇总我们对函子的看法时,我们将验证当幺半群与证券价格的预序配对时,我们如何制定一个系统来设置证券交易时的持仓规模,譬如 BTCUSD(比特币)。当我们上次视察幺半群时,对于交易者它们的主要应用是进行交易步骤分类,如此即可由每个幺半群的操作制定决策。回想一下,幺半体是一个集合、一个二元运算、和一个衡点元素。由此,针对交易者在做出开仓决定时面临的每个名义步骤,我们创建了幺半群。


理解交易中的函子和幺半群

如果我们快速回顾一下到目前为止所涵盖的一些基本原理,对象(在前面的文章中称为域)是单元格,或构建的范畴模块。在一个范畴中,对象之间具有称为态射的映射,就像您拥有的链接对象的态射一样,故函子链接范畴亦如此。

因此,由函子提供的范畴之间的链接在进行预测时很实用,因为协域范畴是一个时间序列,而我们的主题正是预测它。在我们的上一篇文章中曾为我们决定是做多还是做空标普 500 指数提供了信息。

然而,与上一篇文章中函子将图形链接到线性序不同的是,我们将把幺半群作为域范畴。如前所述,我们在进行交易的每一步都利用幺半群作为决策关键。由于策略的差异,这些步骤必然与其它交易者所用的步骤不同,包括选择时间帧、选择回溯区间、选择所用的应用价格、以及选择一个在产生读数时可协同所选时间帧、回溯区间和应用价格的指标。最后的选择是交易行动,即我们是否给定指标值,我们跟顺其趋势、或与其价位相逆。因此,与这些决策中的每一个对应的幺半群都已编号,且每个幺半群的二元运算负责在遍历所有集合值后从每个集合中选择相应的数值。


函子作为多层感知器(MLP)

多层感知器在交易中的作用怎样强调都不为过。关于神经网络的大量相关文献都清晰地证明了为什么它们已经、并且越来越变得不可或缺。对于大多数交易者来说,尽管它们的用途是预测证券价格的下一个趋势,这挺好。然而,在我看来,可能被忽视的是选择(也许是监管)网络的输入数据。是的,您想预测一个时间序列,但您的预测基于哪个数据流,为什么?这也许听起来小事一桩,但这可能就是为什么许多依据大数据训练的网络,其它因素不变,实际运行时不如它们在测试运行中的表现。

因此,正如我们在第 9 篇文章中曾用到的非常基本的交易系统,每个步骤中都用幺半群来告知决策,以及随后的其它一些类似步骤,我们将对本文做同样的事情,唯一的例外是最后一步将被省略,保留四个步骤。省略的第五步,其幺半群设置是顺势亦或逆势交易,于此无关。我们的多层感知器(MLP)随之用到剩余的四个幺半群的每个输出值作为输入。我们的 MLP 训练后的目标输出,将作为持仓的理想止损点。这个止损间隙的大小与持仓交易的手数成反比,因此它将作为我们持仓规模的关键指标。

由此,这就是我们如何基于预测止损间隙得到的持仓规模:

         //output from MLP forecast
         double _stoploss_gap=_y_output[0];
         
         //printf(__FUNCSIG__+" ct call: "+DoubleToString(_stoploss_gap));
      
         sl=m_symbol.Ask()+fabs(_stoploss_gap);
         
         //--- select lot size
         double _ct_1_lot_loss=(_stoploss_gap/m_symbol.TickSize())*m_symbol.TickValue();
         double lot=((m_percent/100.0)*m_account.FreeMargin())/_ct_1_lot_loss;
         
         //--- calculate margin requirements for 1 lot
         if(m_account.FreeMarginCheck(m_symbol.Name(),ORDER_TYPE_SELL,lot,m_symbol.Bid())<0.0)
         {
            printf(__FUNCSIG__" insufficient margin for sl lot! ");
            lot=m_account.MaxLotCheck(m_symbol.Name(),ORDER_TYPE_SELL,m_symbol.Bid(),m_percent);
         }
         
         //--- return trading volume
         return(Optimize(lot));

首先,我们计算一手亏损持仓覆盖预测止损间隙招致的回撤亏损或货币价值。这是用间隙除以跳价大小再乘以跳价价值。如果我们在开新仓时取 “m_percent” 百分比分配保证金,其为开仓允许的最大回撤百分比,那么我们的手数将是该百分比除以 100,再乘以我们的可用保证金,再除以每手亏损。换言之,在最大幅度回撤金额中,我们可以承受每手亏损多少?


针对持仓规调整的幺半群操作

故此,如同之前的文章,对每个幺半群制定决策的编码是由一个函数处理,即 “Operate” 函数,并且取决于输入幺半群的操作方法,它涉及从幺半群集的数值中进行选择。我们案例中所用的操作方法非常初级,编号为 6 个,其中我们只用到了 4 个,因为加法和乘法要求 0 和 1 始终存在于幺半群中,这在我们的案例中是不可能的。我们高亮显示此枚举和函数的代码如下:

//+------------------------------------------------------------------+
//| Enumeration for Monoid Operations                                |
//+------------------------------------------------------------------+
enum EOperations
  {
      OP_FURTHEST=5,
      OP_CLOSEST=4,
      OP_MOST=3,
      OP_LEAST=2,
      OP_MULTIPLY=1,
      OP_ADD=0
  };


//+------------------------------------------------------------------+
//|   Operate function for executing monoid binary operations        |
//+------------------------------------------------------------------+
void CMoneyCT::Operate(CMonoid<double> &M,EOperations &O,int IdenityIndex,int &OutputIndex)
   {
      OutputIndex=-1;
      //
      double _values[];
      ArrayResize(_values,M.Cardinality());ArrayInitialize(_values,0.0);
      //
      for(int i=0;i<M.Cardinality();i++)
      {
         m_element.Let();
         if(M.Get(i,m_element))
         {
            if(!m_element.Get(0,_values[i]))
            {
               printf(__FUNCSIG__+" Failed to get double for 1 at: "+IntegerToString(i+1));
            }
         }
         else{ printf(__FUNCSIG__+" Failed to get element for 1 at: "+IntegerToString(i+1)); }
      }
      
      //
      
      if(O==OP_LEAST)
      {
         ...
      }
      else if(O==OP_MOST)
      {
         ...
      }
      else if(O==OP_CLOSEST)
      {
         ...
      }
      else if(O==OP_FURTHEST)
      {
         ...
      }
   }

现在,因为我们感兴趣的是持仓规模,而非预测市场趋势的波动性,因此最终的“动作”幺半群及其决策可以用函子代替。故此,将执行前四个幺半群,从而有助于决定指标读数,来指导持仓规模。我们将坚持使用 RSI 和布林带指标,后者像以前一样正则化,从而产生一个介于 0 和 100 之间的 RSI 值。因此,即使这个指标读数是三个早期幺半群的结果,它也将与它们配对,来创建一组四个值,这些值构成了我们多层感知器的输入。因此,需要我们如上一篇文章中所做的那样,将时间帧和应用价格的输入规范化为可由 MLP 处理的数字格式。

因此,要重申一个幺半群,它只是一个集合、一个二元运算和一个衡点元素,简单地允许从该集合中选择一个由二元运算定义的元素。通过选择时间帧、回溯区间和应用价格,我们得到了指标的输入,其归一化值 (0-100) 将作为 MLP 中的第四个输入。

在前面的文章中介绍了选择时间帧、回溯区间、应用价格和指标所涉及的步骤细节,并附上了更新的代码。尽管如此,以下是如何从每个相应函数得到输出:

         ENUM_TIMEFRAMES _timeframe_0=SetTimeframe(m_timeframe,0);
         int _lookback_0=SetLookback(m_lookback,_timeframe_0,0);
         ENUM_APPLIED_PRICE _appliedprice_0=SetAppliedprice(m_appliedprice,_timeframe_0,_lookback_0,0);
         double _indicator_0=SetIndicator(_timeframe_0,_lookback_0,_appliedprice_0,0);


集成函子和幺半群,实现综合持仓规模

为了正确驾驭我们的 MLP,我们需要适当地训练它。在上一篇文章中,训练是在初始化时完成的,如果所选网络设置可用,则加载最有利可图的网络权重,并将其用作调整权重的训练过程的起点。对于本文,在初始化之前或初始化时不执行预训练。取而代之,对于每根新柱线,网络都会得到训练。一次一根柱线。这并不是说这是正确的方式,而只是展示了在训练 MLP 或网络时,其所拥有的众多选择。但这意味着,由于初始权重始终是随机的,因此相同的智能系统设置必然会在不同的测试运行中产生非常不同的结果。作为变通,运行可盈利测试将把其权重写入文件,并在下一次运行开始时采用类似的网络设置(隐藏层中的项目数量),加载这些权重,并作为测试运行的初始权重。网络读写函数仍然是专有的,因此这里只提供对其函数库的引用,因为读者要自行实现函数库。

故此,幺半群和 MLP 的协同作用是这里真正呈现的内容,因为可以说它们中的任何一个都可以单独得到停止距离预测。理想情况下,这需要一个控制来进行验证,这意味着我们需要有单独的智能系统,这些智能系统仅实现幺半群,且只有 MLP,并比较所有三组结果。不幸的是,这对本文来说是不可行的,不过,源代码展示了这两个想法,因此邀请读者探索和验证(或反驳?)这种协同作用的想法。

由此,将两者集成在一起的代码如下:

      m_open.Refresh(-1);
      m_high.Refresh(-1);
      m_close.Refresh(-1);
      
      CMLPTrain _train;
      
      int _info=0;
      CMLPReport _report;
      CMatrixDouble _xy;_xy.Resize(1,__INPUTS+__OUTPUTS);
      
      _xy[0].Set(0,RegularizeTimeframe(_timeframe_1));
      _xy[0].Set(1,_lookback_1);
      _xy[0].Set(2,RegularizeAppliedprice(_appliedprice_1));
      _xy[0].Set(3,_indicator_1);
      //
      int _x=StartIndex()+1;
      
      double _sl_1=m_high.GetData(_x)-m_low.GetData(_x);
      
      if(m_open.GetData(_x)>m_close.GetData(_x))
      {
         _sl_1=m_high.GetData(_x)-m_open.GetData(_x);
      }
      
      double _stops=(2.0*(m_symbol.Ask()-m_symbol.Bid()))+((m_symbol.StopsLevel()+m_symbol.FreezeLevel())*m_symbol.Point());
      
      _xy[0].Set(__INPUTS,fmax(_stops,_sl_1));
      
      _train.MLPTrainLM(m_mlp,_xy,1,m_decay,m_restarts,_info,_report);


案例研究:实际应用和回测

为了分析我们的综合持仓规模方法,我们将使用比特币(BTCUSD)作为测试证券。优化测试将自 2020 年 1月 1 日至 2023年 8 月 1 日,依据日线时间帧进行。由于我们所用的只是有一个隐藏层的 MLP,因此除了优化隐藏层中的理想权重数量外,我们还将微调用于获取持仓uimo的四个幺半群。这意味着我们要为求解持仓规模的四个幺半群中的每一个,寻求理想的衡点元素和操作类型设置。我们的分析完全集中在持仓规模,这意味着所用的智能信号必须是 MQL5 标准库中提供的信号之一。为此,我们将使用 RSI 智能信号类。不会实现尾随止损,且与之前的所有测试一样,不使用止盈或止损值,因此 “take level” 和 “stop level” 参数均为零。我们的智能系统有可能使用挂单,因此参数 “price level” 也将像以前一样进行优化。

我们使用对象函子和态射函子执行测试。其结果分别如下:

r_1


r_2

对于任何给定的输入值,例如来自上述对象态射的运行中的这些值,结果不一定是可重现的,因为在初始化每个 MLP 时,会随机分配权重。现在,我们可以在初始化时从先前的盈利运行中加载权重,从而避免每次都从零开始,但即便如此,由于训练是在每根新柱线上进行的,因此您最终会调整盈利 MLP 的权重,这意味着您不会得到相同的结果。因此,这要求读者为自己的策略编写一个自定义方法,仔细记录和读取他的最佳测试运行中的权重。

如果作为对照,我们在运行智能交易系统时采用相同的 RSI 信号、相同的无尾随止损,但使用不同的持仓规模调整方法,使用固定保证金,我们会得到以下结果:

r_ctrl

从结果来看,即使没有穷尽的测试运行,我们的系统也往往会产生更好的结果。(优化被截短了,因为目的只是为了展示潜力)。然而,正如本文开头所述,通常,对于大多数交易者来说,系统的持仓规模方面是您最后补上的,毕竟它们对测试运行结果有巨大的影响,严格来说,如果系统所采用的入场信号坚实可靠,则其毫无意义。


限制和注意事项

来自我们基于函子的持仓规模的潜在挑战和局限性,涵盖了一系列要点,我们尝试强调其中一些。首先,对于任何交易者来说,我们需要意识到,在获得与他的策略相符的幺半群集,并建立相应的 MLP 训练模式时,存在一个陡峭的学习曲线。这两者对于实现此处讲述的策略都至关重要,并且在开始工作之前需要投入大量时间。

其次,价格间隙或有些证券的一分钟 OHLC 数据与其跳价数据不一致,那么在真实账户上交易时不会产生可靠的幺半群设置或 MLP 权重(给定向前)。原因很明显,但在实现这里提出的思路时,这是一个主要的症结所在。

第三,过度拟合和普适化是另一个特别困扰 MLP 的问题。我之前通过指出输入层数据集的重要性来暗示这一点,在我看来,解决这个问题的工作是使用正则化的有意义的数据。“有意义”是指输入层数据集中有一些可信赖的基本面,并期望会影响我们感兴趣的预测。

然后是参数调整的问题,有些人可能会说这与前一点有关,但我认为它超出了这个范围,如果我们研究所涉及的 CPU 资源的成本和到达目标参数的时间,那么这显然是一个超出过度拟合的问题。

使用 MLP 开发的任何系统的可解释性和透明度差是编码人员需要注意的另一个障碍。如果您有一个有效的系统,并且想开始吸引投资者,他们通常会要求您披露更多系统是如何工作的,而这归功于其 MLP。这可能会带来挑战,具体取决于您的网络层及其复杂性,以便说服您的潜在追随者。还有其它层面,例如数据预处理,这对于 MLP 来说是强制性的,因为它们总是需要加载权重、市场制度变化、以及模型更新和维护、等等。所有这些因素都需要加以考虑,并在它们出现时制定相应的应急措施来应对这些因素。

事实上,可以说,关于市场制度变化的最后一点,为传统交易方式,如手动交易提供了理由。我相信裁判仍然没有定论,因为今天系统正在宽泛的历史数据集中开发和测试,这些数据集捕捉了广泛的市场制度。


结论和未来方向

为了汇总我们从这项研究中得到的主要收获和发现,我们已经展示了一个不同的幺半群视角,并以 MQL5 语言实现一个范畴。我们进一步证明,这种实现在指导交易系统的持仓规模方面很实用,在我们的例子中,它依赖于 RSI 指标的入场和离场信号。

函子和幺半群作为持仓规模工具的意义明确,意味着交易系统的其它方面,例如入场信号,或者像这些系列中的尾随止损放置和调整中经常出现的情况一样。

正如最后所指出的,在交易者充分利用这里提出的思路之前,还有一些工作和障碍需要克服,因此邀请读者探索和试验基于函子的方法来开发他们的交易系统。


参考

相关来源来自维基百科,在文章中作为超链接。


附录:MQL5 代码片段

请将文件 'MoneyCT_17_.mqh' 放在文件夹 'MQL5\include\Expert\Money\' 当中,'ct_9.mqh' 可以放在 include 文件夹中。

您也许需要按照本指南了解如何利用向导拼装智能系统,因为您需要将它们拼装为智能系统的一部分。如文章中所述,我使用 RSI 振荡器作为入场信号,且无尾随止损。始终如一,本文的目标不是向您出示圣杯,而是一个思路,您可以根据自己的策略进行定制。所附的 *.mq5 文件是由向导组装的,您可编译它们,或拼装自己的文件。带有下划线 “control” 的拼装文件是采用固定保证金管理持仓规模。


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

附加的文件 |
ct_17.mq5 (9.25 KB)
ct_9.mqh (65.06 KB)
MoneyCT_17_.mqh (36.29 KB)
为 MetaTrader 5 开发一款 MQTT 客户端:TDD 方式 - 第2部分 为 MetaTrader 5 开发一款 MQTT 客户端:TDD 方式 - 第2部分
本文是描述 MQTT 协议的本机MQL5客户端开发步骤系列文章的一部分。在这一部分中,我们将描述我们的代码组织、第一个头文件和类,以及我们如何编写测试。本文还包括关于测试驱动开发实践以及我们如何将其应用于该项目的简要说明。
神经网络变得轻松(第五十四部分):利用随机编码器(RE3)进行高效研究 神经网络变得轻松(第五十四部分):利用随机编码器(RE3)进行高效研究
无论何时我们研究强化学习方法时,我们都会面对有效探索环境的问题。解决这个问题通常会导致算法更复杂性,以及训练额外模型。在本文中,我们将看看解决此问题的替代方法。
MQL5中的范畴论(第20部分):自我注意的迂回与转换 MQL5中的范畴论(第20部分):自我注意的迂回与转换
我们暂时离开我们的系列文章,考虑一下 chatGPT 中的部分算法。有没有从自然变换中借鉴的相似之处或概念?我们尝试用信号类格式的代码,在一篇有趣的文章中回答这些和其他问题。
MQL5中的范畴论(第19部分):自然性四边形归纳法 MQL5中的范畴论(第19部分):自然性四边形归纳法
我们继续通过探讨自然性四边形归纳法来研究自然变换。对于使用MQL5向导构建的EA交易来说,对多货币实现的轻微限制意味着我们正在通过脚本展示我们的数据分类能力。所考虑的主要应用是价格变化分类及其预测。