English Русский Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
在视图内/外绘制通道

在视图内/外绘制通道

MetaTrader 5指标 | 30 十二月 2013, 08:35
1 900 0
Dmitriy Skub
Dmitriy Skub

简介

如果说通道是继移动平均线之后最流行的市场分析和交易决策工具,我想这并没有夸大。在本系列的第一篇文章中,我们主要介绍通道,讨论某指标的数据基础和理论实现,该指标用于在客户端的屏幕上绘制由三个极值设定的通道。

乍一看,绘制通道似乎是一个简单的任务,因为它基于直线的方程,这在小学就已教过。然而,这在客户端中实际实施时将涉及到大量问题,回答这些问题并没那么简单。

如何以最佳方式组织极值的设置和追踪它们的变化?如何绘制通道(如果其中间部分位于缺少的柱上)?如果通道的左极值位于星期五且其右极值位于星期一,在星期五和星期一之间的休假日没有柱将会怎么样?我们如何获得通道边界的当前值?

这些问题以及其他一些问题将在本系列关于通道的第一篇文章中予以解答。本文还包括使用标准类和面向对象的方法,通过三个指定的极值实现通道的绘制。我们将以指标的形式实现通道的绘制工具。


设置极值

事实上,通道在图表上的位置需通过至少三个极值确定。如果要给极值下个定义,我们认为是:它是函数在指定范围的最大最小值。达到极值的点称为极值点。相应地,如果达到的是最小值,则该极值点称为最小值点,反之为最大值点

数学分析定义了另一个术语:局部极值(包括最小值和最大值)。在最大(最小)值点,函数值大于(小于)所有相邻点的值。该定义出自维基百科(译自俄语)。

为绘制通道,我们需要局部极值。让我们通过图表来显示局部极值,无需使用数学公式。在下面的图 1 中,有三个用红色价格水平标示的局部极值。矩形点显示了两个最大值和一个最小值:

图 1. 局部极值示例

图 1. 局部极值示例

不是所有存在的极值都在图表上标示了出来,仅仅标识那些最重要的。对于烛形图或条形图,使用“分形”术语定义极值十分方便 - 当向左或向右的多个相邻柱严格递减或递增时(请参见图 1)。

由于我们的目的不是建立自动的通道绘制工具,则极值位置将按照图 1 所示进行设置 - 按照时间轴和价格轴上的位置。最适于该目的的是价格标签 - MetaTrader 5 客户端的特殊图形对象。价格标签具有时间和价格坐标属性,可以明确地识别图表上的极值。


存储极值的对象 - TExtremum 类

我们首先要做的是建立存储极值的容器类和操作极值组的类。由于我们要尽可能地使用终端中包含的标准类TExtremum 类将继承自标准类 CObject。类的描述如下所示:

class TExtremum : public CObject
{
private:
  datetime  extr_datetime;              // data/time in an extremum point
  double    extr_price;                 // price in an extremum point
        
protected:
  virtual int  Compare(const CObject* _node, int _mode = 0) const;

public:
  void      TExtremum();               // constructor
  void      ~TExtremum();              // destructor
  void      SetExtremum(datetime _time, double _price);  // change date/time and price in an extremum point
  void      SetDateTime(datetime _time);                 // change date/time in an extremum point
  void      SetPrice(double _price);  // change price in an extremum point

public:
  datetime  GetDateTime() const;      // get date/time in an extremum point
  double    GetPrice() const;         // get price in an extremum point

public:
  virtual bool  SaveExtremum(string _dt_name, string _p_name);  // save extremum
  virtual bool  LoadExtremum(string _dt_name, string _p_name);  // load extremum
  virtual bool  DeleteExtremum(string _dt_name, string _p_name);// delete extremum
};

大部分方法都是无关紧要的,无需关注它们的实施情况。我们应该关注的是 TExtremum::Compare 方法。该方法在 CObject 类中声明,用于在列表中排序。我们按以下方式实施:

//---------------------------------------------------------------------
//  Comparing two extremums by time:
//---------------------------------------------------------------------
int TExtremum::Compare(const CObject* _node, int _mode = 0) const
{
  datetime  temp = ((TExtremum* )_node).GetDateTime();
  datetime  curr = GetDateTime();
  if(curr > temp)
  {
    return(_mode > 0 ? 1 : -1);
  }
  else if(curr < temp)
  {
    return(_mode > 0 ? -1 : 1);
  }

  return(0);
}

此处的参数 _mode 用于设置排序方向。如果此参数的值大于零,则排序是正向的(升序),反之为逆向(降序)。

此外,还有两个方法用于保存/加载极值。我们将极值存储在全局变量中。这些方法如下所示:

//---------------------------------------------------------------------
//  Save extremum (date/time):
//---------------------------------------------------------------------
bool TExtremum::SaveExtremum(string _dt_name, string _p_name)
{
  datetime  dt_result = GlobalVariableSet(_dt_name, (double)extr_datetime);
  datetime  p_result = GlobalVariableSet(_p_name, (double) extr_price);
  if(dt_result != 0 && p_result != 0)
  {
    return(true);
  }

  return(false);
}

//---------------------------------------------------------------------
//  Load extremum (date/time):
//---------------------------------------------------------------------
bool TExtremum::LoadExtremum(string _dt_name, string _p_name)
{
  double  dt_temp, p_temp;
  bool    result = GlobalVariableGet(_dt_name, dt_temp);
  result &= GlobalVariableGet(_p_name, p_temp);
  if(result != false)
  {
    extr_datetime = (datetime)dt_temp;
    extr_price = p_temp;
    return(true);
  }

  return(false);
}

在全局变量读/写的两个方法 TExtremum::LoadExtremumTExtremum::SaveExtremum在成功执行后返回 "true"。


操作极值列表 - TExtremumList 类

由于我们需要按时间存储和排序极值,我们需要从标准类 CList 继承 TExtremumList 类。通过这种继承,我们得到极值的一个通用操控工具,从而不必受限于极值的数量和类型。这可以进一步扩展绘制的通道数量。例如,如果多个极值非线性回归,我们可以增加通道的绘制。

该类的说明如下所示:

class TExtremumList : public CList
{
private:
  string              channel_prefix;     // channel name (prefix)
  ENUM_TIMEFRAMES      chart_timeframe;    // current timeframe
  string              chart_symbol;       // work symbols of the chart

protected:
  string    MakeDTimeName(int _nmb);     // get name for saving/reading data/time of an extremum
  string    MakePriceName(int _nmb);     // get name for saving/reading price of an extremum

public:
  void      TExtremumList();             // конструктор
  void     ~TExtremumList();             // деструктор
  void     SetChannelParams(string _pref, string _symbol = NULL, ENUM_TIMEFRAMES _curr_tf = PERIOD_CURRENT);
  void     AddExtremum(datetime _time, double  _price);
  void     DeleteAllExtremum();
  void     SaveExtremumList();
  void     LoadExtremumList();
  int      FindExtremum(datetime _dt);  // search extremum by specified time

public:
  datetime GetDateTime(int _index);
  double   GetPrice(int _index);
};

TExtremumList::AddExtremum 是类的主方法。它用于向列表中添加新的极值。添加后,列表中极值的排序根据极值点时间进行。该方法的代码如下所示:

void TExtremumList::AddExtremum(datetime _time, double  _price)
{
//  Create extremum:
  TExtremum*    extr = new TExtremum();
  extr.SetExtremum(_time, _price);

//  Add it in the list:
  Add(extr);

//  Sort:
  Sort(1);
}

在这里使用了基类的以下方法:CList::Add - 用于向列表添加新的元素;CList::Sort - 用于排序列表中的元素。方法 TExtremum::CompareCList::Sort 中使用。

我们来看看用于在列表中搜索具有指定时间的极值的方法 TExtremumList::FindExtremum。该方法的代码如下所示:

int TExtremumList::FindExtremum(datetime _dt)
{
  int           k = 0;
  TExtremum*    extr = (TExtremum*)(GetFirstNode());
  while(extr != NULL)
  {
    if(extr.GetDateTime() == _dt)
    {
      return(k);
    }
    extr = (TExtremum*)(GetNextNode());
  }
  return(-1);                     // extremum not found
}

在这里使用了基类的以下方法:CList::GetFirstNode - 用于获取列表中的第一个元素(如果列表为空,它返回一个零指针);CList::GetNextNode - 用于获取列表中的下一个元素(如果没有下一个元素且列表已结束,它返回一个零指针)。

注:

在类列表 CList 的内部数据中,存在一个指向当前元素的指针。当调用在列表中移动的方法(CList::GetFirstNode、CList::GetNextNode、CList::GetPrevNode 等)时,该指针改变。如果之前没有调用过这类方法,那么指向当前元素的指针指向第一个元素。

如果成功找到具有指定时间的极值,方法 TExtremumList::FindExtremum 为找到的元素的索引。如果没有这样的元素,它返回 -1。

方法 TExtremum::MakeDTimeNameTExtremum::MakePriceName 是辅助方法。它们用于获取保存和读取极值时使用的全局变量的名称。这些方法的实现如下所示:

string TExtremumList::MakeDTimeName(int _nmb)
{
  string    name;
  StringConcatenate(name, channel_prefix, "_", channel_symbol, "_", channel_timeframe, "_DTime_Extr", _nmb);
  return(name);
}

string TExtremumList::MakePriceName( int _nmb )
{
  string    name;
  StringConcatenate(name, channel_prefix, "_", channel_symbol, "_", channel_timeframe, "_Price_DTime_Extr", _nmb);
  return(name);
}

获取的名称的一个示例:"MainChannel_EURUSD_5_DTime_Extr1"。该名称对应于通道 MainChannel(常用名)的临时极值点、交易品种 EURUSD、时间表 5M 和极值编号 1 。极值的编号按照其时间的升序进行分配,从 1 开始。实际上,它是按升序排序的列表中在 1 上偏移的索引。

下图为终端中保存的三个极值的值的示例:

图 2. 存储在全局变量中的极值

图 2. 存储在全局变量中的极值

上文说明的类在本文所附的文件 ExtremumClasses.mqh 中。


手动设置极值的指标 - ExtremumHandSet

好了,我们已具备了开发第一个指标所需的一切,我们将使用它在手动模式下设置极值的位置。指标的代码在本文所附的文件 ExtremumHandSet.MQ5 中。让我们来具体讨论它的绘制。

首先,我们来想象一下,我们希望在屏幕上看到什么:

图 3. 设置极值的指标

图 3. 设置极值的指标

使用左价格标签,我们在图表的时间轴和价格轴上设置极值的位置。指标必须确定图表上那些标签的位置,在屏幕上显示临时极值点,并以上文中说明的格式将它们保存在客户端的全局变量中。此外,指标必须追踪价格标签在图表上的移动,并修正加载的临时极值点。

追踪价格标签在图表上的移动将每秒钟执行一次。这就使系统可以独立于出现的报价和工作/非工作日。

首先,让我们连接所需的库:

//---------------------------------------------------------------------
//  Included libraries:
//---------------------------------------------------------------------
#include  <TextDisplay.mqh>
#include  <ExtremumClasses.mqh>

第一个库包含用于组织在屏幕上显示文本信息的类(请参见《利用标准库类创建您自己的“市场报价”》一文)。使用它,我们可显示临时极值点的值。

然后我们添加该指标的输入参数(这里只说明主要的参数):

input string  PrefixString = "MainChannel";
//---------------------------------------------------------------------
input color   ExtremumPointColor = Yellow;
//---------------------------------------------------------------------
input bool    ShowInfo = true;

第一个参数 PrefixString 设置在写入/读取极值时用于构成全局变量的名称的前缀。它同样提供了在单个图表上使用多个这类指标的可能性。我们唯一要做的就是为它们设置不同的前缀。

参数 ExtremumPointColor 设置决定极值位置的左价格标签的颜色。价格标签必须呈指定的颜色。此一致性在指标中进行检查。具有不同参数的标签则被忽略。

参数 ShowInfo 控制有关指定极值点的文本信息在屏幕上的显示。

接下来,我们创建用于显示信息和操作极值的对象:

TableDisplay    TitlesDisplay;    // displaying information on the screen
//---------------------------------------------------------------------
TExtremumList*  PrevExtr_List;    // list of previous extremums
TExtremumList*  CurrExtr_List;    // list of current extremums
TExtremumList*  NewExtr_List;     // list of new extremums

这些对象的初始化如下所示:

PrevExtr_List = new TExtremumList();
PrevExtr_List.SetChannelParams(PrefixString, Symbol(), Period());
PrevExtr_List.LoadExtremumList();

CurrExtr_List = PrevExtr_List;

NewExtr_List = new TExtremumList();
NewExtr_List.SetChannelParams(PrefixString, Symbol(), Period());

PrevExtr_List 列表中,我们使用 TExtremumList::LoadExtremumList 方法从全局变量加载极值。此列表将存储极值以和新值进行比较,当拖动屏幕上价格标签时,新值将从图表中读取。

CurrExtr_List 列表用作当前列表,它存储当前的极值。由于在开始时我们只有从全局变量读取的极值,它们将被当作实际的极值。

NewExtr_List 列表中,我们将写入在图表上找到的新的极值。

我们来看一下在指标中使用的主函数。第一个函数 FindExtremumPoints 用于读取和检查确定极值位置的价格标签的参数:

bool FindExtremumPoints(long _chart_id)
{
  string  name;

//  1. Search for the total number of objects with specified parameters and write them to the list:
  int total_objects = ObjectsTotal(_chart_id, -1, OBJ_ARROW_LEFT_PRICE);
  if(total_objects <= 0)
  {
    return(false);
  }

  NewExtr_List.Clear();
  for(int i = 0; i < total_objects; i++)
  {
    name = ObjectName(_chart_id, i, -1, OBJ_ARROW_LEFT_PRICE);

    if( IsGraphicObjectGood(_chart_id, name, OBJ_ARROW_LEFT_PRICE, ExtremumPointColor) == true)
    {
      NewExtr_List.AddExtremum(ObjectGetInteger( _chart_id, name, OBJPROP_TIME),
                               ObjectGetDouble(_chart_id, name, OBJPROP_PRICE));
    }
  }

//  2. If three extremums are found, we can try to draw a channel:
  if(NewExtr_List.Total() == 3)
  {

//  Save the list of new extremums:
    NewExtr_List.SaveExtremumList();
    return(true);
  }

  NewExtr_List.Clear();
  return(false);
}

首先,通过调用 TExtremumList::Clear 方法清除 NewExtr_List 列表,然后把所有找到的具有指定参数的极值点添加到该列表。如果找到三个点,则列表在全局变量中保存,且函数返回 "true"。

另一个 CheakExtremumMoving 函数追踪极值点在图表上的移动。如果至少有一个点沿图表的时间轴移动,该函数返回 "true"。

其代码如下所示:

//---------------------------------------------------------------------
//  Check whether extremums have been moved on the screen:
//---------------------------------------------------------------------
bool CheakExtremumMoving()
{
  if(FindExtremumLines(0) == true)
  {
    int  count = NewExtr_List.Total();
    int  index;
    for(int i = 0; i < count; i++)
    {
      index = CurrExtr_List.FindExtremum(NewExtr_List.GetDateTime(i));

//  If a new extremum is found:
      if(index == -1)
      {
        PrevExtr_List = CurrExtr_List;
        CurrExtr_List = NewExtr_List;
        return(true);
      }
    }
    CurrExtr_List = PrevExtr_List;
  }

  return(false);
}

我们已讨论了在手动模式下设置极值点的方式。我们已准备好指标,可控制这一过程和将点写入全局变量。指标的完整代码在附件 ExtremumHandSet.mq5 中。现在,我们可以开始本文的主要部分 - 绘制通道!


绘制通道 - 一些理论

一个线性通道由两根严格通过极值点的平行线组成。此外,一根线必须通过两个点,而另一根线必须通过与第一条线平行的点。它可以通过一个简单的图片显示:

使用三个极值点绘制通道

图 4. 使用三个极值点绘制通道

从几何学我们得知,通过两点只能绘制一条直线。该直线在图 4 中为红色。它通过坐标为 (T1, P1) 和 (T2, P2) 的两个点;这两个点分别用字母 A 和 B 标示。直线的方程为:

(1)   P(t) = P1 + (t - T1)*(P2 - P1) / (T2 - T1);此处的 P(t) 是在时间 "t" 处计算得出的价格。


通过 C 点(第三个极值),我们绘制与第一条直线平行的另一条直线。该直线在图 3 中为绿色。由于两条直线具有相同的 T1 点和 T2 点,我们应找出 P1' 和 P2' 的值(请参见图 4)。

在继续之前,还需要做一个重要的说明。终端图表没有显示时间“断点”。例如,在休假日,当终端没有报价出现时,应显示为价格的中断。但糟糕的是并非如此。查看空的图表有什么意义呢?!然而,如果我们在上面的方程中使用绝对时间,我们将获得错误的通道。

所幸这种情况也不是没有解决的办法。如果我们将绝对时间更改为柱的相对编号,我们将能够使用那些坐标来绘制通道,因为柱的枚举不会有中断(实际上,它是价格数组的索引)。

如果我们进一步假设图 4 中的点 A 始终位于时间轴上的零坐标(零柱),则我们的方程将变得更为简单。这样一来, T1=0、T3=B3、T2=B2。此处的 B3 和 B2 是相对于点 T1(即零点)的柱的编号。显然,这一假设不会导致直线倾斜。接下来,我们获得通过点 A 和点 B 的直线的方程:

(2)   P(n) = P1 + n * (P2-P1) / B2,其中 P(n) 是计算得出的编号为 "n" 的柱的价格。


这样,我们就得出了 P1、P2、P3 和 B2、B3 的值。现在,我们需要算出 P1' 和 P2' 的值。 结合两个方程并求解,我们得出下面的公式,使用该公式我们可以得出未知的值:

(3)   P1' = P3 - B3 * (P2 - P1) / B2

(4)   P2' = P2 - P1 + P1'


我们算出 P1' 的值并将其代入公式 (4),我们将得到 P2' 的值。现在我们具备了绘制通道的理论基础。我们开始实施通道绘制。


绘制通道边界 - TChannelBorderObject 类

该类派生自标准类 CChartObjectTrend。其目的是存储所有与通道边界相关的参数,以及绘制/删除边界线和控制这些线的图形参数。

该类的描述如下所示:

class TChannelBorderObject : public CChartObjectTrend
{
//  General properties of a border:
private:
  bool             is_created;       // whether the graphical object is created on the screen
  long             chart_id;         // identifier of the chart window
  int              window;           // identifier of the subwindow

//  Parameters of a border line:
private:
  string           border_name;      // name of the border line
  color            border_color;     // color of the border line
  int              border_width;     // thickness of the border line
  ENUM_LINE_STYLE   border_style;     // style of the border line

//  Coordinates of a border:
private:
  datetime         point_left;       // time of the left point (T1)
  datetime         point_right;      // time of the right point (T2)
  double           price_left;       // price of the left point (P1)
  double           price_right;      // price of the right point (P2)

public:
  void     TChannelBorderObject();  // constructor
  void    ~TChannelBorderObject();  // destructor
  bool     IsCreated();             // check whether the line is created

//  Creating/deleting a line:
public:
  bool     CreateBorder(long _chart_id, int _window, string _name, datetime _t_left, datetime _t_right, 
                           double _p_left, double _p_right, color _color, int _width, ENUM_LINE_STYLE _style);
  bool     CreateBorder(long _chart_id, int _window, string _name, datetime _t_left, datetime _t_right, 
                           double _p_left, double _p_right);
  bool     CreateBorder(datetime _t_left, datetime _t_right, double _p_left, double _p_right);
  bool     RemoveBorder();          // delete line from the chart

//  Setting parameters of the line:
public:
  void     SetCommonParams(long _chart_id, int _window, string _name);
  bool     SetBorderParams(color _color, int _width, ENUM_LINE_STYLE _style);
  bool     SetBorderColor(color _color);
  bool     SetBorderWidth(int _width);
  bool     SetBorderStyle(ENUM_LINE_STYLE _style);

//  Getting values on the line:
  double   GetPrice(datetime _dt); // get price value in the specified position of the border line
};

我们无需对该类进行特别讨论。

只需将注意力放在获取指定点的边界价格的方法上:

//---------------------------------------------------------------------
//  Get price value in the specified position of the border line:
//---------------------------------------------------------------------
double TChannelBorderObject::GetPrice(datetime _dt)
{
//  If the graphical object is created:
  if(is_created == true)
  {
    return(ObjectGetValueByTime( chart_id, border_name, _dt));
  }
  return(0.0);
}

这里使用了终端的函数 ObjectGetValueByTime;它返回指定时间的价格值。我们可以方便地使用终端的可能性而不是数学公式来计算值。


绘制通道 - TSlideChannelObject 类

此类派生自标准类 CList。其用途如下:

  • 存储 TChannelBorderObject 类的对象并使用它们执行不同的操作;
  • 计算各点以绘制组成通道所需的直线;
  • 存储和修改通道的参数;
  • 获取描述绘制的通道的计算值(其高度、边界上的价格值等);

描述该类的代码太长,因此无法在此全部显示。有需要的读者可在本文随附的文件 SlideChannelClasses.mqh 中找到代码。我们来分析一下代码的主要部分。

首先,它分别获取点 T2 和 T3 的值 B2 和 B3(参见图 4)。使用的代码如下:

//  Get relative shifts in bars relatively to the extremum points:
  total_bars = Bars(symbol, time_frame);     // total number of bars in history
  if(total_bars == 0)
  {
    return(false);                           // channel cannot be drawn
  }
  double  B2 = Bars(symbol, time_frame, point_left, point_right);
  double  B3 = Bars(symbol, time_frame, point_left, point_middle);

为避免调用缺失的柱,我们使用终端函数Bars,它返回历史数据中指定交易品种和周期的柱的数目。如果信息尚未形成,函数将返回一个零值;它用于检查。

如果函数返回一个非零值,则我们可以得到 B2 值和 B3 值。这同样通过函数 Bars 完成,但以其他形式调用。我们设置时间限制,并获取此范围内柱的数目。由于我们的左边界相同,我们得到 T2 和 T3 点的以柱为单位的偏移。T1 点的偏移始终等于零。

现在,我们可以计算通道线所有的点。最多可以有九个点,因为我们的通道将显示(除上下边界外)中线和围绕边界和中间线的百分比区域的线。


我们来分析一下计算的主要部分。整个计算都在方法 TSlideChannelObject::CalcChannel 中。

//  Coefficient of the line inclination:
  koeff_A = (price_right - price_left) / B2;

//  Price value on the AB line in the point T3:
  double  P3_AB = price_left + B3 * koeff_A;

// Determine the channel type - 2MAX_1MIN или 1MAX_2MIN:
  if(P3_AB > price_middle)              // 2MAX_1MIN
  {
    channel_type = CHANNEL_2MAX_1MIN;

    left_prices[BORDER_UP_INDEX] = price_left;
    right_prices[BORDER_UP_INDEX] = price_right;
        
    left_prices[BORDER_DN_INDEX] = price_middle - B3 * koeff_A;
    right_prices[BORDER_DN_INDEX] = left_prices[BORDER_DN_INDEX] + (price_right - price_left);
  }
  else if(P3_AB < price_middle)         // 1MAX_2MIN
  {
    channel_type = CHANNEL_1MAX_2MIN;

    left_prices[BORDER_DN_INDEX] = price_left;
    right_prices[BORDER_DN_INDEX] = price_right;
        
    left_prices[BORDER_UP_INDEX] = price_middle - B3 * koeff_A;
    right_prices[BORDER_UP_INDEX] = left_prices[BORDER_UP_INDEX] + (price_right - price_left);
  }
  else
  {
    return( false );                      // channel cannot be drawn (all extremums are on the same line)
  }

此处的 left_pricesright_prices 是存储通道的九条线的价格坐标的数组。通道所有线条的时间坐标均为已知。

首先,确定直线的倾斜系数(请参见公式 (2)koeff_A。然后我们计算线条 AB 在 T3 点的价格值(请参加图 4)。这样是为了确定绘制所指定的通道类型 - 通过两个最大值和一个最小值,还是通过两个最小值和一个最大值。我们检查价格轴上哪个点更高 - C 点或坐标为 (P3', T3) 的点。我们根据它们的位置来确定通道是第一种还是第二种类型。

一旦通道的两条主线(上下边界)的坐标确定,其他七条线的坐标的计算就不再困难。例如,我们可以使用通道上下边界的坐标来通过以下方式计算中线的坐标:

  left_prices[BORDER_MD_INDEX] = (left_prices[BORDER_DN_INDEX] + left_prices[BORDER_UP_INDEX ]) / 2.0;
  right_prices[BORDER_MD_INDEX] = (right_prices[BORDER_DN_INDEX] + right_prices[BORDER_UP_INDEX]) / 2.0;

仅需取通道上下边界的平均值即可。


通过指定极值绘制通道的指标 - SlideChannel

好了,我们已经有了绘制通道的类。现在我们来编写一个指标,以从全局变量读取极值的参数和在图表上绘制通道。如下所示:

图 5. 使用极值绘制通道的示例

图 5. 使用极值绘制通道的示例

绘制的通道的相关信息同样在此显示 - 其宽度、当前价格到通道边界和中线的以点为单位的距离。

让我们连接所需的库:

#include  <TextDisplay.mqh>
#include  <SlideChannelClasses.mqh>

第一个库包含用于组织在屏幕上显示文本信息的类(请参见《利用标准库类创建您自己的“市场报价”》一文)。使用它,我们可显示临时极值点的值。

然后我们添加该指标的输入参数(这里只描述主要参数):

input string          PrefixString = "MainChannel";
//---------------------------------------------------------------------
input ENUM_TIMEFRAMES  ExtremumTimeFrame = PERIOD_CURRENT;
//---------------------------------------------------------------------
input bool            ShowInfo = true;

第一个参数 PrefixStringExtremumHandSet 指标中的一样,设置用于在读取极值时组成全局变量名称的前缀。它同样提供了在单个图表上使用多个这类指标的可能性。我们唯一要做的就是为它们设置不同的前缀。

ExtremumTimeFrame 参数设置将用于从全局变量读取极值点的时间表。它是一个非常有用的参数。它允许在不同的时间表上绘制同步通道。例如,如果您从 H1 设置极值,您可以在 M5 时间表上绘制相同的通道。为此,只需将我们绘制通道的指标添加至 M5 图表;然后它将同步显示所有变化。

参数 ShowInfo 控制有关通道参数的文本信息在屏幕上的显示。

接下来,创建显示信息和绘制通道的对象:

TableDisplay         ChannalDisplay;  // displaying of general information about a channel on the screen
TableDisplay         BordersDisplay;  // displaying information about the borders of a channel on the screen
//---------------------------------------------------------------------
TSlideChannelObject  Channel;         // drawing of a channel

绘制通道的对象通过以下的方式进行初始化:

  Channel.CreateChannel(PrefixString, 0, 0, Symbol(), period_current, curr_left_point, curr_middle_point, 
                        curr_right_point, curr_left_price, curr_middle_price, curr_right_price);
  Channel.SetBorderWidth(BorderWidth );
  Channel.SetmiddleWidth(middleLineWidth);
  Channel.SetUpBorderColor(UpBorderColor);
  Channel.SetDnBorderColor(DnBorderColor);
  Channel.SetmiddleColor(middleLineColor );
  Channel.ShowBorderZone(ShowBorderPercentageLines);
  Channel.BorderZonePercentage( PercentageZoneSize);
  Channel.Showmiddle(ShowmiddleLine);
  Channel.ShowmiddleZone( ShowmiddlePercentageLines);
  Channel.middleZonePercentage(PercentagemiddleZoneSize);

在这里,我们首先通过调用 TSlideChannelObject::CreateChannel 方法创建一个通道,然后设置通道线所需的参数。执行顺序无关紧要,反过来同样可行 - 先设置参数再创建通道。

参数 period_current 是从全局变量读取极值时使用的周期。它可能不同于当前图表的周期。

我们来看一下在指标中使用的主函数。第一个函数 GetExtremums 用于读取极值的位置和根据获得的值刷新通道:

void GetExtremums()
{
  double  temp;
  string  name;

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr1");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_left_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr2");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_middle_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr3");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_right_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_Price_Extr1");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_left_price = temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol( ), "_", period_current, "_Price_Extr2");
  if( GlobalVariableGet(name, temp) != false )
  {
    curr_middle_price = temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_Price_Extr3");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_right_price = temp;
  }

//  Update the position of channel:
  Channel.SetExtremums(curr_left_point, curr_middle_point, curr_right_point, 
                       curr_left_price, curr_middle_price, curr_right_price);
}

我们使用 TSlideChannelObject::SetExtremums 方法在屏幕上刷新通道。该方法重新计算通道线的坐标并在屏幕上重新绘制通道。

我们在下面的视频中给出了在不同时间表上绘制通道的示例:


指标的启动顺序并不重要,但首先启动 ExtremumHandSet 指标,然后添加三个黄色的左价格标签(标签的颜色在指标参数中设置,默认设置为黄色),最后启动通过指定的极值绘制通道的 SlideChannel 指标更符合逻辑顺序。

对于使用第一个图表的极值同步绘制的通道,您应该在 SlideChannel 指标的 ExtremumTimeFrame 参数中设置与极值设置所在图表中相同的时间表。

这是将设置通道极值点的函数和在终端屏幕上绘制通道的函数分离的结果。


总结

至此我们已讨论了从在屏幕上设置通道的位置到其绘制的整个过程。这一切看上去并不是那么复杂 - 尤其是在使用标准类和 OOP 时。

但还有一个问题:我们如何将通道付诸市场应用?首先,它们对于金融工具当前状态的技术分析是必要的。其次,在分析后,它们对于决策的作出是必要的。通道可提供很大的帮助。

可以开发一个半自动的“EA 交易”,它将分析通道的边界以建仓或平仓。它可以突破边界或从边界回滚。这将是下一篇文章的主题 -《使用通道的方法 - 回滚和突破》

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

附加的文件 |
extremumclasses.mqh (12.58 KB)
extremumhandset.mq5 (11.89 KB)
slidechannel.mq5 (13.7 KB)
textdisplay.mqh (15.21 KB)
使用计量经济学方法分析图表 使用计量经济学方法分析图表
本文介绍了用于分析、自相关分析尤其是条件方差分析的计量经济学方法。本文介绍的方法有何益处?使用非线性 GARCH 模型可以从数学角度正式表示分析序列并为指定步骤数建立预测。
编写"EA 交易"时,MQL5 标准交易类库的使用 编写"EA 交易"时,MQL5 标准交易类库的使用
本文阐述的是,在编写"EA 交易"的过程中,如何使用 MQL5 标准库交易类的主要功能,实现更改仓位与平仓、挂单的下达与删除,以及交易进行之前的预付款验证。我们还会演示可以如何使用交易类来获取订单与交易详情。
MQL5 向导:如何创建交易信号模块 MQL5 向导:如何创建交易信号模块
本文介绍了如何通过在穿越价格和移动平均线时构成信号这一方法来编写自己的交易信号类,以及如何将其包含在 MQL5 向导的交易策略生成器中,还介绍了用于 MQL5 向导的生成类的说明的结构和格式。
利用判别分析开发交易系统 利用判别分析开发交易系统
开发交易系统时,通常都会出现选择最佳指标与信号组合的问题。而判别分析就是找到此类组合的方法之一。本文会给出一个用于市场数据采集的 EA 开发示例,并详细阐明如何利用判别分析在 Statistica 软件中构建外汇市场预后模型。