English Русский Español Deutsch 日本語 Português
ZigZag(之字折线)的力量(第一部分)。 开发指标基类

ZigZag(之字折线)的力量(第一部分)。 开发指标基类

MetaTrader 5示例 | 18 三月 2019, 07:36
6 194 0
Anatoli Kazharski
Anatoli Kazharski

内容


概述

在之前的一篇文章中,我展示了如何呈现 相对强弱指数(RSI)这样的指标。 在其中一个版本中,所获结果可同时用于接收趋势和横盘条件的信号。 该指标可能只缺少一件事 — 定义价格行为的能力,这对于决定何时开始和停止交易也非常重要。 

许多研究人员对于判定价格行为轻率地忽略或者没有给予足够重视。 与此同时,还使用各种复杂方法,而这些方法通常只是“黑盒子”,例如机器学习或神经网络。 在这种情况下显现出的最严重问题就是提交何种数据来训练特定模型。 在本文中,我们将扩展此类研究的工具。 在搜索最佳参数之前,您将了解如何选择更合适的交易品种。 为实现这一目标,我们将使用 ZigZag 指标的改编版本和代码类,这些代码类可以显著简化获取和处理属于此类型指标的数据。

在本系列文章中,我们将实现:

  • ZigZag 指标的改编版本
  • 获取 ZigZaga 数据的类
  • 用于获取数据的过程的测试 EA
  • 定义价格行为的指标
  • 带有图形界面的 EA,用于收集价格行为统计数据
  • 跟随 ZigZag 信号的 EA


扩展的 ZigZag(之字折线)指标版本

一般来说,ZigZag 类型的指标是基于柱线的最高点和最低点建立的,没有考虑点差因素。 本文介绍了一个修订版本,其中针对更低的 ZigZag 极值点构造线段时考虑了点差。 假设成交将在交易系统的价格通道内执行。 这很重要,因为经常会发生买入价格(ask)明显高于卖出价格(bid)。 例如,这可能发生在夜间。 因此,仅基于卖出价格构建指标是错误的。 毕竟,如果不可能以这些价格买入,那么基于柱线低点构建指标的更低极值点是没有意义的。 当然,在交易条件中可以考虑点差,但所有内容在图表上一目了然时,当然更好。 这可简化交易策略的开发,因为最初的一切似乎都很合理。

此外,您可能还希望查看更新后的所有 ZigZag 极值点。 在这种情况下,全貌变得更加完整。 现在我们来研究指标代码。 我们只讨论基本功能和函数。

我们需要两个指标缓冲区来构建线段。 一个用于高点(最大值),另一个用于低点(最小值)。 它们将在图表上显示为单条线。 因此,我们需要 六个指标缓冲区显示其中五个

我们列出所有指标缓冲区:

  • 最低要价。 ZigZag 的低点值将基于它们
  • 最高出价。 ZigZag 的高点值将基于它们
  • 高点
  • 低点
  • 所有检测到的上行线段的高点
  • 所有检测到的下行线段的低点

#property indicator_chart_window
#property indicator_buffers 6
#property indicator_plots   5
//---
#property indicator_color1  clrRed
#property indicator_color2  clrCornflowerBlue
#property indicator_color3  clrGold
#property indicator_color4  clrOrangeRed
#property indicator_color5  clrSkyBlue

//--- Indicator buffers:
double low_ask_buffer[];    // 最低要价
double high_bid_buffer[];   // 最高出价
double zz_H_buffer[];       // 高点
double zz_L_buffer[];       // 低点
double total_zz_h_buffer[]; // 所有高点
double total_zz_l_buffer[]; // 所有低点

我们在外部参数中添加可设定柱线数量( NumberOfBars ),以便构建指标线。 零意味着使用图表上所有呈现的数据。 MinImpulseSize 参数设置点数,价格应自最后一个极值按该点数偏离,以便开始构建相反方向的线段。 此外,我们添加附加参数来定义要在图表上显示的指标缓冲区,以及 ZigZag 线段的颜色。

//--- 外部参数
input int   NumberOfBars   =0;       // 柱线数量
input int   MinImpulseSize =100;     // 线段中的最小点数
input bool  ShowAskBid     =false;   // 显示要加/出价
input bool  ShowAllPoints  =false;   // 显示所有点
input color RayColor       =clrGold; // 线段颜色

在全局层面,声明计算极值所需的辅助变量。 我们需要保存先前计算出的极值点的索引,跟踪当前的线段方向,并保存最小要价和最高出价。

//--- ZZ 变量
int    last_zz_max  =0;
int    last_zz_min  =0;
int    direction_zz =0;
double min_low_ask  =0;
double max_high_bid =0;

FillAskBidBuffers() 函数用来填充最低要价和最高出价的指标缓冲区。 对于出价缓冲区,保存来自 最高价 数组中的值,而对于要价缓冲区,保存来自 最低价 数组中的值并考虑点差

//+------------------------------------------------------------------+
//| 填充出价高点和要价低点指标缓冲区                                       |
//+------------------------------------------------------------------+
void FillAskBidBuffers(const int i,const datetime &time[],const double &high[],const double &low[],const int &spread[])
  {
//--- 如果未达到初始日期,则退出
   if(time[i]<first_date)
      return;
//---
   high_bid_buffer[i] =high[i];
   low_ask_buffer[i]  =low[i]+(spread[i]*_Point);
  }

FillIndicatorBuffers() 函数用于定义 ZigZag 极值点。 计算仅从指定日期 执行,具体取决于 MinImpulseSize 外部参数中设置的柱线数。 根据前一次函数调用期间定义的线段方向,程序进入相应的代码块。

检查以下条件以便定义方向:

  • 当前上行线段的方向
    • 当前最大出价超过最后的最大值:
      • 如果满足该条件,则(1)重置先前的最大值,(2)记住当前数据的数组索引,以及(3)将当前的最大出价分配给指标缓冲区的当前元素。
      • 如果不满足此条件,则线段方向已改改,到检查更低极值形成条件的时刻:
        • 目前最小要价低于最后一个高点
        • 当前最小要价和最后的 ZigZag 最大值之间的距离超过指定的阈值(MinImpulseSize)。
          • 如果满足这些条件,(1)记住当前数据的数组索引,(2)在变量中保存新的(下行)线段方向,以及(3)将当前最小要价值分配给指标缓冲区的当前元素。
  • 当前线段方向为下行
    • 目前的最小要价低于最后的最小值:
      • 如果满足此条件,则(1)重置先前的最小值,(2)记住当前数据的数组索引,以及(3)将当前最小值分配给指标缓冲区的当前元素。
      • 如果不满足此条件,则线段方向已改改,到了检查顶端极值形成条件的时刻:
        • 当前最大出价超过最后的最小值
        • 当前最大出价和最后的 ZigZag 最小值之间的距离超过指定的阈值(MinImpulseSize)。
          • 如果满足这些条件,则(1)记住当前数据的数组索引,(2)在变量中保存新的(上行)线段方向,以及(3)将当前最大出价分配给指标缓冲区的当前元素。

FillIndicatorBuffers() 函数的详尽代码如下所见:

//+------------------------------------------------------------------+
//| 填充 ZZ 指标缓冲区                                                  |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   if(time[i]<first_date)
      return;
//--- 如果 ZZ 上行
   if(direction_zz>0)
     {
      //--- 如果出现新高
      if(high_bid_buffer[i]>=max_high_bid)
        {
         zz_H_buffer[last_zz_max] =0;
         last_zz_max              =i;
         max_high_bid             =high_bid_buffer[i];
         zz_H_buffer[i]           =high_bid_buffer[i];
         total_zz_h_buffer[i]     =high_bid_buffer[i];
        }
      //--- 如果方向发生变化(下行)
      else
        {
         if(low_ask_buffer[i]<max_high_bid && 
            fabs(low_ask_buffer[i]-zz_H_buffer[last_zz_max])>MinImpulseSize*_Point)
           {
            last_zz_min          =i;
            direction_zz         =-1;
            min_low_ask          =low_ask_buffer[i];
            zz_L_buffer[i]       =low_ask_buffer[i];
            total_zz_l_buffer[i] =low_ask_buffer[i];
           }
        }
     }
//--- 如果 ZZ 下行
   else
     {
      //--- 如果出现新低
      if(low_ask_buffer[i]<=min_low_ask)
        {
         zz_L_buffer[last_zz_min] =0;
         last_zz_min              =i;
         min_low_ask              =low_ask_buffer[i];
         zz_L_buffer[i]           =low_ask_buffer[i];
         total_zz_l_buffer[i]     =low_ask_buffer[i];
        }
      //--- 如果方向发生变化(上行)
      else
        {
         if(high_bid_buffer[i]>min_low_ask && 
            fabs(high_bid_buffer[i]-zz_L_buffer[last_zz_min])>MinImpulseSize*_Point)
           {
            last_zz_max          =i;
            direction_zz         =1;
            max_high_bid         =high_bid_buffer[i];
            zz_H_buffer[i]       =high_bid_buffer[i];
            total_zz_h_buffer[i] =high_bid_buffer[i];
           }
        }
     }
  }

指标主要函数的代码显示在下面的清单中。 指标仅计算已成形柱线。 之后,(1)将数组和变量设置为零,(2)定义计算柱线数量和初始索引。 最初,计算指标缓冲区所有元素的数据,随后每次仅计算 最后一根柱线上的数据。 在执行初步计算和检查后,计算并填充指标缓冲区。

//+------------------------------------------------------------------+
//| 自定义指标迭代函数                                                  |
//+------------------------------------------------------------------+
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[])
  {
//--- 避免每次即时报价时计算
   if(prev_calculated==rates_total)
      return(rates_total);
//--- 如果这是首次计算
   if(prev_calculated==0)
     {
      //--- 将指标缓冲区设置为零
      ZeroIndicatorBuffers();
      //--- 将变量设置为零
      ZeroIndicatorData();
      //--- 检查可用数据量
      if(!CheckDataAvailable())
         return(0);
      //--- 如果已指定了更多复制数据,则使用当前数量
      DetermineNumberData();
      //--- 定义每个品种起始绘制柱线 
      DetermineBeginForCalculate(rates_total);
     }
   else
     {
      //--- 仅计算最后一个值
      start=prev_calculated-1;
     }
//--- 填写最高出价和最低要价指标缓冲区
   for(int i=start; i<rates_total; i++)
      FillAskBidBuffers(i,time,high,low,spread);
//--- 用数据填充指标缓冲区
   for(int i=start; i<rates_total-1; i++)
      FillIndicatorBuffers(i,time);
//--- 返回数据数组大小
   return(rates_total);
  }

EURUSD D1 上的指标显示如下:

 图例 1. EURUSD D1 上的改编版 ZigZag 指标

图例 1. EURUSD D1 上的改编版 ZigZag 指标

下一个屏幕截图显示了 EURMXN M5 上的指标。 在此,我们可以看到夜间的点差显著扩大。 无论如何,该指标的计算已考虑到点差。

 图例 2. EURMXN M5 上的改编版 ZigZag 指标

图例 2. EURMXN M5 上的改编版 ZigZag 指标

在下一节中,我们将研究一个代码类,其中包含的方法可帮助我们获取定义当前价格行为的所有必要数据。


澄清 ZigZag 指标数据的类别

价格趋向混乱和不可预测。 当价格经常改变方向时,横盘走势可能会突然被长期的单边趋势所取代,且没有回调。 有必要始终监控当前状态,但拥有正确解析价格行为的工具也很重要。 这可以通过 CZigZagModule 代码类来实现,该代码类具有处理 ZigZag 数据的所有必要方法。 我们来看它是如何工作的。

由于我们能够同时处理若干个类实例,例如,使用来自不同时间帧的 ZigZag 数据,我们可能需要使用不同颜色的趋势线将所获得的线段可视化。 所以,将标准库中的 ChartObjectsLines.mqh 文件与 CZigZagModule 类文件联系起来。 从该文件中,我们需要 CChartObjectTrend 类来处理趋势线。 趋势线的颜色可以通过 CZigZagModule::LinesColor() 公开方法指定。 默认设置为灰色(clrGray)。

//+------------------------------------------------------------------+
//|                                                 ZigZagModule.mqh |
//|                                版权所有 2018, MetaQuotes 软件公司 |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ChartObjects\ChartObjectsLines.mqh>
//+------------------------------------------------------------------+
//| 获取 ZigZag 指标数据的类                                            |
//+------------------------------------------------------------------+
class CZigZagModule
  {
protected:
   //--- 线段
   CChartObjectTrend m_trend_lines[];

   //--- 线段颜色
   color             m_lines_color;
   //---
public:
                     CZigZagModule(void);
                    ~CZigZagModule(void);
   //---
public:
   //--- 线颜色
   void              LinesColor(const color clr) { m_lines_color=clr; }
  };
//+------------------------------------------------------------------+
//| 构造函数                                                           |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_lines_color(clrGray)
  {
// ...
  }
//+------------------------------------------------------------------+
//| 析构函数                                                           |
//+------------------------------------------------------------------+
CZigZagModule::~CZigZagModule(void)
  {
  }

在获取 ZigZag 指标数据之前,我们需要设置工作所需的极值数量。 为此,我们应该调用 CZigZagModule::CopyExtremums() 方法。 已声明的单独动态数组存储(1)极值价格,(2)极值柱线索引,(3)它们的柱线时间和(4)用于在图表上构建趋势线的线段数量。 数组的大小以相同的方法设置。

线段数量由指定的极值数量自动计算。 例如,如果我们将 1 传递给 CZigZagModule::CopyExtremums() 方法,我们会收到一个高点和一个低点的数据。 在此情况下,它只是 ZigZag 指标的一条线段。 如果传递的数值大于 1,则 线段数量始终等于复制的极值数量乘以 2 再减 1。 换言之,线段的数量总是奇数:

  • 一个极点 – 1 条线段
  • 两个极点 – 3 条线段
  • 三个极点 – 5 条线段,等等。
class CZigZagModule
  {
protected:
   int               m_copy_extremums;    // 保存的高点/低点数量
   int               m_segments_total;    // 线段数量
   //--- 极点价格
   double            m_zz_low[];
   double            m_zz_high[];
   //--- 极点柱线的索引
   int               m_zz_low_bar[];
   int               m_zz_high_bar[];
   //--- 极点柱线的时间
   datetime          m_zz_low_time[];
   datetime          m_zz_high_time[];
   //---
  };
//+------------------------------------------------------------------+
//| 构造函数                                                           |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_copy_extremums(1),
                                     m_segments_total(1)
  {
   CopyExtremums(m_copy_extremums);
  }

//+------------------------------------------------------------------+
//| 处理的极值数量                                                      |
//+------------------------------------------------------------------+
void CZigZagModule::CopyExtremums(const int total)
  {
   if(total<1)
      return;
//---
   m_copy_extremums =total;
   m_segments_total =total*2-1;
//---
   ::ArrayResize(m_zz_low,total);
   ::ArrayResize(m_zz_high,total);
   ::ArrayResize(m_zz_low_bar,total);
   ::ArrayResize(m_zz_high_bar,total);
   ::ArrayResize(m_zz_low_time,total);
   ::ArrayResize(m_zz_high_time,total);
   ::ArrayResize(m_trend_lines,m_segments_total);
  }

在我们开始处理 ZigZag 指标数据之前,为便于使用,先将它们放入上述类数组当中。 我们需要辅助字段作为极值计数器。 

若要获取数据,我们需要 CZigZagModule::GetZigZagData() 方法。 应将初始 ZigZag 指标数组与时间数组一同传递给它。 可以使用 CopyBuffer()CopyTime() 函数获取这些源数据。 在从源数据中获取必要数值之前,应重置所有字段和数组。 然后,获得指定数量的(1)极值,(2)极值柱线索引,和(3)极值时间。 

当前线段的方向在方法结束时定义。 在此,如果当前线段高点时间超过低点,则方向上行。 否则,则为下行。

class CZigZagModule
  {
protected:
   int               m_direction;         // 方向
   int               m_counter_lows;      // 低点计数器
   int               m_counter_highs;     // 高点计数器
   //---
public:
   //--- 取数据
   void              GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[]);
   //--- 重置结构
   void              ZeroZigZagData(void);
  };
//+------------------------------------------------------------------+
//| 取 ZigZag 数据                                                    |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[])
  {
   int h_total =::ArraySize(zz_h);
   int l_total =::ArraySize(zz_l);
   int total   =h_total+l_total;
//--- 重置 ZZ 变量
   ZeroZigZagData();
//--- 在循环中按顺序移动所复制的 ZZ 数值
   for(int i=0; i<total; i++)
     {
      //--- 如果已收到必要数量的 ZZ 高点和低点,则退出循环
      if(m_counter_highs==m_copy_extremums && m_counter_lows==m_copy_extremums)
         break;
      //--- 数组超界管控
      if(i>=h_total || i>=l_total)
         break;
      //--- 填充高点值数组,直到复制必要的数量
      if(zz_h[i]>0 && m_counter_highs<m_copy_extremums)
        {
         m_zz_high[m_counter_highs]      =zz_h[i];
         m_zz_high_bar[m_counter_highs]  =i;
         m_zz_high_time[m_counter_highs] =time[i];
         //---
         m_counter_highs++;
        }
      //--- 填充低点值数组,直到复制必要的数量
      if(zz_l[i]>0 && m_counter_lows<m_copy_extremums)
        {
         m_zz_low[m_counter_lows]      =zz_l[i];
         m_zz_low_bar[m_counter_lows]  =i;
         m_zz_low_time[m_counter_lows] =time[i];
         //---
         m_counter_lows++;
        }
     }
//--- 定义价格走势方向
   m_direction=(m_zz_high_time[0]>m_zz_low_time[0])? 1 : -1;
  }

现在已收到数据,我们可以研究这个类的其他方法。 为了获得这些已形成的极值所在处的价格、柱线索引和柱线时间,只需调用相应的方法并指定极值索引(参见下面的代码清单)。 在此仅提供了 CZigZagModule::LowPrice() 方法代码作为示例,因为它们几乎完全相同。

class CZigZagModule
  {
public:
   //--- 由指定索引得到的极值价格
   double            LowPrice(const int index);
   double            HighPrice(const int index);
   //--- 由指定索引得到的极值柱线索引
   int               LowBar(const int index);
   int               HighBar(const int index);
   //--- 由指定索引得到的极值柱线时间
   datetime          LowTime(const int index);
   datetime          HighTime(const int index);
  };
//+------------------------------------------------------------------+
//| 指定索引处的低点值                                                  |
//+------------------------------------------------------------------+
double CZigZagModule::LowPrice(const int index)
  {
   if(index>=::ArraySize(m_zz_low))
      return(0.0);
//---
   return(m_zz_low[index]);
  }

如果您需要获取线段大小,调用 CZigZagModule::SegmentSize() 方法,指定的线段索引是唯一参数。 根据指定索引是偶数值还是奇数值,可以相应地定义所计算线段大小的极值索引。 如果索引值是偶数,则极值索引彼此匹配,并且不需要根据线段方向计算。

class CZigZagModule
  {
public:
   //--- 指定索引处的线段大小
   double            SegmentSize(const int index);
  };
//+------------------------------------------------------------------+
//| 按索引返回线段大小                                                  |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentSize(const int index)
  {
   if(index>=m_segments_total)
      return(-1);
//---
   double size=0;
//--- 如果值是偶数
   if(index%2==0)
     {
      int i=index/2;
      size=::fabs(m_zz_high[i]-m_zz_low[i]);
     }
//--- 如果值是奇数
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
        }
      //---
      size=::fabs(m_zz_high[h]-m_zz_low[l]);
     }
//---
   return(size);
  }

CZigZagModule::SegmentsSum() 方法用于获取所有线段的总和。 这里一切都很简单,因为在循环中当沿着所有线段移动时,会调用上面描述的 CZigZagModule::SegmentSize() 方法。

class CZigZagModule
  {
public:
   //--- 所有线段的总和
   double            SegmentsSum(void);
  };
//+------------------------------------------------------------------+
//| 所有线段的总和                                                      |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentsSum(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_segments_total; i++)
      sum+=SegmentSize(i);
//---
   return(sum);
  }

此外,我们也许仅需要获得所有上行或下行线段的总和。 以下显示上行线段的代码作为示例。 这一切都取决于当前线段的方向。 如果它是上行,则在循环中以当前索引进行计算。 如果当前方向下行,计算则应从第一个索引开始,偏移量为高点向后退回一个元素。 如果您要获得下行的所有线段的总和,使用相同的方法,唯一的区别是如果当前方向上行,则偏移量基于低点。

class CZigZagModule
  {
public:
   //--- 线段的总和指向(1)上行,和(2)下行
   double            SumSegmentsUp(void);
   double            SumSegmentsDown(void);
  };
//+------------------------------------------------------------------+
//| 返回所有上行线段的大小                                               |
//+------------------------------------------------------------------+
double CZigZagModule::SumSegmentsUp(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_copy_extremums; i++)
     {
      if(Direction()>0)
         sum+=::fabs(m_zz_high[i]-m_zz_low[i]);
      else
        {
         if(i>0)
            sum+=::fabs(m_zz_high[i-1]-m_zz_low[i]);
        }
     }
//---
   return(sum);
  }

得到单向线段总和与集合中线段总数的百分比可能很有用。 要实现此目的,使用 CZigZagModule::PercentSumSegmentsUp() 和 CZigZagModule::PercentSumSegmentsDown() 方法。 它们能获得这些比率的百分比差值 — CZigZagModule::PercentSumSegmentsDifference() 方法,这反过来可以向我们指明当前价格(趋势)方向。 如果差值不显著,则价格在两个方向上均匀波动(横盘)。 

class CZigZagModule
  {
public:
   //--- 线段总和与集合中所有线段总数的的百分比
   double            PercentSumSegmentsUp(void);
   double            PercentSumSegmentsDown(void);
   //--- 线段总和之间的差异
   double            PercentSumSegmentsDifference(void);
  };
//+------------------------------------------------------------------+
//| 返回所有上行线段总和的百分比                                          |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsUp(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsDown()/sum*100);
  }
//+------------------------------------------------------------------+
//| 返回所有下行线段总和的百分比                                          |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDown(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsUp()/sum*100);
  }
//+------------------------------------------------------------------+
//| 返回所有线段总和的百分比差值                                          |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDifference(void)
  {
   return(::fabs(PercentSumSegmentsUp()-PercentSumSegmentsDown()));
  }

为了定义价格行为,我们需要获得单独线段的持续时间和整个结果集的方法。 CZigZagModule::SegmentBars() 方法用于获取指定线段中的柱线数量。 方法代码的逻辑与获取线段大小的 CZigZagModule::SegmentSize() 方法的逻辑相同。 所以,在此处提供其代码毫无意义。 

若要从所获取的数据集中得到柱线总数,使用 CZigZagModule::SegmentsTotalBars() 方法。 此处,集合中的初始和结束柱线索引已定义,并返回差值。 CZigZagModule::SegmentsTotalSeconds() 方法遵循相同的原则。 唯一的区别是它返回集合中的秒数。 

class CZigZagModule
  {
public:
   //--- 指定线段中的柱线数量
   int               SegmentBars(const int index);
   //--- (1) 柱线数量。和 (2) 线段秒数
   int               SegmentsTotalBars(void);
   long              SegmentsTotalSeconds(void);
  };
//+------------------------------------------------------------------+
//| 所有线段的柱线数量                                                  |
//+------------------------------------------------------------------+
int CZigZagModule::SegmentsTotalBars(void)
  {
   int begin =0;
   int end   =0;
   int l     =m_copy_extremums-1;
//---
   begin =(m_zz_high_bar[l]>m_zz_low_bar[l])? m_zz_high_bar[l] : m_zz_low_bar[l];
   end   =(m_zz_high_bar[0]>m_zz_low_bar[0])? m_zz_low_bar[0] : m_zz_high_bar[0];
//---
   return(begin-end);
  }
//+------------------------------------------------------------------+
//| 所有线段的秒数                                                      |
//+------------------------------------------------------------------+
long CZigZagModule::SegmentsTotalSeconds(void)
  {
   datetime begin =NULL;
   datetime end   =NULL;
   int l=m_copy_extremums-1;
//---
   begin =(m_zz_high_time[l]<m_zz_low_time[l])? m_zz_high_time[l] : m_zz_low_time[l];
   end   =(m_zz_high_time[0]<m_zz_low_time[0])? m_zz_low_time[0] : m_zz_high_time[0];
//---
   return(long(end-begin));
  }

通常可能需要找出所观察数据集中的价格范围。 出于这些目的,该类具有获取最小和最大极值,以及它们之间差值(价格范围)的方法。

class CZigZagModule
  {
public:
   //--- 集合中的 (1) 最小值,和 (2) 最大值
   double            LowMinimum(void);
   double            HighMaximum(void);
   //--- 价格范围
   double            PriceRange(void);
  };
//+------------------------------------------------------------------+
//| 集合中的最小值                                                      |
//+------------------------------------------------------------------+
double CZigZagModule::LowMinimum(void)
  {
   return(m_zz_low[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| 集合中的最大值                                                      |
//+------------------------------------------------------------------+
double CZigZagModule::HighMaximum(void)
  {
   return(m_zz_high[::ArrayMaximum(m_zz_high)]);
  }
//+------------------------------------------------------------------+
//| 价格范围                                                          |
//+------------------------------------------------------------------+
double CZigZagModule::PriceRange(void)
  {
   return(HighMaximum()-LowMinimum());
  }

另一组 CZigZagModule 类方法允许接收以下值:

  • SmallestSegment() – 返回所获取数据中的最小线段。
  • LargestSegment() – 返回所获取数据中的最大线段。 
  • LeastNumberOfSegmentBars() – 返回所获取数据中线段的较小柱线数量。
  • MostNumberOfSegmentBars() – 返回所获取数据中线段的较大柱线数量。

该类已经有了通过指定索引获取线段大小和线段柱线数量的方法。 因此,从上面的列表中能够很容易地理解方法的代码。 所有这些只是在它们内部调用的方法中有所不同,所以,我将仅提供其中两段代码 — CZigZagModule::SmallestSegmen() 和 CZigZagModule::MostNumberOfSegmentBars()。

class CZigZagModule
  {
public:
   //--- 集合中最小的线段
   double            SmallestSegment(void);
   //--- 集合中最大的线段
   double            LargestSegment(void);
   //--- 集合中最小的线段柱线数量
   int               LeastNumberOfSegmentBars(void);
   //--- 集合中最大的线段柱线数量 
   int               MostNumberOfSegmentBars(void);
  };
//+------------------------------------------------------------------+
//| 集合中最小的线段                                                    |
//+------------------------------------------------------------------+
double CZigZagModule::SmallestSegment(void)
  {
   double min_size=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         min_size=SegmentSize(0);
         continue;
        }
      //---
      double size=SegmentSize(i);
      min_size=(size<min_size)? size : min_size;
     }
//---
   return(min_size);
  }
//+------------------------------------------------------------------+
//| 集合中最大线段柱线数量                                               |
//+------------------------------------------------------------------+
int CZigZagModule::MostNumberOfSegmentBars(void)
  {
   int max_bars=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         max_bars=SegmentBars(0);
         continue;
        }
      //---
      int bars=SegmentBars(i);
      max_bars=(bars>max_bars)? bars : max_bars;
     }
//---
   return(max_bars);
  }

当搜索形态时,我们也许需要定义指定线段的大小与前一段的差值(以%为单位)。 若要解决此类任务,请使用 CZigZagModule::PercentDeviation() 方法。

class CZigZagModule
  {
public:
   //--- 偏差百分比
   double            PercentDeviation(const int index);
  };
//+------------------------------------------------------------------+
//| 偏差百分比                                                         |
//+------------------------------------------------------------------+
double CZigZagModule::PercentDeviation(const int index)
  {
   return(SegmentSize(index)/SegmentSize(index+1)*100);
  }

现在我们来看看如何将获取的数据可视化,并在自定义项目中使用 CZigZagModule 类。


可视化获得的数据集

收到不同时间帧的 ZigZag 指标句柄后,我们可以在 EA 启动的当前图表上可视化线段。 我们使用趋势线类型的图形对象进行可视化。 CZigZagModule::CreateSegment() 私有方法用于创建对象。 当您需要显示来自不同时间帧和参数的 ZigZag 指标数据时,为避免重复,它接收线段索引和后缀(可选参数),用于形成图形对象的独有名称。 

CZigZagModule::ShowSegments() 和 CZigZagModule::DeleteSegments() 公有方法允许显示和删除图形对象。

class CZigZagModule
  {
public:
   //--- (1) 显示,和 (2) 删除对象
   void              ShowSegments(const string suffix="");
   void              DeleteSegments(void);
   //---
private:
   //--- 创建对象
   void              CreateSegment(const int segment_index,const string suffix="");
  };
//+------------------------------------------------------------------+
//| 在图表上显示 ZZ 线段                                                |
//+------------------------------------------------------------------+
void CZigZagModule::ShowSegments(const string suffix="")
  {
   for(int i=0; i<m_segments_total; i++)
      CreateSegment(i,suffix);
  }
//+------------------------------------------------------------------+
//| 移除线段                                                           |
//+------------------------------------------------------------------+
void CZigZagModule::DeleteSegments(void)
  {
   for(int i=0; i<m_segments_total; i++)
     {
      string name="zz_"+string(::ChartID())+"_"+string(i);
      ::ObjectDelete(::ChartID(),name);
     }
  }

在类中添加了在图表上显示注释的方法,以便快速反馈有关获取的指标数据的基本信息。 显示所计算指标数据的方法代码如下所示。

 class CZigZagModule
  {
public:
   //--- 在图表上的注释
   void              CommentZigZagData();
   void              CommentShortZigZagData();
  };
//+------------------------------------------------------------------+
//| 将 ZigZag 数据显示为图表注释                                         |
//+------------------------------------------------------------------+
void CZigZagModule::CommentShortZigZagData(void)
  {
   string comment="Current direction : "+string(m_direction)+"\n"+
                  "Copy extremums: "+string(m_copy_extremums)+
                  "\n---\n"+
                  "SegmentsTotalBars(): "+string(SegmentsTotalBars())+"\n"+
                  "SegmentsTotalSeconds(): "+string(SegmentsTotalSeconds())+"\n"+
                  "SegmentsTotalMinutes(): "+string(SegmentsTotalSeconds()/60)+"\n"+
                  "SegmentsTotalHours(): "+string(SegmentsTotalSeconds()/60/60)+"\n"+
                  "SegmentsTotalDays(): "+string(SegmentsTotalSeconds()/60/60/24)+
                  "\n---\n"+
                  "PercentSumUp(): "+::DoubleToString(SumSegmentsUp()/SegmentsSum()*100,2)+"\n"+
                  "PercentSumDown(): "+::DoubleToString(SumSegmentsDown()/SegmentsSum()*100,2)+"\n"+
                  "PercentDifference(): "+::DoubleToString(PercentSumSegmentsDifference(),2)+
                  "\n---\n"+
                  "SmallestSegment(): "+::DoubleToString(SmallestSegment()/_Point,0)+"\n"+
                  "LargestSegment(): "+::DoubleToString(LargestSegment()/_Point,0)+"\n"+
                  "LeastNumberOfSegmentBars(): "+string(LeastNumberOfSegmentBars())+"\n"+
                  "MostNumberOfSegmentBars(): "+string(MostNumberOfSegmentBars());
//---
   ::Comment(comment);
  }

我们来开发一个接收和可视化获取数据的应用程序。


用于测试所获结果的 EA

让我们开发一个简单的测试 EA,用于接收和可视化 ZigZag 指标数据。 为了尽可能简化代码,我们不会执行额外的检查。 该示例的主要目的是展示获取数据的原则。 

将含有 CZigZagModule 类的文件包含到 EA 文件中并声明其实例。 这里有两个外部参数,允许您指定要复制的极值数量,以及形成新的 ZigZag 指标线段的最小距离。 在全局层面,我们还声明了用于获取源数据的动态数组,和用于指标句柄的变量。 

//+------------------------------------------------------------------+
//|                                                    TestZZ_01.mq5 |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ZigZagModule.mqh>
CZigZagModule zz_current;

//--- 外部参数
input int CopyExtremum   =3;
input int MinImpulseSize =0;

//--- 初始数据的数组
double   l_zz[];
double   h_zz[];
datetime t_zz[];

//--- ZZ 指标句柄
int zz_handle_current=WRONG_VALUE;

OnInit() 函数中,我们(1)接收指标句柄,(2)设置形成最终数据的极值数量,和基于所获集合绘制线段的颜色,以及 (3)为源数据数组设置反向索引顺序。

//+------------------------------------------------------------------+
//| 智能系统初始化函数                                                  |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- ZZ 指标路径
   string zz_path="Custom\\ZigZag\\ExactZZ_Plus.ex5";
//--- 获取指标句柄
   zz_handle_current=::iCustom(_Symbol,_Period,zz_path,10000,MinImpulseSize,true,true);
//--- 设置线段的颜色和要获取的极值数量
   zz_current.LinesColor(clrRed);
   zz_current.CopyExtremums(CopyExtremum);
//--- 设置反向索引顺序 (... 3 2 1 0)
   ::ArraySetAsSeries(l_zz,true);
   ::ArraySetAsSeries(h_zz,true);
   ::ArraySetAsSeries(t_zz,true);
   return(INIT_SUCCEEDED);
  }

OnTick() 函数中,通过其句柄和柱线开盘时间获取指标源数据。 然后 通过调用CZigZagModule::GetZigZagData() 方法来准备最终数据。 总之,将获得的 ZigZag 指标数据可视化为线段,并在图表上显示该数据作为注释。

//+------------------------------------------------------------------+
//| 智能系统即时报价函数                                                 |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//--- 获取源数据
   int copy_total=1000;
   ::CopyTime(_Symbol,_Period,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_current,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_current,3,0,copy_total,l_zz);
//--- 获取最终数据
   zz_current.GetZigZagData(h_zz,l_zz,t_zz);
//--- 在图表上可视化线段
   zz_current.ShowSegments();
//--- 在图表上显示数据值作为注释
   zz_current.CommentZigZagData();
  }

如果我们在策略测试器中以可视化模式启动 EA,我们将看到以下内容。 在这种情况下已经获得了 5 个高点和 5 个低点极值。 作为结果,在图表上以红色高亮显示出 9 根线段。

 图例 3. 在可视化模式下的演示(一个 ZigZag)

图例 3. 在可视化模式下的演示(一个 ZigZag)

如果我们需要同时从不同的时间帧获取 ZigZag 指标数据,则应略微增强测试 EA 的代码。 我们研究一个需要从三个时间帧获取数据的示例。 在这种情况下,您需要声明三个 CZigZagModule 类的实例。 第一个时间帧取自 EA 启动的当前图表。 其他两个则是 M15 和 H1。

#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_current;
CZigZagModule zz_m15;
CZigZagModule zz_h1;

每个指标都有自己的变量来保存所获句柄:

//--- ZZ 指标句柄
int zz_handle_current =WRONG_VALUE;
int zz_handle_m15     =WRONG_VALUE;
int zz_handle_h1      =WRONG_VALUE;

接下来,在 OnInit() 函数中,分别为每个指标接收句柄并设置颜色和极值数量:

//+------------------------------------------------------------------+
//| 智能系统初始化函数                                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- ZZ 指标路径
   string zz_path="Custom\\ZigZag\\ExactZZ_Plus.ex5";
//--- 获取指标句柄
   zz_handle_current =::iCustom(_Symbol,_Period,zz_path,10000,MinImpulseSize,false,false);
   zz_handle_m15     =::iCustom(_Symbol,PERIOD_M15,zz_path,10000,MinImpulseSize,false,false);
   zz_handle_h1      =::iCustom(_Symbol,PERIOD_H1,zz_path,10000,MinImpulseSize,false,false);
//--- 设置线段颜色
   zz_current.LinesColor(clrRed);
   zz_m15.LinesColor(clrCornflowerBlue);
   zz_h1.LinesColor(clrGreen);
//--- 设置接收的极值数量
   zz_current.CopyExtremums(CopyExtremum);
   zz_m15.CopyExtremums(CopyExtremum);
   zz_h1.CopyExtremums(CopyExtremum);
//--- 设置反向索引顺序 (... 3 2 1 0)
   ::ArraySetAsSeries(l_zz,true);
   ::ArraySetAsSeries(h_zz,true);
   ::ArraySetAsSeries(t_zz,true);
   return(INIT_SUCCEEDED);
  }

OnTick() 函数中分别接收每个 ZigZag 指标实例的数据,如上所示。 图表上只显示一个指标的注释。 在此情况下,我们能看到当前时间帧指标的摘要数据

//+------------------------------------------------------------------+
//| 智能系统即时报价函数                                                 |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   ::CopyTime(_Symbol,_Period,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_current,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_current,3,0,copy_total,l_zz);
   zz_current.GetZigZagData(h_zz,l_zz,t_zz);
   zz_current.ShowSegments("_current");
   zz_current.CommentShortZigZagData();
//---
   ::CopyTime(_Symbol,PERIOD_M15,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_m15,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_m15,3,0,copy_total,l_zz);
   zz_m15.GetZigZagData(h_zz,l_zz,t_zz);
   zz_m15.ShowSegments("_m15");
//---
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,3,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
  }

以下是它的外观:

 图例 4. 在可视化模式下的演示(三个 ZigZags)

图例 4. 在可视化模式下的演示(三个 ZigZags)

我们可以看到,来自更高时间帧的指标极值略微向左漂移。 原因是其顶部和底部是由指标句柄所在时间帧的柱线开盘时间设定的。 


恢复 CZigZagModule 类的开发

观察已获得的结果,可能会有种想法它们足以完成 ZigZag 指标的处理。 但事实上,情况并非如此。 我们需要继续开发 CZigZagModule 代码类,用合用的新方法填充它。 

到目前为止,我们从最近的柱线开始从 ZigZag 指标获取数据并深入到历史数据中。 然而,我们可能还需要获取特定时间帧内的数据。 为了实现这一点,我们用另一组参数编写另一个方法 CZigZagModule::GetZigZagData()。 在这个版本中,我们将在方法内部接收初始数据,因此,我们将需要指标句柄,品种,时间帧和时间范围(开始和结束日期)作为参数。

进而,我们需要在所获数据中分别 计算高点和低点的数量。 在此情况下,进一步处理的极值数量由 这些计数器之间的最小量 定义。 

在最后以另一组参数调用同名方法 CZigZagModule::GetZigZagData()。 我们已研究了上面的设置,同时描述了如何将含有源数据的数组作为参数进行传递,以便获取最终数据。

class CZigZagModule
  {
private:
   //--- 用于获取源数据的数组
   double            m_zz_lows_temp[];
   double            m_zz_highs_temp[];
   datetime          m_zz_time_temp[];
   //---
public:
   //--- 取数据
   void              GetZigZagData(const int handle,const string symbol,const ENUM_TIMEFRAMES period,const datetime start_time,const datetime stop_time);
  };
//+------------------------------------------------------------------+
//| 从所传递的句柄当中获取 ZZ 数据                                        |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const int handle,const string symbol,const ENUM_TIMEFRAMES period,const datetime start_time,const datetime stop_time)
  {
//--- 获取源数据
   ::CopyTime(symbol,period,start_time,stop_time,m_zz_time_temp);
   ::CopyBuffer(handle,2,start_time,stop_time,m_zz_highs_temp);
   ::CopyBuffer(handle,3,start_time,stop_time,m_zz_lows_temp);
//--- 计数器
   int lows_counter  =0;
   int highs_counter =0;
//--- 高点计数
   int h_total=::ArraySize(m_zz_highs_temp);
   for(int i=0; i<h_total; i++)
     {
      if(m_zz_highs_temp[i]>0)
         highs_counter++;
     }
//--- 低点计数
   int l_total=::ArraySize(m_zz_lows_temp);
   for(int i=0; i<l_total; i++)
     {
      if(m_zz_lows_temp[i]>0)
         lows_counter++;
     }
//--- 得到极值的数量
   int copy_extremums=(int)::fmin((double)highs_counter,(double)lows_counter);
   CopyExtremums(copy_extremums);
//--- 在循环中按顺序移动所复制的 ZZ 数值
   GetZigZagData(m_zz_highs_temp,m_zz_lows_temp,m_zz_time_temp);
  }

使用 CZigZagModule::SmallestMinimumTime() 和 CZigZagModule::LargestMaximumTime() 方法从获取的数据集中得到最低和最高极值的时间。 

class CZigZagModule
  {
public:
   //--- 最小值的时间
   datetime          SmallestMinimumTime(void);
   //--- 最大值的时间
   datetime          LargestMaximumTime(void);
  };
//+------------------------------------------------------------------+
//| 最小值的时间                                                       |
//+------------------------------------------------------------------+
datetime CZigZagModule::SmallestMinimumTime(void)
  {
   return(m_zz_low_time[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| 最大值的时间                                                       |
//+------------------------------------------------------------------+
datetime CZigZagModule::LargestMaximumTime(void)
  {
   return(m_zz_high_time[::ArrayMaximum(m_zz_high)]);
  }

此外,我们扩展处理 ZigZag 线段的方法列表。 将通过链接传递的若干数值一次性赋予变量可能很方便。 该类有三种这样的方法:

  • SegmentBars() 返回指定线段的开始和结束柱线索引。
  • SegmentPrices() 返回指定线段的开始和结束价格。
  • SegmentTimes() 返回指定线段的开始和结束时间。

在其他先前研究的方法中存在类似的结构,因此下面仅提供一个样本代码。 

class CZigZagModule
  {
public:
   //--- 返回指定线段的开始和结束柱线
   bool              SegmentBars(const int index,int &start_bar,int &stop_bar);
   //--- 返回指定线段的开始和结束价格
   bool              SegmentPrices(const int index,double &start_price,double &stop_price);
   //--- 返回指定线段的开始和结束时间
   bool              SegmentTimes(const int index,datetime &start_time,datetime &stop_time);
  };
//+------------------------------------------------------------------+
//| 返回指定线段的开始和结束柱线                                          |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentBars(const int index,int &start_bar,int &stop_bar)
  {
   if(index>=m_segments_total)
      return(false);
//--- 如果是偶数
   if(index%2==0)
     {
      int i=index/2;
      //---
      start_bar =(Direction()>0)? m_zz_low_bar[i] : m_zz_high_bar[i];
      stop_bar  =(Direction()>0)? m_zz_high_bar[i] : m_zz_low_bar[i];
     }
//--- 如果是奇数
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
         //---
         start_bar =m_zz_high_bar[h];
         stop_bar  =m_zz_low_bar[l];
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
         //---
         start_bar =m_zz_low_bar[l];
         stop_bar  =m_zz_high_bar[h];
        }
     }
//---
   return(true);
  }

假设我们有一个 M5 图表并从 H1 接收数据。 我们从 H1 时间帧中寻找形态,我们需要从当前的 H1 时间帧中定义特定 ZigZag 线段的价格行为。 换句话说,我们想知道指定线段如何在较低的时间帧上形成。

如上一节所示,来自较高时间帧的线段极值,显示在当前图表上那根开盘时间与其相应的柱线上(意即不一定位于当前图表的极值点)。 我们已经有 CZigZagModule::SegmentTimes() 方法返回指定线段的开始和结束时间。 如果我们使用此时间范围从较低的时间帧获取 ZigZag 数据,那么在大多数情况下,我们将得到许多实际上对于更高时间帧来说属于冗余的其他线段。 我们用另一组参数编写另一个 CZigZagModule::SegmentTimes() 方法,以备需要更高的精度。 此外,我们将需要几个私有辅助方法来接收(1)源数据 和(2)所传递数组中的最小值和最大值的索引。 

class CZigZagModule
  {
private:
   //--- 将源数据复制到所传递的数组
   void              CopyData(const int handle,const int buffer_index,const string symbol,
                              const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                              double &zz_array[],datetime &time_array[]);
   //--- 返回来自所传递的数组中(1)最小值,和(2)最大值的索引
   int               GetMinValueIndex(double &zz_lows[]);
   int               GetMaxValueIndex(double &zz_highs[]);
  };
//+------------------------------------------------------------------+
//| 将源数据复制到所传递的数组                                            |
//+------------------------------------------------------------------+
void CZigZagModule::CopyData(const int handle,const int buffer_index,const string symbol,
                             const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                             double &zz_array[],datetime &time_array[])
  {
   ::CopyBuffer(handle,buffer_index,start_time,stop_time,zz_array);
   ::CopyTime(symbol,period,start_time,stop_time,time_array);
  }
//+------------------------------------------------------------------+
//| 从所传递的数组返回最大值的索引                                         |
//+------------------------------------------------------------------+
int CZigZagModule::GetMaxValueIndex(double &zz_highs[])
  {
   int    max_index =0;
   double max_value =0;
   int total=::ArraySize(zz_highs);
   for(int i=0; i<total; i++)
     {
      if(zz_highs[i]>0)
        {
         if(zz_highs[i]>max_value)
           {
            max_index =i;
            max_value =zz_highs[i];
           }
        }
     }
//---
   return(max_index);
  }
//+------------------------------------------------------------------+
//| 从所传递的数组返回最小值的索引                                         |
//+------------------------------------------------------------------+
int CZigZagModule::GetMinValueIndex(double &zz_lows[])
  {
   int    min_index =0;
   double min_value =INT_MAX;
   int total=::ArraySize(zz_lows);
   for(int i=0; i<total; i++)
     {
      if(zz_lows[i]>0)
        {
         if(zz_lows[i]<min_value)
           {
            min_index =i;
            min_value =zz_lows[i];
           }
        }
     }
//---
   return(min_index);
  }

另一个已实现的 CZigZagModule::SegmentTimes() 方法用于接收较低时间帧的指定线段的开始和结束时间。 这需要一些解释。 以下参数会传递给方法:

  • handle — 来自较低时间帧的 ZigZag 指标句柄。
  • highs_buffer_index — 指标缓冲区里最大极值的索引。
  • lows_buffer_index — 指标缓冲区里最小极值的索引。
  • symbol — 较低时间帧的品种。
  • period — 较高时间帧的周期。
  • in_period — 较低时间帧的周期。
  • index — 较高时间帧的线段索引。

返回通过引用传递的参数值:

  • start_time — 较低时间帧的线段开始时间。
  • stop_time — 较低时间帧的线段结束时间。

首先,我们需要获得指定线段的第一根和最后一根柱线的开盘时间。 为此,调用上面描述的第一个 CZigZagModule::SegmentTimes() 方法。 

接下来,使用 CZigZagModule::CopyData() 方法接收极值和柱线时间的数据。 根据线段方向,我们按特定顺序获取数据。 上行方向的情况下,我们首先获得较低时间帧 ZigZag 的最小值数据,这些最小值在更高的时间帧内是形成线段的第一根柱线的一部分。 之后,我们获得较低时间帧 ZigZag 的最大值数据,这些数据是形成较高时间帧内线段的最后一根柱线的一部分。 下行方向的情况下,动作的顺序是相反的。 首先,我们需要获取有关最大值的数据,然后获取有关最小值的信息。 

收到源数据后,找到最大值和最小值索引。 使用这些索引,您可以在较低的时间帧内找到所分析线段的开始和结束时间。

class CZigZagModule
  {
public:
   //--- 返回较低时间帧内指定线段的开始和结束时间
   bool              SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                  const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                  const int index,datetime &start_time,datetime &stop_time);
  };
//+------------------------------------------------------------------+
//| 返回指定线段的                                                      |
//| 开始和结束时间                                                      |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                 const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                 const int index,datetime &start_time,datetime &stop_time)
  {
//--- 在不考虑当前时间帧的情况下获得时间
   datetime l_start_time =NULL;
   datetime l_stop_time  =NULL;
   if(!SegmentTimes(index,l_start_time,l_stop_time))
      return(false);
//---
   double   zz_lows[];
   double   zz_highs[];
   datetime zz_lows_time[];
   datetime zz_highs_time[];
   datetime start =NULL;
   datetime stop  =NULL;
   int      period_seconds=::PeriodSeconds(period);
//--- 上行方向的情况下获取源数据
   if(SegmentDirection(index)>0)
     {
      //--- 更高时间帧上第一根柱线的数据
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
      //--- 更高时间帧上最后一根柱线的数据
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
     }
//--- 下行方向的情况下获取源数据
   else
     {
      //--- 更高时间帧上第一根柱线的数据
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
      //--- 更高时间帧上最后一根柱线的数据
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
     }
//--- 寻找最大值索引
   int max_index =GetMaxValueIndex(zz_highs);
//--- 寻找最小值索引
   int min_index =GetMinValueIndex(zz_lows);
//--- 获取线段的开始和结束时间
   start_time =(SegmentDirection(index)>0)? zz_lows_time[min_index] : zz_highs_time[max_index];
   stop_time  =(SegmentDirection(index)>0)? zz_highs_time[max_index] : zz_lows_time[min_index];
//--- 成功
   return(true);
  }

现在我们来编写一个测试 EA。 当前时间帧为 M5。 在策略测试器的可视化模式下使用它启动 EA。 我们将从 H1 以及当前时间帧里接收数据。 EA 代码与之前研究的代码类似,因此我在此处仅展示 OnTick() 函数的内容。

首先,我们将使用第一种方法获取 H1 的数据,并在图表上清晰显示线段。 接下来,依照 H1 第三个(索引 2)ZigZag 线段的时间范围内从当前时间帧(M5)里获取 ZigZag 数据。 为此,从当前时间帧获取线段的开始和结束

然后使用第二种方法获取当前时间帧的数据,并在图表上显示线段以确保一切正常。

//+------------------------------------------------------------------+
//| 智能系统即时报价函数                                                 |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   int h_buff=2,l_buff=3;
//--- 获取数据的第一个方法
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,h_buff,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,l_buff,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
//---
   int      segment_index =2;
   int      start_bar     =0;
   int      stop_bar      =0;
   double   start_price   =0.0;
   double   stop_price    =0.0;
   datetime start_time    =NULL;
   datetime stop_time     =NULL;
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//---
   zz_h1.SegmentBars(segment_index,start_bar,stop_bar);
   zz_h1.SegmentPrices(segment_index,start_price,stop_price);
   zz_h1.SegmentTimes(segment_index,start_time,stop_time);
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,segment_index,start_time_in,stop_time_in);
   
//--- 获取数据的第二个方法
   zz_current.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current.ShowSegments("_current");
   
//--- 在图表注释中显示数据
   string comment="Current direction : "+string(zz_h1.Direction())+"\n"+
                  "\n---\n"+
                  "Direction > segment["+string(segment_index)+"]: "+string(zz_h1.SegmentDirection(segment_index))+
                  "\n---\n"+
                  "Start bar > segment["+string(segment_index)+"]: "+string(start_bar)+"\n"+
                  "Stop bar > segment["+string(segment_index)+"]: "+string(stop_bar)+
                  "\n---\n"+
                  "Start price > segment["+string(segment_index)+"]: "+::DoubleToString(start_price,_Digits)+"\n"+
                  "Stop price > segment["+string(segment_index)+"]: "+::DoubleToString(stop_price,_Digits)+
                  "\n---\n"+
                  "Start time > segment["+string(segment_index)+"]: "+::TimeToString(start_time,TIME_DATE|TIME_MINUTES)+"\n"+
                  "Stop time > segment["+string(segment_index)+"]: "+::TimeToString(stop_time,TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "Start time (in tf) > segment["+string(segment_index)+"]: "+::TimeToString(start_time_in,TIME_DATE|TIME_MINUTES)+"\n"+
                  "Stop time (in tf) > segment["+string(segment_index)+"]: "+::TimeToString(stop_time_in,TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "Extremums copy: "+string(zz_current.CopyExtremums())+"\n"+
                  "SmallestMinimumTime(): "+string(zz_current.SmallestMinimumTime())+"\n"+
                  "LargestMaximumTime(): "+string(zz_current.LargestMaximumTime());
//---
   ::Comment(comment);
  }

这是它的样子:

 图例 5. 接收指定线段内的数据

图例 5. 接收指定线段内的数据

接下来,开发另一个 EA,用于接收来自更高时间帧里三个线段的数据。

现在,我们应当在文件开头声明四个 CZigZagModule 类的实例。 其中一个用于更高时间帧(H1),而其余三个用于当前时间帧。 在这种情况下,我们针对 M5 进行测试。 

CZigZagModule zz_h1;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;

为了更清晰,较高时间帧内的线段会以不同颜色显示在较低时间帧内:

//--- 设置线段颜色
   zz_current0.LinesColor(clrRed);
   zz_current1.LinesColor(clrLimeGreen);
   zz_current2.LinesColor(clrMediumPurple);
   zz_h1.LinesColor(clrCornflowerBlue);

OnTick() 函数中,我们首先接收 H1 时间帧数据,然后从较低时间帧里按顺序获取第一,第二和第三根线段的数据。 在图表注释中分组显示较低时间帧和较高时间帧上获取的线段数据。 在这种情况下,此为线段总和百分比之间的差值。 可以使用 CZigZagModule::PercentSumSegmentsDifference() 方法得到它。 

//+------------------------------------------------------------------+
//| 智能系统即时报价函数                                                 |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   int h_buff=2,l_buff=3;
//--- 获取数据的第一个方法
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,h_buff,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,l_buff,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
//---
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//--- 第一根线段数据
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current0.ShowSegments("_current0");
//--- 第二根线段数据
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,1,start_time_in,stop_time_in);
   zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current1.ShowSegments("_current1");
//--- 第三根线段数据
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,2,start_time_in,stop_time_in);
   zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current2.ShowSegments("_current2");
//--- 在图表注释中显示数据
   string comment="H1: "+::DoubleToString(zz_h1.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[0]: "+::DoubleToString(zz_current0.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[1]: "+::DoubleToString(zz_current1.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[2]: "+::DoubleToString(zz_current2.PercentSumSegmentsDifference(),2);
//---
   ::Comment(comment);
  }

以下是它在图表上的样子:

 图例 6. 接收三个指定线段内数据

图例 6. 接收三个指定线段内数据

这种方法为分析形态中价格行为的性质提供了附加的机会。 假设我们在 H1 上定义形态,并分析每根线段内的价格行为如何。 CZigZagModule 类方法允许获取极值和线段的所有属性,例如:

  • 每个单独极值的柱线价格、时间和索引。
  • 每根单独线段的大小。
  • 每根线段的持续时间,以柱线为单位。
  • 整个所获线段集合的价格范围大小。
  • 整个线段集合形成的持续时间(以柱线为单位)。
  • 单向线段的总和。
  • 反向线段总和的比率,等等。 

此基本集合可用作开发多个自定义参数,来构建指标的起点。 测试将展示从中会得到哪些益处。 本网站包含许多文章,也许会有助您对该主题自行进行研究。 


结束语

ZigZag 不适合生成交易信号的说法在交易论坛上广泛传播。 这是一个极大的误解。 事实上,没有其他指标能提供如此多的信息来判定价格行为的性质。 现在,您拥有了一款工具,可以轻松获取所有必需的 ZigZag 指标数据,以便进行更详尽的分析。

在本系列的后续文章中,我将展示使用 CZigZagModule 类可以开发哪些指标,以及从 ZigZag 指标获取不同品种的统计数据,还有基于 ZigZag 交易策略的 EA 开发。

文件名 注释
MQL5\Indicators\Custom\ZigZag\ExactZZ_Plus.mq5 改编版 ZigZag 指标
MQL5\Experts\ZigZag\TestZZ_01.mq5 用于测试单个数据集的 EA
MQL5\Experts\ZigZag\TestZZ_02.mq5 测试来自不同时间帧的三个数据集的 EA
MQL5\Experts\ZigZag\TestZZ_03.mq5 在指定的更高时间帧内采集数据的测试 EA
MQL5\Experts\ZigZag\TestZZ_04.mq5 在三个指定的更高时间帧内采集数据的测试 EA


本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/5543

附加的文件 |
MQL5.zip (17.45 KB)
研究烛条分析技术(第一部分):检查现存形态 研究烛条分析技术(第一部分):检查现存形态
在本文中,我们将研讨流行的烛条形态,并尝试探索它们在当今市场中是否仍然相关和有效。 烛条分析出现在 20 多年前,从此后变得非常流行。 众多交易者认为日本烛条是最方便、易懂的资产价格可视化形式。
相关性在交易中的实际应用 相关性在交易中的实际应用
在本文中,我们将分析变量之间相关性的概念,以及相关系数的计算方法及其在交易中的实际应用。相关性是两个或多个随机变量之间的统计关系(或可以被视为具有某种可接受精度的随机量)。一个或多个变量的变化导致其他相关变量的系统变化。
ZigZag (之字折线) 的力量(第二部分)。 接收、处理和显示数据的示例 ZigZag (之字折线) 的力量(第二部分)。 接收、处理和显示数据的示例
在本文的第一部分当中,我曾描述过一个修订的 ZigZag (之字折线) 指标和一个用于接收该类型指标数据的类。 在此,我将展示如何基于这些工具开发指标,并编写一款根据 ZigZag 指标形成的信号进行交易的 EA 来测试。 作为补充,本文将介绍一套开发图形用户界面的新版 EasyAndFast 函数库。
以马丁格尔(翻倍加仓)为基础的长线交易策略 以马丁格尔(翻倍加仓)为基础的长线交易策略
在本文中,我们将深入研究马丁格尔(翻倍加仓)系统。 我们将评测该系统是否可以用于实盘交易,以及在运用它时如何将风险减至最小。 这一简单系统的主要缺点在于很可能会将全部存款亏损。 如果您决定使用马丁格尔技术进行交易,则必须考虑这一事实。