English Русский Español Deutsch 日本語 Português
preview
利用对象轻松制作复杂指标

利用对象轻松制作复杂指标

MetaTrader 5示例 | 14 十一月 2022, 09:29
1 095 0
Manuel Alejandro Cercos Perez
Manuel Alejandro Cercos Perez

1. 概述

如果您曾经尝试过创建或修改复杂指标,您也许知晓缓冲区数量增加时出现的一些问题:您需要为缓冲区声明大量的双精度数组,将它们设置为缓冲区,为它们进行配置...

随之而来的是作图板:您必须声明您正在使用的作图板类型,为其配置所有属性,然后确保一切正确,并且您已声明正确数量的缓冲区和作图板(如果少了,那么您将得到数组超界错误,或不可见的作图板, 直到您注意到问题)。

最后而来的是处置缓冲区的数据:如果您打算将来自大量缓冲区的数据合并(例如,取 10 个缓冲区的平均/最大/最小值,合并为一),那么您需要编写冗长的重复代码行逐个比较/组合每个缓冲区,或者依靠一些聪明的小技巧,利用宏替换或函数来节省一些空间。 结果大概是一样的:大量代码行和重复功能,非常复杂和混乱,极易出现漏洞。 如果您在任何地方犯了疏漏,那么检测并纠正它将是一场噩梦!

当制作复杂的指标时,无论是功能或视觉,这类情况都会对初学者(甚至高级程序员)造成障碍。 然而,有一个小技巧并不是每个人都清楚,它可以令您的指标编码更快捷、更容易:

您可以将对象内部包含的数组设置为缓冲区

在本文中,我们将探讨此技巧能为我们提供的选项,及随之而来的解决方案,从而能够运用面向对象编程将此技巧应用于任何其它情况。


2. 第一个例子

在我们开始制作指标之前,我们先看看包含缓冲区数组的对象,其最基本形式是什么样子的:

class CIndicatorPlot
{
public:
   double            array[];
};

它只有一个公开数组。 至于目前,重要的是它是公开的,如此我们就可以在将其设置为缓冲区,或设置/访问它拥有的数据(与任何其它指标一样)。

现在我们来处置指标:为了应用一些概念,我们将创建一个指标,该指标显示 10 个具有不同周期和平均值的 RSI。 我们将从属性、输入、和 OnInit 函数开始。

#property indicator_buffers 11
#property indicator_plots 11

input int firstPeriod = 6;
input int increment = 2;

CIndicatorPlot indicators[];
int handles[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{

   ArrayResize(indicators, 11);
//--- indicator buffers mapping

   for (int i=0; i<11; i++)
   {
      SetIndexBuffer(i, indicators[i].array, INDICATOR_DATA);
      PlotIndexSetInteger(i, PLOT_DRAW_TYPE, DRAW_LINE);
   }

   for (int i=0; i<10; i++)
      PlotIndexSetInteger(i, PLOT_LINE_COLOR, clrRed);


   PlotIndexSetInteger(10, PLOT_LINE_COLOR, clrCyan);
   PlotIndexSetInteger(10, PLOT_LINE_STYLE, STYLE_DASH);
   PlotIndexSetInteger(10, PLOT_LINE_WIDTH, 2);

   ArrayResize(handles, 10);
   for (int i=0; i<10; i++)
      handles[i] = iRSI(NULL, PERIOD_CURRENT, firstPeriod+i*increment, PRICE_CLOSE);


//---
   return(INIT_SUCCEEDED);
}

请注意,我们只用到了 2 个属性:indicator_buffersindicator_plots。 除了共用的(版权,链接,版本,单独/图表窗口......)之外,始终需要这两个。 其它属性(线条颜色、绘制类型...)都是可选的,但为了令代码更紧凑,我们将在循环中利用 PlotIndexSetInteger 配置它们。对于该指标,我们需要为每个拥有不同周期的 RSI 提供 10 个缓冲区,还需要一个额外缓冲区保存它们的平均值。 我们将所有这些放在一个数组当中。 此外,我们还要在 OnInit 中创建指标的句柄。 

现在,少量计算和数据复制...

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
{
//---
   int limit = MathMax(0, prev_calculated-1);

   for (int i=0; i<10; i++)
   {
      if (limit==0)
         CopyBuffer(handles[i], 0, 0, rates_total-limit, indicators[i].array);
      else
      {
         double newValues[];
         CopyBuffer(handles[i], 0, 0, rates_total-limit, newValues);

         for (int k=0; k<rates_total-limit; k++)
         {
            indicators[i].array[limit+k] = newValues[k];
         }
      }
   }

   for (int i=limit; i<rates_total; i++)
   {
      indicators[10].array[i] = 0.0;
      for (int j=0; j<10; j++)                            
         indicators[10].array[i] +=indicators[j].array[i];

      indicators[10].array[i]/=10.0;
   }


//--- return value of prev_calculated for next call
   return(rates_total);
}

请注意,计算来自所有缓冲区的平均值,现在就像执行循环一样简单。 如果每个缓冲区都是在全局范围内都声明为双精度数组(像往常一样),那么它们相加则不那么容易,或难以在少量代码行内完成。

最后,不要忘记释放指标句柄...

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   for (int i=0; i<10; i++)
      IndicatorRelease(handles[i]);
}
//+------------------------------------------------------------------+

这是我们的最终结果:

对于代码的长度来说还不错,但是正如我们将在下一章节中将看到的那样,它仍然有一些改进的空间。


3. 添加更多选项

尽管该类在初始化阶段配置属性(而不是 #property)时为我们节省了一些空间,但我们仍然必须手工配置缓冲区和作图板,而且每次都如此并非很简单。 难道没有更有效的方法吗?答案是肯定的,我们通过向类委派功能来实现这一点。 答案是肯定的,我们能依靠向类委派功能来做到这一点。

首先,我们将往类中添加一些稍后需要的附加函数。

class CIndicatorPlot
{
private:
   int               indicator_plot;

public:
   double            array[];

   void              SetBuffer(int &buffer, int &plot);
   void              SetLineWidth(int width);
   void              SetLineStyle(ENUM_LINE_STYLE style);
   void              SetLineColor(color line_color);
   void              SetLabel(string label);
};

SetBuffer 函数设置指标缓冲区和作图板。 通过引用传递的两个变量,若一个对象改变了它们的值,则会反映在其它对象下一次对它们的调用中。 保存作图板索引,以便设置其它属性。

其余的设置函数是简单的作图板属性设置器。

//+------------------------------------------------------------------+
void CIndicatorPlot::SetBuffer(int &buffer,int &plot)
{
   indicator_plot = plot;

   SetIndexBuffer(buffer, array, INDICATOR_DATA);
   PlotIndexSetInteger(indicator_plot, PLOT_DRAW_TYPE, DRAW_LINE);

   buffer++; //Increment for other steps (One buffer in this case)
   plot++;   //Increment one plot in any case
}

//+------------------------------------------------------------------+
void CIndicatorPlot::SetLineWidth(int width)
{
   PlotIndexSetInteger(indicator_plot, PLOT_LINE_WIDTH, width);
}

//---
//...

为了帮助在指标中添加一些视觉趣味,我们还将创建一个插值颜色函数,稍后我们将用到它:

//+------------------------------------------------------------------+
//| Function to linearly interpolate 2 colors                        |
//+------------------------------------------------------------------+
color InterpolateColors(color colorA, color colorB, double factor)
{
   if (factor<=0.0) return colorA;
   if (factor>=1.0) return colorB;

   int result = 0;

   for (int i=0; i<3; i++) //R-G-B
   {
      int subcolor = int(
                        ((colorA>>(8*i))&(0xFF))*(1.0-factor) +
                        ((colorB>>(8*i))&(0xFF))*factor
                     );

      subcolor = subcolor>0xFF?0xFF:(
                    subcolor<0x00?0x00:
                    subcolor);

      result |= subcolor<<(8*i);
   }
   return (color)result;
}

现在,OnInit 函数的模样如此:

CIndicatorPlot* indicators[];
CIndicatorPlot average;
int handles[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
   ArrayResize(indicators, 10);
   ArrayResize(handles, 10);


   int index=0, plot=0;

   for (int i=0; i<10; i++)
   {
        indicators[i] = new CIndicatorPlot();
   
      indicators[i].SetBuffer(index, plot);
      indicators[i].SetLineColor(InterpolateColors(clrYellow, clrRed, i/9.0));
      indicators[i].SetLabel("RSI ("+IntegerToString(firstPeriod+i*increment)+")");

      handles[i] = iRSI(NULL, PERIOD_CURRENT, firstPeriod+i*increment, PRICE_CLOSE);
   }

   average.SetBuffer(index, plot);
   average.SetLineColor(clrBlue);
   average.SetLineStyle(STYLE_DASH);
   average.SetLineWidth(2);
   average.SetLabel("Average");

//---
   return(INIT_SUCCEEDED);
}

请注意,我们未在任何地方通过编号来引用任何缓冲区或作图板:由类来处理该问题。 现在,很容易就能正确设置任何作图板属性,或更改缓冲区的顺序,因为我们可以按对象来引用它们,替代索引。 我们还借机为作图板添加了一些颜色和标签。

在这个例子中,我们还用 RSI 的指针数组修改指标结构(亦证明也可用于动态创建的对象),并从数组中将平均值分离。 有因于此,我们需要修改 OnCalculate 中的平均值引用,并在 OnDeInit 中删除指针数组中的指标。

void OnDeinit(const int reason)
{
   for (int i=0; i<10; i++)
      IndicatorRelease(handles[i]);
   for (int i=0; i<10; i++)
        delete indicators[i];
}

现在它看起来像这样:

唯一的视觉变化是颜色(以及数据窗口中的标签)。 在内里,我们通过简化处置作图板和缓冲区来改进工作流程,但其组织内部依旧还有很大的改进空间。

如果您仔细观察,就会发现每个句柄只由其中一个缓冲区使用:每个 RSI 缓冲区都可以独立计算,因此我们可以让类在内部执行此操作(替代直接在 OnCalculate 中进行计算)。 均化需要访问其余缓冲区,但这些计算也可以委托给类。 我们可以使用继承来添加特定功能,而无需更改函数,或在基类中添加条件。

首先,我们将虚拟的空白事件处理程序添加到基类之中:

class CIndicatorPlot
{
   //...

public:
   
   //...

   virtual void      Init() { }
   virtual void      DeInit() { }
   virtual void      Update(const int start, const int rates_total) { }
};

正如此例之前所示,Update 只需要 start rates_total 即可执行计算,因此其余值可被省略。

现在我们制作单独的 RSI 类。 它可以创建和删除所需的句柄。 此外,我们包含了一个函数来设置该句柄的周期,但它也可以将该参数包含在 Init() 当中。

class CRSIIndividual : public CIndicatorPlot
{
private:
   int               handle;
   int               rsi_period;

public:

   void              SetPeriodRSI(int period);

   virtual void      Init();
   virtual void      DeInit();
   virtual void      Update(const int start, const int rates_total);
};

//+------------------------------------------------------------------+
void CRSIIndividual::SetPeriodRSI(int period)
{
   rsi_period = period;
}

//+------------------------------------------------------------------+
void CRSIIndividual::Init(void)
{
   handle = iRSI(NULL, PERIOD_CURRENT, rsi_period, PRICE_CLOSE);
}

//+------------------------------------------------------------------+
void CRSIIndividual::Update(const int start,const int rates_total)
{
   if (start==0)
      CopyBuffer(handle, 0, 0, rates_total-start, array);
   else
   {
      double newValues[];
      CopyBuffer(handle, 0, 0, rates_total-start, newValues);

      for (int k=0; k<rates_total-start; k++)
      {
         array[start+k] = newValues[k];
      }
   }
}

//+------------------------------------------------------------------+
void CRSIIndividual::DeInit(void)
{
   IndicatorRelease(handle);
}

对于 Average 类,我们需要存储指针,以便访问指标作图板对象的其余部分(单独的 RSI)。 在此情况下,Init() 和 DeInit() 就不需要了。

class CRSIAverage : public CIndicatorPlot
{
private:
   CRSIIndividual*   rsi_indicators[];

public:
   void              SetRSIPointers(const CRSIIndividual &rsi_objects[]);

   virtual void      Update(const int start, const int rates_total);
};

//+------------------------------------------------------------------+
void CRSIAverage::SetRSIPointers(const CRSIIndividual &rsi_objects[])
{
   int total = ArraySize(rsi_objects);
   ArrayResize(rsi_indicators, total);

   for (int i=0; i<total; i++)
      rsi_indicators[i] = (CRSIIndividual*)GetPointer(rsi_objects[i]);
}

//+------------------------------------------------------------------+
void CRSIAverage::Update(const int start,const int rates_total)
{
   for (int i=start; i<rates_total; i++)
   {
      array[i] = 0.0;
      for (int j=0; j<10; j++)
         array[i] +=rsi_indicators[j].array[i];

      array[i]/=10.0;
   }
}

若您可以直接从全局范围访问对象时,创建指针数组可能会令问题过于复杂,但如此这般其它指标重用该类就变得更加容易,且无需进行更多修改。 另请注意,在此示例中,我们将再次使用对象数组,替代 RSI 指标指针,因此我们需要获取它们的指针。

最后,OnInit 函数(以及上面的对象声明)将如下所示...

CRSIIndividual indicators[];
CRSIAverage average;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
   ArrayResize(indicators, 10);


   int index=0, plot=0;

   for (int i=0; i<10; i++)
   {
      indicators[i].SetBuffer(index, plot);
      indicators[i].SetLineColor(InterpolateColors(clrYellow, clrRed, i/9.0));
      indicators[i].SetLabel("RSI ("+IntegerToString(firstPeriod+i*increment)+")");

      indicators[i].SetPeriodRSI(firstPeriod+i*increment);
      indicators[i].Init();                               
   }

   average.SetBuffer(index, plot);
   average.SetLineColor(clrBlue);
   average.SetLineStyle(STYLE_DASH);
   average.SetLineWidth(2);
   average.SetLabel("Average");

   average.SetRSIPointers(indicators);                    

//---
   return(INIT_SUCCEEDED);
}

...我们将能够令其它事件处理函数更加干净:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
{
//---
   int limit = MathMax(0, prev_calculated-1);

   for (int i=0; i<10; i++)
      indicators[i].Update(limit, rates_total);

   average.Update(limit, rates_total);

//--- return value of prev_calculated for next call
   return(rates_total);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   for (int i=0; i<10; i++)
      indicators[i].DeInit();
}
//+------------------------------------------------------------------+

直观来看,该指标与第二个示例完全相同。



4. 扩展类

截至目前,所有这些类都能很好地完成它们的工作,但它们特别依赖于我们一直在处置的指标类型:我们仅用了少数的作图板属性,并且仅用了折线图,但如果我想用具有颜色缓冲区的作图板该怎么办? 或是直方图,或是之字形...? 为了能够重用我们已完成的工作,我们需要泛化该类。 为此,我们需要满足三个条件:

  • 您应该能够创建任何类型的作图板/缓冲区,或修改作图板属性,而无需离开类,或了解缓冲区/作图板索引的详细信息。
  • 您应该能够添加具有任何绘制样式(折线、直方图、蜡烛...)的作图板,而不必担心与之对应的缓冲区数量和类型(不过,您应始终负责把数据放入这些数组之中)。
  • 您应该能够通过继承(可选)轻松地向类添加特定功能。

考虑到这一点,我们将首先解释类是如何实现的,以及继承是如何构建的。

首先,类的结构如下:

  • CIndicatorBufferBase
    • CIndicatorCalculations
    • CIndicatorPlotBase
      • CIndicator_1Data
        • CIndicatorPlotLine
        • CIndicatorPlotHistogram
        • ...
        • CIndicator_1Data1Color
          • CIndicatorPlotColorLine
          • ...
      • CIndicator_2Data
        • CIndicatorPlotHistogram2
        • ...
        • CIndicator_2Data1Color
          • CIndicatorPlotColorHistogram2
          • ...
      • CIndicator_4Data
        • CIndicatorPlotCandles
        • ...
        • CIndicator_4Data1Color
          • CIndicatorPlotColorCandles
          • ...

几个关键点:

  • 这三个点表示有更多的类继承自与上面的类相同的类(它们仅在绘制样式上有所不同,这是每个类所隐含的)。
  • 红色的类是无法实例化的抽象类,但它们可以存储从它们派生的其它类的指针(多态性)。
  • 其余类继承自拥有相应数据/颜色缓冲区数量的基类。 这也允许多态性,因为您可能有一个指标需要访问拥有 1 个数据缓冲区的类,它与是否折线、直方图、等等无关。
  • 颜色类继承自数据缓冲区的原因与上点相同。
  • CIndicatorCalculations 是辅助计算缓冲区,它没有对应的作图板。

实现汇总如下所示:

//+------------------------------------------------------------------+
//| Base class for plots and calculation buffers                     |
//+------------------------------------------------------------------+
class CIndicatorBufferBase
{
public:
   virtual void      SetBuffer(int &buffer, int &plot)=NULL;
   virtual void      SetAsSeries(bool set)=NULL;

   virtual void      Init() { }
   virtual void      DeInit() { }
   virtual void      Update(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[]) { }
};

//+------------------------------------------------------------------+
//| Calculations Buffer (with no plot)                               |
//+------------------------------------------------------------------+
class CIndicatorCalculations : public CIndicatorBufferBase
{
public:
   double            array[];

   virtual void      SetBuffer(int &buffer, int &plot);
   virtual void      SetAsSeries(bool set);
};

//+------------------------------------------------------------------+
void CIndicatorCalculations::SetBuffer(int &index, int &plot)
{
   SetIndexBuffer(index, array, INDICATOR_CALCULATIONS);

   index++;
//No plot is used
}

//+------------------------------------------------------------------+
void CIndicatorCalculations::SetAsSeries(bool set)
{
   ArraySetAsSeries(array, set);
}

//+------------------------------------------------------------------+
//| Base indicator plot class                                        |
//+------------------------------------------------------------------+
class CIndicatorPlotBase : public CIndicatorBufferBase
{
protected:

   int               indicator_plot;

   virtual void      SetDrawType()=NULL; //Implicit in each class

public:

   void              SetArrow(uchar arrow);
   void              SetArrowShift(int shift);
   void              SetDrawBegin(int begin);
   void              SetShowData(bool show);
   void              SetShift(int shift);
   void              SetLineStyle(ENUM_LINE_STYLE style);
   void              SetLineWidth(int width);
   void              SetColorIndexes(color &color_array[]);
   void              SetLineColor(color line_color);
   void              SetLineColor(color line_color, int index);
   void              SetEmptyValue(double empty);
   void              SetLabel(string label);

   int               GetInteger(ENUM_PLOT_PROPERTY_INTEGER property_id, int property_modifier=0);
};

//...

//...

//+------------------------------------------------------------------+
//| Base for indicators with 1 Data Buffer                           |
//+------------------------------------------------------------------+
class CIndicator_1Data : public CIndicatorPlotBase
{
public:

   double            array[];

   virtual void      SetBuffer(int &buffer, int &plot);
   virtual void      SetAsSeries(bool set);
};

//+------------------------------------------------------------------+
void CIndicator_1Data::SetBuffer(int &buffer,int &plot)
{
   indicator_plot = plot;

   SetIndexBuffer(buffer, array, INDICATOR_DATA);
   SetDrawType();

   buffer++;
   plot++;
}

//...

//+------------------------------------------------------------------+
//| Plot Line (1 data buffer)                                        |
//+------------------------------------------------------------------+
class CIndicatorPlotLine : public CIndicator_1Data
{
protected:

   virtual void      SetDrawType() final;
};

//+------------------------------------------------------------------+
void CIndicatorPlotLine::SetDrawType(void)
{
   PlotIndexSetInteger(indicator_plot, PLOT_DRAW_TYPE, DRAW_LINE);
}

//...

//...

//+------------------------------------------------------------------+
//| Base for indicators with 2 Data Buffers                          |
//+------------------------------------------------------------------+
class CIndicator_2Data : public CIndicatorPlotBase
{
public:

   double            first_array[];
   double            second_array[];

   virtual void      SetBuffer(int &buffer, int &plot);
   virtual void      SetAsSeries(bool set);
};


//+------------------------------------------------------------------+
void CIndicator_2Data::SetBuffer(int &buffer, int &plot)
{
   indicator_plot = plot;

   SetIndexBuffer(buffer, first_array, INDICATOR_DATA);
   SetIndexBuffer(buffer+1, second_array, INDICATOR_DATA);
   SetDrawType();

   buffer+=2;
   plot++;
}

//...

//...

//+------------------------------------------------------------------+
//| Base for indicators with 1 Data Buffer & 1 Color Buffer          |
//+------------------------------------------------------------------+
class CIndicator_1Data1Color : public CIndicator_1Data
{
public:

   double            color_buffer[];

   virtual void      SetBuffer(int &buffer, int &plot);
   virtual void      SetAsSeries(bool set);
};

//+------------------------------------------------------------------+
void CIndicator_1Data1Color::SetBuffer(int &buffer, int &plot)
{
   CIndicator_1Data::SetBuffer(buffer, plot);

   SetIndexBuffer(buffer, color_buffer, INDICATOR_COLOR_INDEX);

   buffer++; //Add color buffer
}

//+------------------------------------------------------------------+
void CIndicator_1Data1Color::SetAsSeries(bool set)
{
   CIndicator_1Data::SetAsSeries(set);
   ArraySetAsSeries(color_buffer, set);
}

//...

每个类都包含(并设置)所需数量的缓冲区。 CIndicatorBufferBase 拥有的事件处理程序,可选择性地被任何类型的缓冲区类覆盖,CIndicatorPlotBase 包含所有作图板属性的设置器(和一个读值函数),每个基准数据类(可有或无颜色)包含缓冲区的数组声明和设置函数,且每个特定类都要重载函数 SetDrawType() ,并以 final 声明它,如此它就不能再次被重载(如果您需要未定义绘图类型的类,您应继承相应的基准数据类,并重载该函数)。

请注意,在此实现中,Update 拥有 OnCalculate 事件中所用的所有值,但如果不需要多态性,则可用较少的参数覆盖这些值。

ArraySetAsSeries 也包括在内,因为它是一个十分常见的函数,所有缓冲区几乎总是需要以相同的方式调用它进行设置。


现在我们已有了类,这样我们就可以创建一个指标了。 我们把这些琐碎之事汇总到一起作为示例:

  • 首先,我们将基于 ATR 指标创建波带,并将其显示为填充图
  • 然后,我们将创建 10 条具有不同周期的移动平均线,并在图表中将它们显示为折线图
  • 最后,我们将采用彩色蜡烛图,根据高于/低于波带的均线数量来改变蜡烛的颜色。

首先,我们将声明输入,并包含指标类的文件,以及我们在第 3 节中制作的颜色插值:

#property indicator_buffers 19
#property indicator_plots 13

#include <OOPIndicators/IndicatorClass.mqh>
#include <OOPIndicators/ColorLerp.mqh>

input int atr_period = 10; //ATR Period
input double atr_band_multiplier = 0.8; //ATR Multiplier for bands
input bool show_bands = true; //Show Bands
input bool show_data = false; //Show Extra Data

input int ma_faster_period = 14; //MA Faster Period
input int ma_step = 2; //MA Step
input ENUM_MA_METHOD ma_method = MODE_SMA; //MA Method

我们已经定义了所需数量的缓冲区和作图板。 您不必从一开始就要知道每个需要多少,但是正如您以后将在下面看到的那样,很容易就能找出该值(在 OnInit() 中)。

然后,我们将为指标的每个部分创建类。

从 ATR 波带开始:

//+------------------------------------------------------------------+
//| ATR Bands class (inherit from Filling Plot)                      |
//+------------------------------------------------------------------+
class CATRBand : public CIndicatorPlotFilling
{
private:

   int               handle;

public:

   virtual void      Init();
   virtual void      DeInit();
   virtual void      Update(const int limit, const int rates_total, const double &close[]);
};

//+------------------------------------------------------------------+
void CATRBand::Init(void)
{
   handle = iATR(NULL, PERIOD_CURRENT, atr_period);
}

//+------------------------------------------------------------------+
void CATRBand::Update(const int limit,const int rates_total,const double &close[])
{
   double atr[];
   CopyBuffer(handle, 0, 0, rates_total-limit, atr);

   for (int i=limit; i<rates_total; i++)
   {
      first_array[i] = close[i]+atr[i-limit]*atr_band_multiplier;
      second_array[i] = close[i]-atr[i-limit]*atr_band_multiplier;
   }
}

//+------------------------------------------------------------------+
void CATRBand::DeInit(void)
{
   IndicatorRelease(handle);
}

MA 类,它在 Init() 中拥有周期和方法的参数:

//+------------------------------------------------------------------+
//| Moving Averages class (inherit from Line Plot)                   |
//+------------------------------------------------------------------+
class CMA : public CIndicatorPlotLine
{
private:

   int               handle;

public:
   virtual void      Init(int period, ENUM_MA_METHOD mode);
   virtual void      DeInit();
   virtual void      Update(const int limit, const int rates_total);
};

//+------------------------------------------------------------------+
void CMA::Init(int period, ENUM_MA_METHOD mode)
{
   handle = iMA(NULL, PERIOD_CURRENT, period, 0, mode, PRICE_CLOSE);
}

//+------------------------------------------------------------------+
void CMA::Update(const int limit,const int rates_total)
{
   if (limit==0) CopyBuffer(handle, 0, 0, rates_total, array);
   else
   {
      double newVals[];
      CopyBuffer(handle, 0, 0, rates_total-limit, newVals);

      for (int i=limit; i<rates_total; i++)
         array[i] = newVals[i-limit];
   }
}

//+------------------------------------------------------------------+
void CMA::DeInit(void)
{
   IndicatorRelease(handle);
}

还有蜡烛类。 在这种情况下,为了避免示例极端复杂,我们将从全局范围访问对象。 若您计划重用任何类,则不建议这样做。

它还包含我们将在下面声明的宏替换。注意:在代码里,函数位于宏替换的下方,但在此处显示的函数已重新排序。

//+------------------------------------------------------------------+
//| Color Candles class (inherit from Color Candles Plot)            |
//+------------------------------------------------------------------+
class CColorCandles : public CIndicatorPlotColorCandles
{
public:
   virtual void      Update(const int limit,
                            const int rates_total,
                            const double &open[],
                            const double &high[],
                            const double &low[],
                            const double &close[]);
};

//+------------------------------------------------------------------+
void CColorCandles::Update(const int limit,
                           const int rates_total,
                           const double &open[],
                           const double &high[],
                           const double &low[],
                           const double &close[])
{
   for (int i=limit; i<rates_total; i++)
   {
      open_array[i] = open[i];
      high_array[i] = high[i];
      low_array[i] = low[i];
      close_array[i] = close[i];

      int count_ma = TOTAL_MA;

      for (int m=0; m<TOTAL_MA; m++)
      {
         if (maIndicators[m].array[i] > bands.first_array[i]) count_ma++;
         if (maIndicators[m].array[i] < bands.second_array[i]) count_ma--;
      }

      color_buffer[i] = count_ma;

      //Update inside of this other object (to avoid making an extra inheritance, or an external loop)
      showIndex.array[i] = TOTAL_MA - count_ma;
   }
}

现在我们必须声明对象,并配置缓冲区和作图板的视觉效果:

#define TOTAL_MA 10

CMA maIndicators[TOTAL_MA];
CATRBand bands;
CColorCandles candles;
CIndicatorPlotNone showIndex; //To show MAs above/below

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
   int buffer=0, plot=0;

   bands.SetBuffer(buffer, plot);
   candles.SetBuffer(buffer, plot);
   for (int i=0; i<TOTAL_MA; i++)
      maIndicators[i].SetBuffer(buffer, plot);
   showIndex.SetBuffer(buffer, plot);

//Print("Buffers: ", buffer, "  Plots: ", plot);

//--- plot settings
   if (show_bands) bands.SetLineColor(clrDarkSlateGray);
   else bands.SetLineColor(clrNONE);
   bands.SetShowData(show_data);
   if (show_data)
      bands.SetLabel("Close + ATR;Close - ATR");


   for (int i=0; i<TOTAL_MA; i++)
   {
      maIndicators[i].SetLineColor(InterpolateColors(clrAqua, clrRoyalBlue, i/(TOTAL_MA-1.0)));
      maIndicators[i].SetLabel("MA("+IntegerToString(ma_faster_period+i*ma_step)+")");
      maIndicators[i].SetShowData(show_data);
      if (i>0 && i <TOTAL_MA-1) maIndicators[i].SetLineStyle(STYLE_DOT);
      else maIndicators[i].SetLineWidth(2);
   }

   color arrow_colors[TOTAL_MA*2+1];

   for (int i=0; i<TOTAL_MA; i++)
      arrow_colors[i] = InterpolateColors(clrGreenYellow, clrGray, i/double(TOTAL_MA));
   arrow_colors[TOTAL_MA] = clrGray;
   for (int i=TOTAL_MA+1; i<TOTAL_MA*2+1; i++)
      arrow_colors[i] = InterpolateColors(clrGray, clrOrange, (i-TOTAL_MA)/double(TOTAL_MA));

   candles.SetColorIndexes(arrow_colors);
   candles.SetLabel("Open;High;Low;Close");
   candles.SetShowData(false);

   showIndex.SetLabel("MAs above/below");
   showIndex.SetShowData(true);

//--- initialize classes
   bands.Init();
   for (int i=0; i<TOTAL_MA; i++)
      maIndicators[i].Init(ma_faster_period+i*ma_step, ma_method);

   return(INIT_SUCCEEDED);
}

按顺序,首先配置缓冲区,然后初始化作图板属性,然后初始化子指标(在其类中已指定)。

如前所说,您可以打印 bufferplot 的变量值,来轻松知道所需的缓冲区和作图板数量。 然后,您就可以正确设置属性(在开始时,您还可以为它们设置比所需数量更高的数值来避免出错)。

另请注意,我们包含了 Plot None 类的一个实例。 该对象由蜡烛对象更新,因此它不需要特别的事件处理程序。 它显示数据窗口中高于或低于波带的均线数量。

最后,在其它的事件处理程序中没有太多功能,因为所有东西都在对象内部,只需要按照正确的顺序从对象调用这些函数:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
{
//---
   int limit = MathMax(0, prev_calculated-1);

   bands.Update(limit, rates_total, close);

   for (int i=0; i<TOTAL_MA; i++)
      maIndicators[i].Update(limit, rates_total);

   candles.Update(limit, rates_total, open, high, low, close);

//--- return value of prev_calculated for next call
   return(rates_total);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   bands.DeInit();
   for (int i=0; i<TOTAL_MA; i++)
      maIndicators[i].DeInit();
}

指标的最终结果如下所示:


5. 该方法的限制

尽管它更便利,但用函数替代属性也有一些缺点:最明显的就是更改任何作图板的颜色/样式时受到的干扰,有时它们会有残留,有时它们会被初始化重写。

该问题可以用输入颜色(替代在“颜色”选项卡中更改它们)来避免,或检查是否存与默认的黑色 (0x000000) 不同。 这可适用于除黑色以外的所有颜色。

if (obj.GetInteger(PLOT_LINE_COLOR)==clrBlack)
   obj.SetLineColor(clrYellow);

另外,在本文中,我们尚未分析运用这些类对性能的影响。 理论上,直接用属性和少量函数应该更快速,但在大多数情况下意义不大。

最后,您可能已经注意到,这些类不包含图表事件,或 OnTimer。 原因在于图表事件最好直接在 OnChartEvent 中处理,完成后调用特定函数(而不是每次有事件时调用每个指标的处理程序,去多次处理每个事件)。 对于计时器,如果您的指标是多时间帧或多货币,则可依照不同的方式调用 Update 处理程序(您将无法直接访问 OnCalculate 的数组)。 有些人可能不认同这样的设计决策,即把用作缓冲区的数组声明为公开可见的:可以把数组设置为受保护可见,如此指标仍然可以工作,但您可能需要添加读值函数才能从外部访问数据。


6. 结束语

在本文中,我们思考并开发了一种方法,能够更轻松地创建复杂指标,并且代码行更少。 我们从针对特定案例的小规模技巧开始,然后实现一个允许重用和自定义功能的类结构,最后我们将所有内容组合到一个指标示例之中,该指标用到了本文所述的大多数功能。


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

附加的文件 |
MQL5.zip (10.17 KB)
学习如何基于强力指数(Force Index)设计交易系统 学习如何基于强力指数(Force Index)设计交易系统
欢迎阅读我们系列中的新篇章,有关如何基于最流行的技术指标设计交易系统。 在本文中,我们将学习一个新的技术指标,以及如何运用强力指数(Force Index)指标创建交易系统。
DoEasy. 控件 (第 13 部分): 优化 WinForms 对象与鼠标的交互,启动开发 TabControl WinForms 对象 DoEasy. 控件 (第 13 部分): 优化 WinForms 对象与鼠标的交互,启动开发 TabControl WinForms 对象
在本文中,我将修复和优化当鼠标光标移离 WinForms 对象后 WinForms 对象的外观处理,并启动开发 TabControl WinForms 对象。
从头开始开发智能交易系统(第 26 部分):面向未来(I) 从头开始开发智能交易系统(第 26 部分):面向未来(I)
今天,我们将把我们的订单系统提升到一个新的层次。 但在此之前,我们需要解决少量问题。 我们现有的一些问题,是与在交易日里我们想要如何工作,以及我们做什么事情相关。
CCI 指标。 三个变换步骤 CCI 指标。 三个变换步骤
在本文中,我将针对 CCI 进行额外的修改,从而影响该指标的逻辑。 进而,我们就能够在主图表窗口中看到它。