模式搜索的暴力方法

这项技术有什么特别之处使得我决定探讨一下呢?

我总是尽量表达我的想法,以便它们对其他交易者有最大的实际应用。我为什么要这么做?这是个好问题。我想和其他交易者分享我的经验。如果有人能利用我的想法,实施一些有趣和有利可图的东西,我会很高兴,不管我从中得到什么。我当然明白,这些想法可能不完全是我的,我可能会重新发明轮子。我认为我们应该分享经验,尽可能富有成效地合作。也许这就是成功的秘诀。孤立地工作是很难取得任何重大成就的。在我看来,模式这个话题对于理解市场的物理特性是非常重要的。你作为交易者的职业生涯可以依赖于对基本思想的理解,即使“作为交易者的职业生涯”这个短语听起来很有趣。我希望我能对研究这些问题的人有所帮助。

关于暴力及其与神经网络的区别

神经网络本质上也是一种暴力。但是它的算法与简单的暴力算法有很大的不同。我将不提供具体的神经网络结构及其元素的细节,但将尝试提供一个一般性的描述。我认为,如果我们坚持某种架构,我们就提前限制了算法的能力。固定的架构是无法弥补的限制。在我们的例子中,神经网络是一种可能策略的架构。因此,神经网络的配置总是与具有网络图的某个文件相对应,这总是指向特定元的集合。它就像一台3D打印机:设置项目参数,打印机就会产生它。因此,神经网络是一种通用代码,没有图纸就没有意义。这就像使用任何高级编程语言,只创建一个空项目,而不利用它的所有功能。结果,空模板什么也不做。神经网络也是如此。与暴力不同,神经网络可以提供几乎无限的策略变化、任意数量的准则和更高的效率。这种方法的唯一缺点是效率在很大程度上取决于代码质量。系统复杂性的增加可能导致程序的资源密集度的增加。结果,我们的策略被转换成一个网络图,这是它的等价物。暴力方法也是如此,但这里我们使用一些简单的数字序列。这个序列比网络图简单得多,它更容易计算,但在效率方面也有限制。下面的方案显示了上述说明。

换句话说,使用暴力方法,我们选择一个与代码交互并产生不同结果的数字序列。但是由于算法是固定的,所以它的灵活性就包含在数字数组中。其长度固定,结构简单。当使用神经网络时,我们搜索一个能给出最佳结果的网络图。在任何情况下,我们搜索特定的字节序列,或最终转换成结果算法的数据。唯一的区别在于它们的能力和复杂性。 

我的暴力与优化算法

我在算法中使用了多元泰勒展开。这就是我选择这种方法的原因:我想提供一个算法,将尽可能可变,但非常简单。我不想拘泥于任何具体的公式,因为任何函数最终都可以展开成泰勒级数或傅里叶级数。我认为傅里叶级数不太适合这个目的。此外,我不熟悉多维等价物。这就是为什么我决定使用第一种方法。而且,它的实现要容易得多。一维泰勒级数如下:

Y = Cs[0]+Cs[1]*(x-x0)^1 + Cs[2]*(x-x0) ^2 + ... + Cs[i]*(x-x0)^n

其中幂之前的系数作为从0到n阶的导数。可以通过展开括号将其转换为更简单的形式:

Y = C[0]+C[1]*x^1 + C[2]*x^2 + ... + C[i]*x^n + ...= Sum(0,+infinity)(C[i]x^i)

在这种情况下,我们只有一个变量。这个级数可以模拟任意点附近的连续可微函数。公式中的项越多,就越能准确地描述我们的函数。如果它们的数目等于无穷大,那么这就是我们函数的绝对等价。在这里,我将不展示如何在任何点附近将任何函数展开为泰勒级数,这些信息在任何一本数学书上都有。但是一维变量对我们来说是不够的,因为我们想要使用来自多个柱的数据来增加一般公式的可变性。这就是为什么应该使用多维变量:

Y = Sum(0,+Infinity)( C[i]*Product variant(x1^p1*x2^p2...*xN^pN) ) 

这个公式的其他变体相当困难。它与一维变量方法具有相同的逻辑。我们必须提供所有可能的偏导数。如果我们限制了这些项的最高幂,那么我们就可以用组合和和来计算这些项的总数。我们的算法将利用最高阶的限制来限制计算资源的消耗。 

但这仍然不足以使我们的暴力行动更加方便。最好去掉第一项C[0],让函数对于我们要输入的负值或正值具有最大对称性。另外,一个方便的解决方案是将正值函数值解释为买入信号,将负值函数值解释为卖出信号。理想情况下,增加该信号模的下限应导致预期收益和利润系数的增加,但也将不可避免地导致信号数量的减少。功能越接近这些要求越好。我们将特定烛形的值作为变量输入到函数(Close[i]-Open[i])。

我们现在需要做的是随机生成这些系数的变体,并检查变体在测试器中的行为。当然,没有人会手动迭代这些系数。因此,我们需要一个EA交易,它可以产生这样的变体,同时维护数千个这样的变体,或者一个第三方解决方案实现一些策略测试器的功能。最初,我不得不在MQL4中编写这样一个专家顾问—这个EA随附在文章中,并附有说明,因此每个人都可以使用它。但我将使用另一个应用程序,它是我用C#开发的。不幸的是,很明显我不能免费提供这个应用程序。它的能力远远超出了研究领域。但是我将描述并演示它的所有功能,以便每个知道如何编程的人都可以复制这个应用程序。截图将在本文后面提供,我们将在这里分析操作结果。

下面是应用程序的主要功能。系数数组的搜索分两个阶段进行。第一个阶段只是在加载的报价中搜索数组,这些数组在下一个烛形上产生最大预期收益或最大利润因子。过程的执行与策略测试器类似。事实上,它只是试图找到一个公式,以最大的准确性预测下一个柱的方向。一定数量的最佳结果作为数组变量存储在内存和磁盘中。只能测试部分报价-此处应指定相对于已加载报价文件的百分比。这用于允许在第二阶段丢弃随机值。第二阶段模拟市场订单和平衡曲线-这是为整个载入区域完成的。同时,执行信号幅度的平滑增加,同时搜索更好的质量选项。这个阶段也有各种各样的过滤器,通过使用这些过滤器我们可以得到更平滑的图表。图表越平滑,找到的公式就越好。在完成第二个搜索阶段后,在列表中可以看到一定数量的最佳选项。选择了所需的选项后,可以在第三个选项卡中为 MetaTrader 4 和 MetaTrader 5 生成一个交易机器人。EA是根据一个预编译的模板生成的,在这个模板中,接收到的数字在某些地方指定。

为任务构建简单模板

模板最初是在MQL4中创建的,然后转换为MQL5。该代码适用于这两种平台(类似于上一篇文章中的代码)。我试图提供这种兼容性,以便花更少的时间来适应解决方案。要像在MQL4中那样使用预定义的数组,应该向EA交易中添加一些额外的代码,这在我的前一篇文章中有描述。所以,请查看这篇文章的细节,本文需要这些知识。实际上,这并不困难,任何开发人员都可以实现这种兼容性。让我们从描述机器人生成时自动填充的变量和数组开始。

double C1[] = { %%%CVALUES%%% };//array of coefficients
int CNum=%%%CNUMVALUE%%%;//number of candlesticks in the formula
int DeepBruteX=%%%DEEPVALUE%%%;//formula depth
int DatetimeStart=%%%DATETIMESTART%%%;//start point in time
input bool bInvert=%%%INVERT%%%;//inverted trading
input double DaysToTrade=%%%DAYS%%%;//number of days into the future to trade

这里C1是我们选择的次数前面的系数数组。CNum是价格表上用于计算多项式值的最近烛形数。接下来是公式的深度,即多维多项式的最大次数。我通常使用1,因为与一维泰勒级数不同,多维泰勒级数的计算复杂度随着阶数的增加而增加,因为系数总数随着阶数的增加而显著增加。为了限制EA操作时间,需要时间上的起始点,因为这样的限制使用关于操作开始位置的信息。inversion(反转)函数用于确保多项式在正确的方向上工作。如果我们把阶系数前面的所有符号都反转,那么多项式本身就不会改变,而只有多项式输出的数字才有不同的符号。这里最重要的部分是系数的比值。如果多项式的负值表示卖出,而正值表示买入,则 inverse = false。如果不是,那么就是 true。因此,我们指示算法“使用带相反符号的多项式的值”。另外,最好将此变量作为输入值,因为我们可能需要能够反转交易以及未来交易的天数。

如果需要使用系数计算数组的大小,可以按以下步骤进行:

int NumCAll=0;//size of the array of coefficients
void DeepN(int Nums,int deepC=1)//intermediate fractal
   {
   for ( int i=0; i<Nums; i++ )
      {
      if (deepC > 1)
         {       
         DeepN(Nums,deepC-1);
         } 
      else 
         {
         NumCAll++;
         }
      }   
   }

void CalcDeepN(int Nums,int deepC=1)//to launch calculations
   {
   NumCAll=0;
   for ( int i=0; i<deepC; i++ )
      {
      DeepN(Nums,i+1);
      }   
   }

中间分形函数计算所有因子的总阶数相同的项数。这样做是为了简单,因为对我们来说,术语的求和顺序并不重要。第二个函数简单地调用循环中的第一个函数,次数与术语类型相同。例如,如果多维级数展开被限制在,比如说,4,那么我们用从1到4的所有自然数调用第一个函数。

计算多项式值的函数几乎相同。但是,在这种情况下,数组是自己生成的,不需要设置其大小。下面是它的样子:

double ValW;//the number where everything is multiplied (and then added to ValStart)
uint NumC;//the current number for the coefficient
double ValStart;//the number where to add everything
void Deep(double &Ci0[],int Nums,int deepC=1,double Val0=1.0)//calculate the sum of one degree
   {
   for ( int i=0; i<Nums; i++ )
      {
      if (deepC > 1)
         {
         ValW=(Close[i+1]-Open[i+1])*Val0;      
         Deep(Ci0,Nums,deepC-1,ValW);
         } 
      else 
         {
         ValStart+=Ci0[NumC]*(Close[i+1]-Open[i+1])*Val0/Point;
         NumC++;
         }
      }   
   }
   
void CalcDeep(double &Ci0[],int Nums,int deepC=1)//calculate the entire polynomial
   {
   NumC=0;
   ValStart=0.0;
   for ( int i=0; i<deepC; i++ )
      {
      Deep(Ci0,Nums,i+1);
      }   
   }

所有计算的结果都将添加到ValStart中,也就是说,结果将添加到全局变量中。需要另一个全局变量-ValW,它用于将已有的乘积乘以某个值。在我们的例子中,这是相应柱的移动点数。这个移动可以是上下两种,这是由标志显示的。所以,这些函数有一个非常有趣的结构。他们在内部调用自身,这些调用的数量和结构总是不同的。这是一种调用树。我真的很喜欢使用这些函数,因为它们非常多变。这次我们用一种简单而优雅的方式实现了多维泰勒级数。

对于仅使用一维多项式的情况,也可以实现附加函数。在这种情况下,整个系列大大简化了。它变成了一个系数的总和乘以一阶柱的运动。它们的数量与所用柱的数量相同。这简化了所有的计算。如果次数为1,则使用简化版本。否则,就使用任何次数都统一的方法。

double Val;
double PolinomTrade()//optimized polynomial
   {
   Val=0;
   if ( DeepBruteX <= 1 )
      {
      for ( int i=0; i<ArraySize(C1); i++ )
         {
         Val+=C1[i]*(Close[i+1]-Open[i+1])/Point;
         }
      return Val;   
      }
   else
      {
      CalcDeep(C1,CNum,DeepBruteX);
      return ValStart;
      }      
   }

使用简单变量时,结果将添加到Val变量中。

现在,让我们编写一个新柱出现时将调用的主要方法:

void Trade()
   {
   double Value;
   Value=PolinomTrade();
   
   if ( Value > ValueCloseE)
      {
      if ( !bInvert )
         {
         CloseBuyF();
         }      
      else 
         {
         CloseSellF();
         }
      }
      
   if ( Value < -ValueCloseE)
      {
      if ( !bInvert )
         {
         CloseSellF();
         }      
      else 
         {
         CloseBuyF();
         }      
      }   
   
   if ( double(TimeCurrent()-DatetimeStart)/86400.0 <= DaysToTrade && Value > ValueOpenE && Value <= ValueOpenEMax )
      {
      if ( !bInvert ) SellF();
      else BuyF();
      }
      
   if ( double(TimeCurrent()-DatetimeStart)/86400.0 <= DaysToTrade && Value < -ValueOpenE && Value >= -ValueOpenEMax )
      {
      if ( !bInvert ) BuyF();
      else SellF();
      }

   }

这个函数非常简单,您所需要做的就是实现我们所期望的仓位打开和关闭功能。

柱的出现可以按下面的方法来检测:

void CalcTimer()
   {
   if ( Time[1] > PrevTimeAlpha )
       {
       if ( PrevTimeAlpha > 0 )
          {
          Trade();
          }
       PrevTimeAlpha=Time[1];
       }
   }

我觉得代码非常简单明了。

我的代码生成的系数是使用上面解释的四个模型创建的。为了方便起见,所有这些系数都在[-1,1]范围内,因为值的比率比值本身更重要。从MQL5程序原型生成这些数字的函数如下所示:

   void GenerateC()
      {
      double RX;
      if ( DeepBrute > 1 ) CalcDeepN(CandlesE,DeepBrute);
      else NumCAll=CandlesE;
      for ( int j=0; j<VariantsE; j++ )
         {
         ArrayResize(Variants[j].Ci,NumCAll,0);
         Variants[j].CNum=CandlesE;
         Variants[j].ANum=NumCAll;
         Variants[j].DeepBruteX=DeepBrute;
         RX=MathRand()/32767.0;
         for ( int i=0; i<Variants[j].ANum; i++ )
            {
            if ( RE == RANDOM_TYPE_1 ) Variants[j].Ci[i]=double(MathRand())/32767.0;
            if ( RE == RANDOM_TYPE_2 )
               {
               if ( MathRand()/32767.0 >= 0.5  )
                  {
                  Variants[j].Ci[i]=double(MathRand())/32767.0;                  
                  }
               else
                  {
                  Variants[j].Ci[i]=double(-MathRand())/32767.0;                  
                  }
               }
            if ( RE == RANDOM_TYPE_3 )
               {
               if ( MathRand()/32767.0 >= RX  )
                  {
                  if ( MathRand()/32767.0 >= RX+(1.0-RX)/2.0  )
                     {
                     Variants[j].Ci[i]=double(MathRand())/32767.0;
                     ///Print(Variants[j].Ci[i]);
                     }
                  else
                     {
                     Variants[j].Ci[i]=double(-MathRand())/32767.0;                  
                     }        
                  }
               else
                  {
                  Variants[j].Ci[i]=0.0;                  
                  }
               }
            if ( RE == RANDOM_TYPE_4 )
               {
               if ( MathRand()/32767.0 >= RX  )
                  {
                  Variants[j].Ci[i]=double(MathRand())/32767.0;
                  }
               else
                  {
                  Variants[j].Ci[i]=0.0;                  
                  }
               }                              
            }
         }
      }

MQL4和MQL5中的暴力算法原型可以在文章附件中找到。这里我不提供我的交易函数实现,因为目的只是展示如何在模板框架内实现该方法。如果您有兴趣查看整个实现,请查看附件。所有的EA交易和其他必要的材料都可以在本文的附件中找到。我的模板有很多多余的东西,包括不必要的函数或变量,可以通过某种方式进行优化。我个人不介意。如果有什么干扰操作,我就把它移走。对我来说更重要的是现在一切正常。我一直在开发一些东西,所以我没有时间把每一个细节都做到完美。我也认为没有理由将所有过程和变量存储在类中,而这可以提高代码顺序和可读性。模板非常简单。程序将使用的带引号的文件将由一个专门的EA交易生成,该EA根据历史运行,并将柱形图数据写入一个文本文件,该文件的结构便于程序读取。我不会提供这个EA的代码在这里,因为它可以很容易地开发。

使用程序查找和分析模式

我选择了三个市场区域进行分析,它们各有一个月的长度,并相互跟随。EURUSD, M5.

  • 第一个时间段:  2020.01.13 - 2020.02.16
  • 第二个时间段:  2020.02.13 - 2020.03.15
  • 第三个时间段:  2020.03.13 - 2020.04.18

时间间隔已选定,因此最后一天总是星期五。你知道,星期五是本周最后一个交易日。以这种方式选择区间可以让你有整整两天的时间来搜索模式,直到交易所重新开始交易。这是一个小的生存技巧。在我们的例子中,这并不重要,因为我们在测试器中测试EA。我决定在这里描述所发现模式的12种变体。其中六个是最大次数为1的多项式。其他六个的最大次数为2。我觉得这就够了。

我的程序的第一个选项卡如下所示:

它提供了更改数字生成类型的功能,例如,仅正、正和负、正和零、正和负和零。搜索条件在第二个组合框中配置。有两种可能的选择:以点数表示的期望值和我对利润系数公式的模拟。在我的公式中,这个值的范围是-1到+1。此外,它也不存在由于除零错误而无法计算利润系数的情况。

P_Factor=(Profit-Loss)/(Profit+Loss).

然后是多项式的最大次数和用于计算的处理器核数。在文本块中,指定多项式的柱数或计算数,以及我自己发明的处理不对称系数,这与前面的公式非常相似:

D_Asymmetry=|(BuyTrades-SellTrades)|/(BuyTrades+SellTrades).

其值在0到1之间。当我们需要指示程序具有相似数量的买入和卖出信号时,需要使用此过滤器,以避免在全局趋势期间所有交易都在同一方向执行的情况。接下来是所有找到的变量中最好的变量的数量,这些变量应该存储在内存中,以及加载的引用的百分比将用于暴力迭代。这一部分是从最后一根柱开始测量的开放时间,它后面的部分将用于优化。我将不解释其余的指标和变量列表,因为它们足够简单。

第二个选项卡如下所示:

选项卡对应于机器人第一部分的第一个变体。你可以比较我的程序中的图表和测试器的图表,这将在下面介绍。报价单中执行暴力的部分用黄色显示,其他部分用红色显示。我不会在这里提供所有变体的截图-所有这些都可以在附件中找到。

让我们看看标签上有什么。Interval Points — 分解多项式值的区间。当我们在第一个选项卡上使用暴力算法时,它计算变量的主要参数以及该多项式的模极大值。因此,我们知道多项式的值的窗口,可以把这个窗口分成相等的部分,逐渐增加值,试图检测出更强的信号。这在第二个选项卡中完成。此选项卡还具有存储在优化内存中的搜索类型和最佳变体数。进一步的过滤器允许我们过滤掉不符合模式定义的不必要的变体。“线控制”为每个变量启用一个附加的运行,其中它计算图形线与连接图形起点和终点的直线的相对偏差。 

Deviation = Max(|Profit[i]-LineProfit[i]|)/EndProfit.

这里,Profit[i]是第i阶平衡曲线的值,LineProfit[i]是直线上的相同值,EndProfit是图表末尾的值。

所有值均以点为单位测量。使用百分比控制是图表中红色部分的百分比(在本例中我没有使用此筛选器)。还有一个过滤器用于过滤最小订单数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值