模式搜索的暴力算法(第三部分):新视野

25 二月 2021, 08:11
Evgeniy Ilin
0
615

目录


介绍

上一篇文章中,我已经说明了使用简单的暴力机制创建有利可图的专家顾问是可能的,因为这种方法并不比其他方法差。这个主题实际上非常接近我的文章"交易系统开发和分析的优化方法",特别是它的“优化搜索背后的数学”部分。在创建这个软件时,我主要使用了本文中阐述的思想。总的来说,本文旨在提供我的程序算法的进一步现代化,目的是提高所发现的方法的质量,以及对一周中不同日期和不同时间区间进行更详细的市场研究。

我的一些专家顾问证明了这种方法的可行性。不同的货币对存在不同的模式。此外,还有狭窄的时间区间适用于所有货币对。我们需要做的是进行深入的分析,这就是我们在本文中要做的。如果没有额外的软件,就无法进行这种全面的分析。请注意,我不认为我的软件是最好的解决方案,

该项目作为一个研究软件出现,并作为分析各种资产的辅助工具。然而,它的能力已经变得更加广泛,所以,我们可以试着从这个方法中得到最大的好处。让我们试着这样做。老实说,我认为像神经网络、人工智能和梯度增强这样的方法更先进。然而,事实证明,暴力算法可以与这些分析方法竞争。别忘了,目前这种方法是建立在非常简单的数学基础上的。我认为这个算法可以表现得更好。


新的思路

这个程序在原型阶段,我想从中挤出最大的能量,看看这样一个简单的暴力方法有什么用。主要问题是,可靠的全局模式搜索需要对大报价间隔进行分析,而一次过程(pass)的容量非常小。例如,当分析10年内任何货币对的M5报价时,该程序在一个好的内核上每小时产生大约700-2000个方案(即使设置很普通)。

即使你使用30核的服务器,你也要等上好几天。唯一的解决方案是减少用于分析的数据量。这可以通过将整个周期分割成相等的间隔并跳过一些未计算的间隔来实现。这些间隔可以是分钟、小时、天、月和年。此外,这些有条件的领域具有某些交易参数的特征。换言之,除了加速,我们还可以从模式的角度详细研究一周中的每一天、每一小时、每一分钟。我们不必担心这种模式与什么有关。

一般来说,不可能对所有的模式进行分类,因为它们的数量是无限的,而人脑不能理解和描述所有的模式(如果你有一个有效公式的话,这并不是真正必要的)。这就是数学。可以探索固定的时间段和固定的天数,也可以一次探索所有数据。这可以和下面的情况比较:我们有一个单变量函数,然后发现这只是一个多变量函数的特例,我们只是不知道而已。


样本分割与样本的可预测性

我认为分析这个选项会很有趣,因为模式搜索模式在其上层功能上有很大的不同,因为分析的样本长度不同。除了显著加速计算外,还可以基于初始样本获得更有用(更小)的样本。得到的函数可以用简单的公式或多项式进一步分析。这是因为市场与世界时间紧密相连,

交易活动主要是基于交易者的日常活动而形成的。这里我们也可以参考专家顾问,许多专家顾问可以被认为是可预测的参与者,但不是全部。例如,许多开发人员创建基于时间的夜间剥头皮交易机器人。事实证明,价格变动的性质不仅取决于过去的价格,还取决于某些日子、星期、月份,甚至可能是几年。这些值中的每一个都是量化的,也就是说,它具有某些固定值。

当然,这些值可以拆分。例如,时间窗口可以不是以小时和分钟来度量,而是以新的一天开始后经过的秒来度量。这完全取决于哪些量更容易测量。这些量子化的量可以进一步分解到无穷大。在我的程序中,分割只在几天和几周内发生。我认为按年份划分并不那么有效。至于几个月,也许我以后会添加相关的功能。下图展示了上述观点:

分段图表

我已经为原始样本分割显示了两个任意选项。下面是如果暴力无限期地持续下去会发生什么。第一种情况是当我们实现所有可能的数学模式进行描述时,第二种情况显示了我们从唯一实现的暴力方法(多维泰勒多项式)得到的结果。总会有最可预测的样本和最不可预测的样本。同时,对于每一个多项式类型,都存在一个最优分段截面。这样的片段可能数不清,但我们可以以很小的精度检测到它们。我不考虑秒,因为我们是分析柱的。

因此,对于分割参数的每个组合,我们可以为每个交易参数创建一个函数,我们可以在整个开发周期的输出中获得该函数。例如,预期收益和利润系数:

  • Ma=Ma(M,D,Ts,Te)
  • PrF=PrF(M,D,Ts,Te)
  • M - 一年中的月份
  • D - 星期几
  • Ts - 区间起始时间
  • Te - 区间结束时间 (可以过渡到0:00,即第二天) 

函数是离散的,所以,参数可以采用严格固定的值。通过修正与一周中的月和日相关的变量,我们可以大致想象这两个函数的样子。更改的最大数目是三个,这就是为什么只能用两个自由变量创建信息图的原因。我使用的是 "Ts" 和 "Te":

3D 图表

我们用来预测未来的每一个公式或算法都会有这样独特的图表来描述未来交易系统的所有数量特征。我只展示了其中的两个来证明,如果存在最大的利润因素,就不一定会有最大的预期收益。在大多数情况下,你必须在预期收益和利润因子之间取得平衡,因为存在点差、佣金和隔夜息。对于每个公式,这些极值的数目和它们的值是不同的。我们不能手动执行多维极值搜索,但可以使用我们的程序来完成。

我想提一件事,在使用我的软件研究市场时,我注意到一个有趣的部分,其中许多货币对高估了可预测性价值。大约在23:30到0:30之间。这肯定与日期点的变化有关。然而,在MetaTrader 4中表现出良好盈利因素的策略的盈利能力在MetaTrader 5中测试时并未得到证实。原因在于点差。一定要重复检查确保模式不会落于点差之内,发现的大部分模式都在点差内部。


搜索算法的最终修改

最终修改的目的是加速程序运行,以及最大限度地提高搜索的可变性和效率。更改列表:

  1. 增加了在固定时间间隔内搜索模式的功能
  2. 增加了只在选定日期交易的功能
  3. 增加了根据所选天数为每个新变量生成随机天数集的功能
  4. 通过以分钟为单位指定可能的最小和最大窗口持续时间,增加了生成随机服务器时间窗口的功能
  5. 组合任何这些设置的功能
  6. 第二个优化选项卡现在可以在多线程模式下工作
  7. 优化了优化模式

更新后的窗口如下所示:

暴力算法选项卡

第二个选项卡没有变化:

优化选项卡

下面是第三个选项卡:

EA交易生成选项卡

界面仍然非常原始,因此设置很难适应表单。我将在下一个程序版本中完全修改界面,这将在下一篇文章中介绍。我还将录制一个视频,内容就是使用我的程序创建一个专家顾问的整个过程。你会看到这是多么简单和快速。您只需要了解界面设置并进行一点练习。


模板优化与辅助专家顾问

为了实现新任务,我还必须修改专家顾问模板。机器人生成所需格式的报价,该报价可以方便地被程序读取。EA为 MetaTrader 5 生成报价的代码如下:

string FileNameString;
uint Handle0x;
datetime Time0=0;

double Open[];
double Close[];
double High[];
double Low[];
datetime Time[];


void WriteEnd()
   {
   FileWriteString(Handle0x,"EndBars"+"\r\n");
   MqlDateTime T;
   TimeToStruct(Time[1],T);
   FileWriteString(Handle0x,IntegerToString(int(T.year))+"\r\n");
   FileWriteString(Handle0x,IntegerToString(int(T.mon))+"\r\n");
   FileWriteString(Handle0x,IntegerToString(int(T.day)));
   }

void OpenAndWriteStart()
   {
   FileDelete(FileNameString);
   Handle0x=FileOpen(FileNameString,FILE_WRITE|FILE_TXT|FILE_COMMON|FILE_ANSI,'\t',CP_UTF8);
   FileSeek(Handle0x,0,SEEK_SET);
   FileWriteString(Handle0x,"DataXXX"+" "+Symbol()+" "+IntegerToString(Period())+"\r\n");
   FileWriteString(Handle0x,DoubleToString(_Point,8)+"\r\n");
   MqlDateTime T;
   TimeToStruct(Time[1],T);
   FileWriteString(Handle0x,IntegerToString(int(T.year))+"\r\n");
   FileWriteString(Handle0x,IntegerToString(int(T.mon))+"\r\n");
   FileWriteString(Handle0x,IntegerToString(int(T.day))+"\r\n");             
   }
      
void WriteBar()
   {
   FileWriteString(Handle0x,"\r\n");
   FileWriteString(Handle0x,DoubleToString(Close[1],8)+"\r\n");
   FileWriteString(Handle0x,DoubleToString(Open[1],8)+"\r\n");
   FileWriteString(Handle0x,DoubleToString(High[1],8)+"\r\n");
   FileWriteString(Handle0x,DoubleToString(Low[1],8)+"\r\n");         
   FileWriteString(Handle0x,IntegerToString(int(Time[1]))+"\r\n");
   MqlDateTime T;
   TimeToStruct(Time[1],T);
   FileWriteString(Handle0x,IntegerToString(int(T.hour))+"\r\n");
   FileWriteString(Handle0x,IntegerToString(int(T.min))+"\r\n");
   FileWriteString(Handle0x,IntegerToString(int(T.day_of_week))+"\r\n");         
   //FileClose(Handle0x);
   }      

void CloseFile()
   {
   FileClose(Handle0x);
   }

bool bNewBar()
   {
   ArraySetAsSeries(Close,false);                        
   ArraySetAsSeries(Open,false);                           
   ArraySetAsSeries(High,false);                        
   ArraySetAsSeries(Low,false);                              
   CopyOpen(_Symbol,_Period,0,2,Open);
   CopyClose(_Symbol,_Period,0,2,Close);
   CopyHigh(_Symbol,_Period,0,2,High);
   CopyLow(_Symbol,_Period,0,2,Low);
   ArraySetAsSeries(Close,true);                        
   ArraySetAsSeries(Open,true);                           
   ArraySetAsSeries(High,true);                        
   ArraySetAsSeries(Low,true);                                 
   if ( Time0 < Time[1] )
      {
      if (Time0 != 0)
         {
         Time0=Time[1];
         return true;
         }
      else
         {
         Time0=Time[1];
         return false;
         }
      }
   else return false;
   }

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
  ArrayResize(Close,2,0);
  ArrayResize(Open,2,0);   
  ArrayResize(Time,2,0);
  ArrayResize(High,2,0);
  ArrayResize(Low,2,0);  
  FileNameString="DataHistory"+" "+Symbol()+" "+IntegerToString(Period());
  OpenAndWriteStart();
   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {
  WriteEnd();
  CloseFile();
  }

void OnTick()
  {
  ArraySetAsSeries(Time,false);
  CopyTime(_Symbol,_Period,0,2,Time);
  ArraySetAsSeries(Time,true);
  if ( bNewBar()) WriteBar();
  }

它只有几个简单的函数,有些函数在测试开始时编写一些内容,有些函数在测试结束时添加一些关于报价的信息,而主函数只在出现一个新柱时编写关于它的信息。这个 EA 没有输入参数。在历史数据上运行它,它将以适当的格式生成报价文件,该文件可以被程序读取。嗯,这不是一个很好的解决办法。也许,在未来我将能够实现从终端直接读取报价。到目前为止,上述解决方案相当方便,至少对我来说很方便。

我没有使用临时函数将 datetime 转换为一周中的几天、几小时和几分钟,而是将它们作为附加的条信息写入了报价中。除了 ENUM_DAY_OF_WEEK,其它都是整数值。我所要做的就是在 C# 代码中实现这样一个编号列表,并在模板中提供以相同形式返回的数据。避免使用时间函数可以避免在 C# 代码端进行不必要的计算。它还避免了时间上的不一致。这样的事情很危险,所以你最好避开它们。

生成的报价文件可以在任何文本编辑器中打开。您将看到以下简单明了的结构:

历史数据文件


模板的头部,其中注册了变量,以前包含了在生成时自动填充的字段。现在,交易时间和交易日的变量已经添加到它们的列表中。

输入参数预设现在如下所示:

double C1[] = { %%%CVALUES%%% };//Brutted Values
int CNum=%%%CNUMVALUE%%%;//Bars To Equation
int DeepBruteX=%%%DEEPVALUE%%%;//Max Pow Of Polynomial
int DatetimeStart=%%%DATETIMESTART%%%;//Help Datetime
input bool bInvert=%%%INVERT%%%;//Invert Trade(or sign of values as the same)
input int DaysToFuture=%%%DAYSFUTURE%%%;//Days To Future
int DaysToTrade[]={ %%%DAYS%%% };//Days To Trade
input double ValueOpenE=%%%OPTVALUE%%%;//Open Signal
input bool bUseTimeCorridorE=%%%TIMECORRIDORVALUE%%%;//Use Time Corridor
input int TradeHour=%%%HOURSTARTVALUE%%%;//Start Trading Hour
input int TradeMinute=%%%MINUTESTARTVALUE%%%;//Start Trading Minute
input int TradeHourEnd=%%%HOURENDVALUE%%%;//End Trading Hour
input int TradeMinuteEnd=%%%MINUTEENDVALUE%%%;//End Trading Minute

这里的所有值都由程序在 EA 创建时填写,这样 EA 就可以立即使用默认设置工作,并且可以在终端开始时刻编译,而无需打开 MetaEditor。所有设置和数组都嵌入到专家顾问中,我觉得很方便。想象一下,你有很多专家顾问,你有不同的设置文件。MetaTrader 4 的暴力算法软件原型表明,我有时会混淆设置。与文本文件相比,这可能会减少可能的功能,但这种方法更可靠。

主函数也已更改:

bool bDay()//Day check
   {
   MqlDateTime T;
   TimeToStruct(Time[0],T);
   for ( int i=0; i<ArraySize(DaysToTrade); i++ )
      {
      if ( T.day_of_week == DaysToTrade[i] ) return true;
      }
   return false;
   }

void Trade()//Trade Function
   {
   double Value;
   Value=PolinomTrade();
   MqlTick LastTick;
   SymbolInfoTick(Symbol(),LastTick);
   MqlDateTime tm;
   TimeToStruct(LastTick.time,tm);
   int MinuteEquivalent=tm.hour*60+tm.min;
   int BorderMinuteStartTrade=HourCorrect(TradeHour)*60+MinuteCorrect(TradeMinute);
   int BorderMinuteEndTrade=HourCorrect(TradeHourEnd)*60+MinuteCorrect(TradeMinuteEnd);
   
   if ( Value > ValueCloseE)
      {
      if ( !bInvert ) CloseBuyF();
      else CloseSellF();
      }
      
   if ( Value < -ValueCloseE)
      {
      if ( !bInvert ) CloseSellF();
      else CloseBuyF();
      }   
   
   if ( !bUseTimeCorridorE )
      {
      if ( double(TimeCurrent()-DatetimeStart)/86400.0 <= DaysToFuture && Value > ValueOpenE && Value <= ValueOpenEMax )
         {
         if ( !bInvert ) SellF();
         else BuyF();
         }
      
      if ( double(TimeCurrent()-DatetimeStart)/86400.0 <= DaysToFuture && Value < -ValueOpenE && Value >= -ValueOpenEMax )
         {
         if ( !bInvert ) BuyF();
         else SellF();
         }      
      }
   else
      {
      if ( BorderMinuteStartTrade > BorderMinuteEndTrade && bDay() )
         {
         if ( !(MinuteEquivalent>=BorderMinuteEndTrade && MinuteEquivalent<= BorderMinuteStartTrade) )
            {
            if ( double(TimeCurrent()-DatetimeStart)/86400.0 <= DaysToFuture && Value > ValueOpenE && Value <= ValueOpenEMax )
               {
               if ( !bInvert ) SellF();
               else BuyF();
               }
      
            if ( double(TimeCurrent()-DatetimeStart)/86400.0 <= DaysToFuture && Value < -ValueOpenE && Value >= -ValueOpenEMax )
               {
               if ( !bInvert ) BuyF();
               else SellF();
               }
            }        
         }
      if ( BorderMinuteStartTrade <= BorderMinuteEndTrade && bDay() )
         {
         if ( MinuteEquivalent>=BorderMinuteStartTrade && MinuteEquivalent<= BorderMinuteEndTrade )
            {
            if ( double(TimeCurrent()-DatetimeStart)/86400.0 <= DaysToFuture && Value > ValueOpenE && Value <= ValueOpenEMax )
               {
               if ( !bInvert ) SellF();
               else BuyF();
               }
      
            if ( double(TimeCurrent()-DatetimeStart)/86400.0 <= DaysToFuture && Value < -ValueOpenE && Value >= -ValueOpenEMax )
               {
               if ( !bInvert ) BuyF();
               else SellF();
               }
            }        
         }      
      }

   if ( bPrintValue ) Print("Value="+DoubleToString(Value));     
   }

我们在这里只添加了控制一周中的几天和一天中的时间间隔的逻辑,而其余的保持不变。顺便说一下,时间间隔不必在0-24小时之间,它可以从一天开始到另一天结束。因此,它可以包括隔夜息点数。根据最初的想法,仓位可以设置在指定的区间中,并且可以随时关闭。也许,一个更好的解决方案是增加强制仓位关闭的时间作为一个单独的操作。到目前为止,在我看来,目前的方法应该产生更稳定的机器人,因为我们使时间区间的边界变得模糊。这样做是为了减少获得随机结果的概率,尽管确认发现的区间及其公式不会突然停止有效,而是逐渐衰减。


分析全局模式

本文给出的所有结果都是基于2010.01.01-2020.01.01期间的训练获得的,如前一篇文章所述。这样做的目的是将本年度作为检查结果的一个提前期。前测期间为2020.01-2020.12.01。

成果质量显著提高。虽然交易数量有所减少,但远期结果有所好转。正如我在上一篇文章中所假设的,初始测试质量的提高会导致前向周期中模式寿命的增加。证据将在下面提供。

让我们从 EURCHF H1 货币对开始。前面的分析显示这个符号有很好的可预测性,所以我决定从它开始。我已经创建了3个优化结果为基础的专家顾问。我还将提供来自MetaTrader 4的两个测试,以显示发现的模式的性能有多大的改进。看看第一个选项:

第一个机器人 EURCHF H1 2010.01.01-2020.01.01 MetaTrader 4

测试手数设为0.1,如果我们将所得的预期收益与上一个计划版本中的预期收益进行比较,它将从之前的8美元增加到54美元。利润因子也显著增加,至少增加了0.5(甚至更多),上一篇文章中的这个参数约为1.14。这是我设法得到的最高利润因子。您可以看到,按一周中的天数和操作时间构建样本对结果的影响。

然而,这并不是最好的结果。由于任务的特殊性,测试的时间非常有限。时间很紧,所有的测试总共花了2-3天。测试在两个核心上进行,每个方案大约需要5-6个小时。

另外,在测试过程中,我发现了一些错误的程序算法,这往往产生错误的结果。我已经修复了它们,但是这减少了找到的模式的数量。不过,这一次足以找到可接受的选择。现在,让我们在 MetaTrader 5 中进行测试:

第一个机器人 EURCHF H1 2010.01.01-2020.01.01 MetaTrader 5

这个测试的交易较少,因为我限制了测试点差,以获得稳定的测试,同时试图获得更高的盈利能力。然而,实践表明,这是没有必要的。我将在文章结尾解释原因。尽管交易数量很少,但这些数据仍然可以被认为是可靠的,因为它们是基于一个很长的样本(它是在第一个选项卡上强制使用公式系数时获得的)。事实上,在第一个选项卡上,我们计算加载段中的所有柱形,因此这是优化的理想基础。当我们把一个大样本的一部分结果转化为一个小样本时,那么第一个样本中包含的数据越多(阶数),越强的模式就越小。

以下是前向周期测试:

未来一年

一年内只有2笔交易,但结果是正面的。

以下是相同间隔的另一个结果:

第二个机器人 EURCHF H1 2010.01.01-2020.01.01 MetaTrader 4

如果没有点差要求,这个公式在MetaTrader 5中是亏损的。但是,如果我们根据获得的预期收益限制点差,就会有好的信号,尽管图表看起来并不好:

第二个机器人 EURCHF H1 2010.01.01-2020.01.01 MetaTrader 5

这张图真的很糟糕,那么我们将来如何从中受益呢?好吧,这个图是基于一个巨大的底层样本,它赋予了所有进一步的样本,这样的图没有什么不好的。不管怎样,它显示了一些利润。让我们检验未来:

未来一年

正如你所看到的,结果是非常好的,虽然没有那么多的交易。实际上,最近几年的测试结果更好,因为经纪人在使用新技术的同时不断减少点差。现在,关于预测及其可能表现的结论是相当好的。然而,没有足够的数据得出最终结论。所以,我对不同的货币对进行了更多的测试——它们将在后面显示。现在,让我们看看同一货币对上的第三个机器人。

MetaTrader 4将不再进行测试。我认为两个完整的方案足以进行比较。所以,这里是第三个方案:

第三个机器人 EURCHF H1 2010.01.01-2020.01.01 MetaTrader 5

图表没那么糟。现在,让我们检验未来:

未来一年

情况一样,所有远期都显示出积极的结果。有人可能会说,结果是相似的,因为所有三个机器人都是在同一货币对的相同数据间隔上训练的,因此它们描述了相同的模式。

或者货币对是如此的好,以至于机器人是正面的。即使是这样,我们也能从中受益。但原因更为普遍。为了证明原因不仅在货币对或间隔内,让我们在不同的时间段检查另一个货币对。

测试 EURUSD H4

我这里不提供 MetaTrader 4 的测试,但是这个货币对的图形看起来和以前的测试一样平滑和漂亮。我在这个时间段发现了两个不同的机器人。第一个:

第一个机器人 EURUSD H4 2010.01.01-2020.01.01 MetaTrader 5

这个图表很难看,但根据我们之前的假设,这不应该让我们不安,因为这只是一个非常大的样本的一部分。检验未来:

未来一年

同样,这种模式全年都有效。即使我们不考虑第一个订单,这是最大的一个,其余部分也是正面的。

现在,让我们检验第二个 EA:

第二个机器人 EURUSD H4 2010.01.01-2020.01.01 MetaTrader 5

这是最平滑的图形。下面是远期测试:

未来一年

一切正常。这张图甚至像一条直线,尽管开头有一座大山。我们在两种不同的货币对上测试了五位专家顾问,所有的前向测试都是正面的。这是很好的统计数据。但是,我需要更多的数据。所以,我决定检查一个完全不同的对和一个不同的时间框架。

测试 EURJPY M5.

EURJPY M5 2010.01.01-2020.01.01 MetaTrader 5

训练间隔看起来很奇怪,2015年出现了明显的逆转,但总的结果看起来不错。这里是前向测试:

未来一年

很难得出结论,但很明显,这是第一次负面的前向检验。然而,我并不认为这个结果是对我们所有算法稳定性假设的反驳。这是因为在2015年的回溯测试中出现了明显的逆转。我并不想让结果符合我对市场的看法,但我看到了这种逆转的原因。相反,我们处理的是2015年发生的逆转的延续。

为了避免这种外部结果的影响,模式搜索算法需要做一些修改。此算法修改将在下一版本中实现。


得出结论

我相信以上测试足以得出关于 MetaTrader 4 和 MetaTrader 5 终端工作专家顾问搜索和测试的几个重要结论。这两个终端的测试器工作方式略有不同。MetaTrader 4 中的报价存储历史而不包含点差数据,因此允许用户将点差设置为“1”,这几乎等于“0”。对于大多数专家顾问来说,点差根本不重要,而我们可以发现不太明显的模式并加以开发。这在 MetaTrader 5 中是不可能的。但MetaTrader 5报价中包含价差信息,可以评估系统的实际盈利能力。

根据我的经验,如果一个最初为 MetaTrader 4 编写的系统没有正确地转换为针对第五版终端,那么这样的系统显示利润的可能性非常小,因为我们能找到的大多数模式都在点差内,因此它们是无用的。在某些情况下,可以在所有信号中找到点差最小的信号。但这并不总是可能的。

通常,通过减少所需的点差,我们也使信号变差了。在本例中,我为 MetaTrader 5 测试手动选择了点差,以获得可接受的测试,但这需要很长时间。这可以通过修改程序自动完成。下图显示了直到最后一个阶段的当前模式搜索过程,以及点差选择过程的可能自动化:

开发周期

该图显示了整个新EA开发周期,从最开始到结束。此周期的当前实现状态以黑色显示。灰色用于可能的修改。进一步可能的改进分为6类:

  1. 用最简单的数学函数处理柱形图数据
  2. 为实现全局模式的专家顾问增加增加利润因子的手数变化机制
  3. 加上马丁格尔和逆马丁格尔机制用于超短周期
  4. 添加一种机制,将好的 EA 合并到一个具有最高交易频率的 EA 中
  5. 向报价中添加点差并避免手动选择点差(整个开发周期完全自动化)
  6. 纠正检测到的优化器错误

我要强调第1、5和6点。暴力公式的可变性将使我们能够从一个暴力优化周期中创建更多不同的专家顾问方案,并提高结果的平均质量。在对以前文章的评论中,用户建议使用傅立叶级数来描述周期性的市场过程。我想把这个任务留给以后修改。但方法并不是那么难。我们不需要将任何函数转换成级数,但我们只需要找到这些项的系数,类似于我使用的泰勒级数。此外,这种方法的可变性将远远高于泰勒级数。我的算法只使用第一阶,因为随着阶数的增加,由于计算复杂度的不成比例的增加,质量会降低。点差也非常重要。既然可以自动过滤信号,为什么还要手动过滤呢?也有柱的时间不匹配,但这些错误已经修改过了。这种不匹配是由于模板的功能和软件的功能不同造成的。

这个软件的最终目的是自动化整个过程,包括为两个平台开发、测试和优化专家顾问。我仍然手动执行的那些操作也可以自动执行。此外,机器可以更快地找到合适的方案。老实说,这是由于我的懒惰,我不想手动配置这些基本的东西,如点差的大小。这个过程大大增加了交易系统的开发周期。重要的是每个时间单位所获得结果的数量和质量,因为这直接影响到进一步的利润。此外,如果您花太多时间配置系统,它可能变得无关紧要。大多数专家顾问的工作时间都很有限,所以每件事都应该做得很快。或者您可以每月使用一个优化器,但这是一个糟糕的解决方案。

如果我看到这个想法的进一步发展潜力,我会继续发展下去。如果这个想法是成功的,我将创建一个类似的梯度提升程序,或将添加一个新的模式的形式,第三阶段的数据分析。因为这种方法本身非常适合作为梯度提升的预测搜索引擎。因此,我们可以简单地使用这种方法找到的预测值,而不是发明和测试新的预测值。然而,这需要更为严肃和深入的修改。所以,我现在不做。但是我上面提到的一些修改已经实现了,现在我正在测试它们并为下一篇文章收集数据。所以,“待续”。

所有早期的文章主要集中在 MetaTrader 4 上,但是实践表明,自动化允许切换到 MetaTrader 5。这种方法将在下一篇文章中完全实现。

MetaTrader 5 是一个更加先进的平台,它提供真实的交易历史和点差数据,这使得评估任何交易系统的真实盈利能力成为可能,因此最好关注这个平台。

在测试过程中,我发现了一个非常重要的因素,它会降低生成的专家顾问的质量,以及在 MetaTrader 5 中对真实的tick数据进行测试后,可以进一步通过选择的EA的百分比。这个因素不仅对于基于机器学习的系统非常重要,对于最初在MetaTrader 4中创建的任何其他专家顾问也非常重要。它与点差以及它如何影响训练的最终数据样本有关。很多用户忽略了点差,或者只是在交易时才考虑价差。但它包含一个无形但非常重要的组成部分,对机器学习过程有着非常负面的影响。我将在下一篇文章中提供它的细节。


结论

本文得出的最重要结论是,如果您想提高程序的实际性能,那么您的交易机器人必须在MQL5中实现。MQL5程序可以在非常接近真实程序的条件下进行测试。深入的分析表明,如果没有MetaTrader 5中的测试,您将无法获得真正可靠的结果。

如本文所示,如果我们使用非常简单的数学公式,例如线性多项式和高次多项式,即使有限的计算能力也可以产生结果。该程序为进一步使用简单的数学函数提供了依据。结果表明,最简单函数的线性和幂次组合可以为交易提供良好的信号。

结果表明,任何一组逻辑条件都可以归结为一个函数形式的定量度量。事实上,这个公式构建了一个指标,它生成对称的正负值,可以用作买入或卖出信号。指标公式总是不同的,这确保了方法的可变性。我认为许多交易者都梦想有一个指标,它可以准确地告诉何时买入,何时卖出。此方法在市场允许的最大范围内实现此机会。下一篇文章将提供更好的结果。我将试图巩固在这一系列文章中得出的所有结论。我还将提供一些细节,这将有助于手动创建交易系统和机器学习过程。

参考资料

  1. 模式搜索的暴力方法
  2. 模式搜索的暴力方法(第二部分):深入


本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/8661

附加的文件 |
Bots.zip (880.06 KB)
DoEasy 函数库中的时间序列(第五十七·部分):指标缓冲区数据对象 DoEasy 函数库中的时间序列(第五十七·部分):指标缓冲区数据对象
在本文中,开发一个对象,其中包含一个指标的一个缓冲区的所有数据。 这些对象对于存储指标缓冲区的数据序列将是必需的。 在其的辅助下,才有可能对任何指标的缓冲区数据,以及其他类似数据进行排序和比较。
DoEasy 函数库中的时间序列(第五十六部分):自定义指标对象,从集合中的指标对象获取数据 DoEasy 函数库中的时间序列(第五十六部分):自定义指标对象,从集合中的指标对象获取数据
本文研究在 EA 中创建自定义指标对象。 我们稍微改进一下库类,并添加一些方法,以便从 EA 中的指标对象获取数据。
MetaTrader 5 中的 WebSockets MetaTrader 5 中的 WebSockets
在引入随 MQL5 API 更新而提供的网络功能之前,MetaTrader 程序与基于 WebSocket 的服务连接和接口的能力受到许多限制。当然,这一切都改变了,在本文中,我们将探讨纯 MQL5 中 WebSocket 库的实现。WebSocket 协议的简要描述将与如何使用生成的库的逐步指南一起给出。
神经网络变得轻松(第七部分):自适应优化方法 神经网络变得轻松(第七部分):自适应优化方法
在之前的文章中,我们利用随机梯度下降法针对网络中的所有神经元按照相同的学习率训练神经网络。 在本文中,我提议着眼于自适应学习方法,该方法能够改变每个神经元的学习率。 我们还将研究这种方法的利弊。