外汇EA编写教程:通过辅助指标降低内存消耗

1。问题

也许您已经使用或创建了与其他辅助度量一起操作的EA事务或度量。

例如,著名的MACD指标使用两份EMA(指数移动平均值)指标来计算其值之间的差异:

实际上,这种综合指标相当于几个简单的指标。例如,前面提到的MACD消耗的内存和处理器时间是单个EMA的三倍多,因为它必须将内存分配给主指示器的缓存和所有辅助指示器的缓存。

除了MACD之外,还有许多复杂的指标使用两个以上的辅助指标。

此外,如果满足以下条件,这些内存开销将显著增加:

  • 指标使用多个时间帧(例如,它跟踪多个时间帧中波动的并行性),从而为每个时间帧创建单独的辅助指标副本;
  • 该指数是多种货币。
  • 交易者使用指标与多种货币进行交易(我认识同时交易20多种货币的人)。

这些条件的组合会导致计算机内存不足(我知道实际情况,当客户机因为使用这些度量而需要千兆字节的内存时)。以下是metatrader 5客户机内存不足的示例:

在这种情况下,客户机无法将度量放在图表中,或者无法正确计算度量(如果度量的代码不处理内存分配错误),甚至无法关闭它们。

幸运的是,客户机可以使用更多的虚拟内存,即在硬盘上存储一些信息,以弥补内存不足。所有程序都在运行,但速度很慢…

2。测试复合指示剂

为了在本文的上下文中继续我们的调查,让我们创建一个复合指标,一个比MACD更复杂的指标。

使之成为趋势开始的指示器。它将聚合来自五个时间帧的信号,如h4、h1、m15、m5、m1。这将使共振能够确定尺寸的上升趋势,从而提高预测的可靠性。作为每个时间帧的信号源,我们将使用一号库和稻谷渠道指标,这些指标包含在MetaTrader 5交付中:

  • 如果一号木库的天干(红色)线高于kijun(蓝色)线,则趋势为向上;如果一号木库的天干(红色)线低于kijun(蓝色)线,则趋势为向下。

  • 如果价格高于价格通道中线,则趋势为向上;如果价格低于中线,则趋势为向下。

我们的指标总共使用10个支持指标:5个时间框架,每个框架两个指标。让我们称之为趋势指示器。

以下是其完整的源代码(也包括在本条附件中):

#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_minimum -1
#property indicator_maximum  1

#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  DarkTurquoise

// The only buffer of the indicator
double ExtBuffer[];

// Timeframes of auxiliary indicators
ENUM_TIMEFRAMES TF[5] = {PERIOD_H4, PERIOD_H1, PERIOD_M15, PERIOD_M5, PERIOD_M1};

// Handles of auxiliary indicators for all timeframes
int h_Ichimoku[5], h_Channel[5];

//+------------------------------------------------------------------+
void OnInit()
  {
   SetIndexBuffer(0, ExtBuffer);
   ArraySetAsSeries(ExtBuffer, true);
   
   // Create auxiliary indicators
   for (int itf=0; itf<5; itf++)
     {
      h_Ichimoku[itf] = iCustom(Symbol(), TF[itf], 
                                "TestSlaveIndicators//Ichimoku",
                                9, 26, 52
                               );
      h_Channel [itf] = iCustom(Symbol(), TF[itf],
                                "TestSlaveIndicators//Price_Channel",
                                22
                               );
     }
  }
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
   ArraySetAsSeries(time, true);
  
   int limit = prev_calculated ? rates_total - prev_calculated : rates_total -1;

   for (int bar = limit; bar >= 0; bar--)
     {
      // Time of the current bar
      datetime Time  = time [bar];
      
      //--- Gather signals from all timeframes
      double Signal = 0; // total signal
      double bufPrice[1], bufTenkan[1], bufKijun [1], bufMid[1], bufSignal[1];
      for (int itf=0; itf<5; itf++)
        {
         //=== Bar price
         CopyClose(Symbol(), TF[itf], Time, 1, bufPrice);
         double Price = bufPrice[0];

         //=== The Ichimoku indicator         
         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== The channel indicator
         CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         double Mid = bufMid[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;
        }
        
      ExtBuffer[bar] = Signal/10;
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+

您应该在一个具有从指示器收集信号的最短时间框架的图表上使用此指示器;只有这样,您才能看到所有的次要趋势。在我们的例子中,M1时间框架。指标如下:

现在让我们讨论最重要的部分:让我们计算这个度量所消耗的内存量。

看看Ichimoku度量的源代码(本文附带完整的代码):

#property indicator_buffers 5

以及价格渠道指标的源代码(本文附有完整的代码):

#property indicator_buffers 3

在这些代码行中,您可以看到已经创建了八个缓存。乘以5个时间帧。为趋势指示器本身添加缓存。我们总共有41个缓存!在这些看似简单(在图表上)的指标背后,有着令人惊讶的价值观。

在客户机的默认属性下,缓存包含大约100000个双精度类型的值,每个值占用8个字节的内存。所以41个缓存消耗了大约31MB的内存。这些只是值本身;除了值本身之外,我不知道缓存中存储了什么服务信息。

你可以说,“31MB不算太多”。然而,当交易者使用多个货币对时,这个数量将成为一个问题。除了度量之外,图表本身还消耗大量内存。与指标不同,每列一次包含几个值:开盘价、最高价、最低价、收盘价(ohlc)、时间和数量。我们如何将它存储在计算机中?

三。问题解决方案

当然,您可以在计算机上安装更多的内存。但是,如果出于技术、财务或任何其他原因,这种情况不适合您,或者如果您已经耗尽了可以安装的内存量,但内存仍然不足,则应检查这些内存消耗指标,并减少它们的消耗。

因此…回想一下你在学校学的几何学。假设复合度量的所有缓存都是实心矩形:

这个矩形的区域是消耗的内存。您可以通过减小宽度或高度来减小面积。

在这种情况下,宽度是在其上绘制索引的列数。Height是索引缓存的数量。

4。减少列数

4.1. 简单的解决方法

要调整MetaTrader设置,您不必是程序员:

减少“图表中的最大条形图”(图表中的最大列数)的值会减小这些窗口中索引缓存的大小。它简单而有效,每个人都可以操作它(如果交易者不需要很长的价格历史来交易的话)。

4.2。还有其他的解决办法吗?

MQL5程序员知道索引缓存被声明为索引中没有默认大小的动态数组。例如,Ichimoku的五个缓存:

double    ExtTenkanBuffer[];
double    ExtKijunBuffer[];
double    ExtSpanABuffer[];
double    ExtSpanBBuffer[];
double    ExtChinkouBuffer[];

未指定数组大小,因为它是由MetaTrader5客户机为整个可用历史设置的。

在oncalculate函数中也是如此:

int OnCalculate (const int rates_total,      // size of the array price[]
               const int prev_calculated,  // number of bars processed at the previous call
               const int begin,            // the start of reliable data
               const double& price[]       // array for the calculation
   );

在这个函数中,价格缓存被传递给指示器。客户机已经为它分配了内存,程序员不能影响它的大小。

此外,MQL5允许将一个指标的缓存用作另一个指标的价格缓存(“根据另一个指标绘制一个指标”)。但即使在这里,程序员也不能设置大小限制;他们只是通过度量句柄。

因此,在MQL5中,不能限制索引缓存的大小。

5。减少缓存
的数量

在这里,程序员有很多选择。我发现了几种简单的理论方法来减少复合度量的缓存数量。但是,所有这些都意味着减少辅助度量的缓存数量,因为主度量中需要所有缓存。

让我们详细看看这些方法,看看它们是否有效,它们有哪些优点和缺点。

5.1。需要“方法

如果辅助指示器包含多个缓存,则主指示器可能不需要所有这些缓存。因此,我们可以禁用未使用的度量来释放它们消耗的内存。为此,我们需要对辅助度量的源代码进行一些更改。

让我们改变价格渠道,这是我们的一个辅助指标。它包含三个缓存,Trender只读取其中一个;因此我们需要删除不必要的内容。

本文附上价格渠道(初始指数)和大米渠道需求(完全修改)指数的完整代码。此外,我将只描述所做的更改。

首先,将缓存数从3减少到1:

//#property indicator_buffers 3
  #property indicator_buffers 1
//#property indicator_plots   2
  #property indicator_plots   1

然后,删除两个不必要的缓存阵列:

//--- indicator buffers
//double    ExtHighBuffer[];
//double    ExtLowBuffer[];
 double    ExtMiddBuffer[];

现在,如果我们试图编译这个度量,编译器将显示调用这些数组的所有行:

这种方法可以很快找到需要更改的内容。当指示码很大时,比较方便。

在我们的例子中,有四个未声明的标识符行。让我们纠正它们。

如我们所料,其中两个在OnInit。但除此之外,我们还必须删除包含必要extmiddbuffer的行。相反,我们使用另一个缓存索引添加类似的行。因为索引不再包含索引2的缓存,所以只有索引0可用:

//   SetIndexBuffer(0,ExtHighBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtLowBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtMiddBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtMiddBuffer,INDICATOR_DATA);

如果计划在可视模型中使用“剪切”指示器,请考虑应使用缓存索引更改外观设置。在这种情况下:

//#property indicator_type1   DRAW_FILLING
  #property indicator_type1   DRAW_LINE

如果不需要可视化,可以跳过外观更改-它不会导致错误。

让我们继续查看未声明的标识符列表。最后两个(和可预测的)更改是oncalculate,它填充索引缓存数组。因为所需的extmiddbuffer调用已删除的exthighbuffer和extlowbuffer,所以中间变量将替换它们:

   //--- the main loop of calculations
   for(i=limit;i<rates_total;i++)
     {
//      ExtHighBuffer[i]=Highest(High,InpChannelPeriod,i);
        double      high=Highest(High,InpChannelPeriod,i);
//      ExtLowBuffer[i]=Lowest(Low,InpChannelPeriod,i);
        double      low=Lowest(Low,InpChannelPeriod,i);
//      ExtMiddBuffer[i]=(ExtHighBuffer[i]+ExtLowBuffer[i])/2.0;;
        ExtMiddBuffer[i]=(   high         +   low         )/2.0;;
     }

正如你所看到的,在整个“外科手术”中没有困难。快速找到你需要的东西;经过几次“手术刀切割”,两个药箱被排除在外。总体复合指标趋势器中总共保存了10个缓存(2 x 5个时间帧)。

您可以在另一个指标下打开price_channel和rice_channel-need,以查看消失的缓存:

0

要在趋势指标中使用Price_Channel-Need,我们需要在趋势代码中将辅助指标“Price_Channel”的名称更正为“Price_Channel-Need”。此外,我们需要将缓存的索引从2更改为0。本文包括现成的趋势需要代码。

5.2。聚合法

如果主指示器读取辅助指示器的多个缓存数据,然后执行聚合操作(如累积或比较),则不必在主指示器中执行此操作。我们可以使它成为一个辅助指示器,然后将结果传递给主指示器。因此,不需要多个缓存;一个缓存可以替换所有缓存。

在这种情况下,这种方法适用于一分子。因为Trender使用了这个度量的两个缓存(0-tenkan,1-kijun);

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;

如果我们将Ichimoku的0和1缓存聚合到一个信号缓存中,那么上面的Trender片段应该替换为:

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufSignal);
         
         Signal += bufSignal[0];

本文附带了完整的Trender聚合代码。

现在让我们看看应该对一号木库进行的关键更改。

此外,此度量包含未使用的缓存。因此,除了“聚合”方法外,我们还可以应用“需要”方法。因此,Ichimoku中只剩下五个缓存中的一个,即聚集必要缓存的缓存:

//#property indicator_buffers 5
  #property indicator_buffers 1
//#property indicator_plots   4
  #property indicator_plots   1

让我们给唯一缓存取一个新名称:

//--- indicator buffers
//double    ExtTenkanBuffer[];
//double    ExtKijunBuffer[];
//double    ExtSpanABuffer[];
//double    ExtSpanBBuffer[];
//double    ExtChinkouBuffer[];
  double    ExtSignalBuffer[];

新名称是有意义的-它允许您从度量中删除所有以前使用的缓存名称。它可以快速找到所有应该更改的行(使用需求方法中描述的编译)。

如果要在图表中可视化度量,请不要忘记更改外观设置。您还应该考虑在这种情况下,与使用的两个缓存相比,聚合缓存具有不同的作用域。现在,它不再显示价格派生,而是显示两个缓存中较大的缓存。在图表下方的单独窗口中显示这样的结果更方便:

//#property indicator_chart_window
  #property indicator_separate_window

因此,在OnInit中进行了以下更改:

//--- indicator buffers mapping
//   SetIndexBuffer(0,ExtTenkanBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtKijunBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtSpanABuffer,INDICATOR_DATA);
//   SetIndexBuffer(3,ExtSpanBBuffer,INDICATOR_DATA);
//   SetIndexBuffer(4,ExtChinkouBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtSignalBuffer,INDICATOR_DATA);

最有趣的部分是精确计算。注意:直接删除三个不必要的缓存(我们使用了“need”方法),用临时变量tenkan和kijun替换必要的exttenkan缓冲区和extkijunbuffer。这些变量在循环结束时用于计算聚合缓存extsignalbuffer:

   for(int i=limit;i<rates_total;i++)
     {
//     ExtChinkouBuffer[i]=Close[i];
      //--- tenkan sen
      double high=Highest(High,InpTenkan,i);
      double low=Lowest(Low,InpTenkan,i);
//     ExtTenkanBuffer[i]=(high+low)/2.0;
       double  Tenkan    =(high+low)/2.0;
      //--- kijun sen
      high=Highest(High,InpKijun,i);
      low=Lowest(Low,InpKijun,i);
//     ExtKijunBuffer[i]=(high+low)/2.0;
       double  Kijun    =(high+low)/2.0;
      //--- senkou span a
//     ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
      //--- senkou span b
      high=Highest(High,InpSenkou,i);
      low=Lowest(Low,InpSenkou,i);
//     ExtSpanBBuffer[i]=(high+low)/2.0;

       //--- SIGNAL
       double Signal = 0;
       if (Tenkan > Kijun) Signal++;
       if (Tenkan < Kijun) Signal--;
       ExtSignalBuffer[i] = Signal;
     }

总共减少了四个缓存。如果我们只将“需要”方法应用于一号木库,我们将只减少三个缓存。

整个趋势器中总共保存了20个缓存(4 x 5个时间帧)。

本文附带了完整的Ichimoku聚合代码。要将此指示器与原始指示器进行比较,请在图表上打开它们。如您所记得,修订后的指标现在显示在图表
下方的单独窗口中。

1

5.3。包含”方法

减少缓存数量的最根本方法是删除所有辅助指标。如果我们这样做,那么我们的度量中只剩下一个缓存——属于主度量的缓存。缓存的数量不能少于。

将辅助指示灯的代码移到主指示灯上也可以达到相同的结果。有时似乎很费时,但最终的结果是值得的。最大的困难是重写从指示器中移动的代码。这些代码并不打算在其他度量的代码中运行。

以下是重写期间的主要问题:

  • 名称冲突。变量和函数具有相同的名称(尤其是系统函数,如oncalculate)。
  • 缺少缓存。在某些度量中,如果度量逻辑与缓存中数据的存储/处理密切相关,则它可能成为无法克服的障碍。在这种情况下,用简单数组替换缓存并不是万能的,因为我们的目标是减少内存消耗。重要的是要拒绝在内存中存储大量的历史数据。

让我们举例说明解决这些问题的有效方法。

每个辅助指标都应该编译为一个类。然后,度量的所有变量和函数都有唯一的名称(在它们的类中),并且不会与其他度量冲突。

如果移动了许多指示器,可以考虑将这些类标准化,以避免在使用中混淆。为此,请创建一个基本指示器类,然后从该类继承所有辅助指示器类。

我写的课程如下:

class CIndicator
  {
protected:
   string symbol;             // currency pair
   ENUM_TIMEFRAMES timeframe;  // timeframe

   double Open[], High[], Low[], Close[]; // simulation of price buffers
   int BufLen; // necessary depth of filling of price buffers

public:
   //--- Analogs of standard functions of indicators
   void Create(string sym, ENUM_TIMEFRAMES tf) {symbol = sym; timeframe = tf;};
   void Init();
   void Calculate(datetime start_time); // start_time - address of bar that should be calculated
  };

现在让我们基于这个类为ichimoku度量创建一个类。首先,具有原始名称的输入参数以属性的形式写入。以后不要更改任何指标代码:

class CIchimoku: public CIndicator
  {
private:
   // Simulation of input parameters of the indicator
   int InpTenkan;
   int InpKijun;
   int InpSenkou;

保留所有缓存的名称。是的,这就是你听到的-我们宣布了这个指标的所有五个缓存。但它们是假的。每个缓存只包含一列:

public:   
   // Simulation of indicator buffers
   double ExtTenkanBuffer [1];
   double ExtKijunBuffer  [1];
   double ExtSpanABuffer  [1];
   double ExtSpanBBuffer  [1];
   double ExtChinkouBuffer[1];   

我们为什么要这样做?以便在以后减少代码更改量。你会看到的。重新定义继承的方法cichimoku。计算并填写从一号木库获得的计算函数代码。

请注意,移动此函数时,将删除“按历史记录循环”列。现在,只计算具有指定时间的一列。计算的主代码保持不变。这就是为什么我们如此小心地保存度量的所有缓存和参数名。

您还应该注意,价格缓存中填充了calculate方法的初始值。值的数目与计算列所需的数目相同。

   void Calculate(datetime start_time)
     {
      CopyHigh (symbol,timeframe,start_time,BufLen,High);
      CopyLow  (symbol,timeframe,start_time,BufLen,Low );
      CopyClose(symbol,timeframe,start_time,1     ,Close);

//    int limit;
      //---
//    if(prev_calculated==0) limit=0;
//    else                   limit=prev_calculated-1;
      //---
//    for(int i=limit;i<rates_total;i++)
      int i=0;
        {
         ExtChinkouBuffer[i]=Close[i];
         //--- tenkan sen
         double high=Highest(High,InpTenkan,i);
         double low=Lowest(Low,InpTenkan,i);
         ExtTenkanBuffer[i]=(high+low)/2.0;
         //--- kijun sen
         high=Highest(High,InpKijun,i);
         low=Lowest(Low,InpKijun,i);
         ExtKijunBuffer[i]=(high+low)/2.0;
         //--- senkou span a
         ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
         //--- senkou span b
         high=Highest(High,InpSenkou,i);
         low=Lowest(Low,InpSenkou,i);
         ExtSpanBBuffer[i]=(high+low)/2.0;
        }
      //--- done
//    return(rates_total);     
     };

当然,我们将跳过保留原始代码。但是在这种情况下,我们必须重写大部分代码,这需要理解其逻辑。在这种情况下,度量是简单易懂的。但如果指标很复杂呢?我已经向你展示了在这种情况下提供帮助的方法。

现在,让我们来填一下“西奇莫库”。init方法;这里,一切都很简单:

   void Init(int Tenkan = 9, int Kijun = 26, int Senkou = 52)
     {
      InpTenkan = Tenkan; InpKijun = Kijun; InpSenkou = Senkou;
      BufLen = MathMax(MathMax(InpTenkan, InpKijun), InpSenkou);
     };

Ichimoku包含另外两个应该复制到cichimoku类的函数:highest和lowest。它们在价格缓存的指定部分内搜索最高和最低价格。

我们的价格缓存不是真实的;它们非常小(您已经看到它们在上面的计算方法中被填充)。这就是为什么我们必须稍微改变最高和最低函数的逻辑。

在本例中,我还遵循了进行最小更改的原则。所有修改包括添加一行代码以将缓存中列的索引从全局索引(当缓存的长度为整个可用历史记录时)更改为本地索引(因为当前价格缓存仅包含计算一个指标列所需的值):

   double Highest(const double&array[],int range,int fromIndex)
     {
       fromIndex=MathMax(ArraySize(array)-1, 0);
      double res=0;
   //---
      res=array[fromIndex];
      for(int i=fromIndex;i>fromIndex-range && i>=0;i--)
        {
         if(res<array[i]) res=array[i];
        }
   //---
      return(res);
     }

用同样的方法修改最低的方法。

对price_channel索引进行了类似的修改,但它将被表示为一个名为cchannel的类。这两个类的完整代码显示在本文所附的trender include文件中。

我已经描述了移动代码的主要方面。我认为这些方法适用于大多数指标。

带有非标准设置的指示器可能会造成额外的困难。例如,price_channel包含一些无关紧要的代码行:

   PlotIndexSetInteger(0,PLOT_SHIFT,1);
   PlotIndexSetInteger(1,PLOT_SHIFT,1);

它们表示指示图在一列上的位移。在我们的例子中,它将导致copyBuffer和copyHigh函数使用两个不同的列,尽管在它们的参数中设置了相同的柱坐标(时间)。

在Trender中解决了这个问题,包括(在cchannel类的必要部分添加“one”,这与cichimoku类完全不同,cichimoku类中不存在)。所以,如果你需要这样一个“狡猾”的指示器,你需要知道在哪里可以找到它。

现在我们已经完成了移动,并且这两个指标都写为趋势器include指标中的两个类。接下来,更改调用这些度量的方式。在Trender中,我们有句柄数组;在Trender中,对象数组替换它们:

// Handles of auxiliary indicator for all timeframes
//int h_Ichimoku[5], h_Channel[5];
// Instances of embedded auxiliary indicators
CIchimoku o_Ichimoku[5]; CChannel o_Channel[5];

现在,在OnInit中创建辅助指标如下:

   for (int itf=0; itf<5; itf++)
     {
      o_Ichimoku[itf].Create(Symbol(), TF[itf]);
      o_Ichimoku[itf].Init(9, 26, 52);
      o_Channel [itf].Create(Symbol(), TF[itf]);
      o_Channel [itf].Init(22);
     }

而不是OnCalculate中的CopyBuffer,直接调用对象的属性:

         //=== The Ichimoku indicator
         o_Ichimoku[itf].Calculate(Time);

         //CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         //double Tenkan = bufTenkan[0];
         double Tenkan = o_Ichimoku[itf].ExtTenkanBuffer[0];

         //CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         //double Kijun  = bufKijun [0];
         double Kijun  = o_Ichimoku[itf].ExtKijunBuffer [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== The Channel indicator
         o_Channel[itf].Calculate(Time);

         //CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         //double Mid = bufMid[0];
         double Mid = o_Channel[itf].ExtMiddBuffer[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;

减少了40个缓存。这是值得的。

在基于上述“需求”和“聚合”方法对Trender进行每次修改后,我都在可视化模式下对指标进行了测试。

现在让我们做这个测试:打开图表上的初始指示器(趋势器)和修改后的指示器(趋势器包括)。我们可以说一切都是对的,因为这两个指标的行完全相同:

2

5.4。我们能一个接一个地进行吗?

我们考虑了三种方法来减少辅助度量的缓存数量。但是,如果我们试图从根本上改变这种方法——如果我们试图减少同时留在内存中的缓存的数量,而不是减少它们的总数,又会怎样呢?换句话说,我们将逐个将度量加载到内存中,而不是同时将所有度量加载到内存中。我们需要组织一个“环路”:创建一个辅助指标,读取它的数据,删除指标,创建下一个辅助指标,等等,直到我们遍历所有的时间帧。Ichimoku度量的最大缓存数-5。因此,理论上,您可以在内存中保留多达五个缓存(加上一个主要指标),总共减少35个缓存!

有可能吗?在MQL5中,有一个用于删除指标-指标发布的特殊功能。

然而,这并不像看上去那么简单。MetaTrader 5主要关注MQL5程序的高速运行,这就是为什么所有调用的时间序列都存储在缓存中的原因——以防其他EAS、度量或脚本需要使用它们。只有在长时间不被调用的情况下才将它们卸载到空闲内存中。等待时间长达30分钟。

因此,不断创建和删除度量不会立即节省大量内存。但是,它可以使计算机运行速度显著减慢,因为每次创建它时,它都会计算整个价格历史记录的指标。考虑在主要指标的每个支柱上执行这些操作是多么合理…

然而,对于头脑风暴,“指示器循环”的概念仍然很有趣。如果您想提出其他优化度量内存的原始想法,请在本文中添加您的评论。也许它们将在下一篇关于这个主题的文章中用于理论或实践。

6。测量实际内存消耗

在前面的章节中,我们实现了三种有效的方法来减少辅助度量的缓存数量。现在让我们分析一下它是如何减少实际的内存消耗的。

我们将使用Microsoft Windows操作系统中的任务管理器来测量客户端的内存消耗。在进程选项卡中,您将看到客户机消耗的RAM和虚拟内存。例如:

3

根据以下算法测量,该算法允许查看客户端的最小内存消耗(接近目标内存消耗):

  1. 从MetaQuotes演示服务器下载深入的价格历史记录(自动下载交易品种历史记录足以对交易品种运行测试);
  2. 为下一个测量设置客户端(打开所需的图表和度量),并重新启动客户端以清除内存中不必要的信息;
  3. 重新启动后等待客户端完成所有指标的计算。处理器的零负载意味着计算完成;
  4. 将客户端最小化到任务栏(通过单击客户端右上角的标准最小化按钮)。此时,它将释放不用于计算的内存(在上面的屏幕截图中,您可以看到内存消耗仍在最小化的示例—您可以看到RAM消耗远远小于虚拟内存消耗);
  5. 在任务管理器中,添加“内存使用”(RAM)和“虚拟内存大小”列的值。这是它们在WindowsXP中的名称,不同版本的操作系统可能有稍微不同的名称。

测量参数:

  • 为了使测量更准确,我们将使用metaquotes演示帐户中的所有货币对,而不是价格图(22 M1图表)。然后我们计算平均值;
  • “图表中的最大条形”选项(如第4.1节所述)的标准值为-100000;
  • 操作系统-Windows XP,32位。

需要什么测量?有两种解释:

  1. 即使趋势指数使用41个缓存,这并不意味着它会消耗41 x 100000列。原因是缓存分布在五个时间帧上,大时间帧包含的列比小时间帧少。例如,欧元兑美元的M1历史记录包含约400万支柱,而H1历史记录仅包含约70000支柱(4000000/60)。这就是为什么你不应该期望在减少趋势者的缓存数量之后,内存消耗也会减少。
  2. 内存不仅被索引本身消耗,而且被索引使用的价格序列消耗。Trender使用五个时间框架。因此,如果我们多次减少缓存的数量,总的内存消耗将不会减少相同的倍数。因为内存中的五个价格序列都将被使用。

在测量消耗量时,您可能会面临影响内存消耗的其他因素。这就是我们进行这些实际测量的原因——将实际节约作为优化结果的指标。

下表列出了所有测量结果。首先,我测量了一个空客户机的内存消耗。通过从下一个度量中减去这个值,我们可以计算图表的内存消耗。在从下一个度量中减去客户机消耗的内存和图表之后,我们得到每个度量所消耗的内存的大小。

内存消耗对象 指示器缓存编号 时间框架编号 内存消耗
客户端 0 0 客户端38兆字节
0 1 空图表12 MB
趋势指数 41 5 一个指示器46 MB
趋势者需要指数 31 5 42 MB的指示器
趋势聚集指数 21 5 指示器37 MB
趋势包括指数 1 5 一个指示灯,38 MB

根据测量结果,得出如下结论:

  • 减少索引缓存的数量不会导致索引使用的内存减少。

本章前文说明了这种效应的原因。或许指标使用的时间框架越短,减少缓存数量的效果就越显著。

  • 将辅助度量的代码移动到主度量中并不总是产生最佳结果。

那么为什么“包容”方法不能像“聚合”方法那样有效呢?为了确定原因,我们需要记住这些度量的代码中的主要差异。在“聚合”方法中,计算所需的价格序列作为oncalculate中的输入数组由客户机传递。在包含方法中,copyhigh、copylow和copyclose用于主动请求每个列(所有时间帧)的所有数据。当使用这些函数时,价格时间序列的高速缓存可能会导致额外的内存消耗。

总结

本文介绍了三种有效的降低辅助指示灯内存消耗的方法,并介绍了一种通过调整客户端来节省内存的方法。

应根据您具体情况的可接受性和适当性采取适当的方法。保存的缓存和兆字节的数量取决于您正在处理的度量:您可以从一些度量中“削减”很多,但在其他度量中您不能做任何事情。

内存节省可以增加客户机中同时使用的货币对的数量。这提高了交易组合的可靠性。这样一个简单的计算机技术资源的考虑,可以转化为财务资源,由你自己支配。

圈地

附件包含本条所述指标。要使所有内容正常工作,请将它们保存在“mql5/indicators/testslaveindicators”文件夹中,其中所有版本的趋势器指示器(趋势器除外)都将查找辅助指示器。

本文由MetaQuotes Software Corp.翻译自俄语原文
,网址为https://www.mql5.com/ru/articles/259。

附加文件下载zip ichimoku.mq5(4.97 kb)price_channel.mq5(4.34 kb)price_channel-need.mq5(4.77 kb)ichimoku-aggregate.mq5(5.57 kb)、trender.mq5(2.94 kb)、trender-aggregate.mq5(2.75 kb)、trender-include.mq5(9.54 kb)trender-need.mq5(2.94 kb)

 

 


MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经(www.myfxtop.cn)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投