下载MetaTrader 5

面向对象法建立多时间表及多货币面板

6 十一月 2013, 12:34
Marcin Konieczny
0
912

简介

本文讲述如何利用面向对象编程创建MetaTrader 5多时间表与多货币面板。主要目标在于建立一个可用于显示多种不同类型数据(比如价格、价格变动、指标值或自定义买/卖条件)、且无需修改面板本身代码的通用面板。通过这种途径,不管以任何方式来定制我们需要的面板,所需的代码都非常少。

我将讲到的解决方案有两种工作模式:

  1. 多时间表模式 - 允许查看基于当前交易品种、但基于不同的时间表而计算得出的表内容;
  2. 多货币模式 - 允许查看基于当前时间表、但基于不同的交易品种而计算得出的表内容;

下方图片显示的就是上述两种模式下的面板。

第一个工作于多时间表模式下,显示下述数据:

  1. 时价;
  2. 当前柱的价格变化;
  3. 以百分比表示的当前柱的价格变化;
  4. 以(上/下)箭头表示的当前柱的价格变化;
  5. RSI(14) 指标值;
  6. RSI(10) 指标值;
  7. 自定义条件:SMA(20) > 时价。

图 1. 多时间表模式

图 1. 多时间表模式

第二个工作于多货币模式下并显示:

  1. 时价;
  2. 当前柱的价格变化;
  3. 以百分比表示的当前柱的价格变化;
  4. 以箭头表示的当前柱的价格变化;
  5. RSI(10) 指标值;
  6. RSI(14) 指标值;
  7. 自定义条件:SMA(20) > 时价。

图 2. 多货币模式

图 2. 多货币模式


1. 实现

下述类图描述出了此面板的实现设计。

图 3. 面板的类图

图 3. 面板的类图


我来介绍一下此图的组成元素:

  1. CTable。此面板的核心类。它负责绘制面板并管理其组件。
  2. SpyAgent。 它是一个负责“监视”其它交易品种(工具)的指标。创建的每一个代理都会发往不同的交易品种。此代理会在有新订单号抵达交易品种图表时对 OnCalculate 事件做出反应,并发送 CHARTEVENT_CUSTOM 事件以通知 CTable 对象其须更新。此方法背后的整体理念基于 “MetaTrader 5 中多货币模式的实现"一文。您可以在此文中找到所有技术详情。
  3. CRow。用于创建此面板的所有指标及条件的基类。通过扩展此类,即可能创建出此面板所有必需的组件。
  4. CPriceRow。CRow 的一个简单扩展类,用于显示当前买价。
  5. CPriceChangeRow。CRow 扩展类,用于显示当前柱的价格变化。它可以显示价格变化、百分比变化或箭头。
  6. CRSIRow。CRow 扩展类,用于显示当前的 RSI 指标 值。
  7. CPriceMARow。CRow 扩展类,显示某种自定义条件:SMA > 时价。

CTable、CRow 类以及 SpyAgent 指标都是此面板的核心组成部分。而 CPriceRow、CPriceChangeRow、CRSIRow 以及 CPriceMARow 则是此面板的实际内容。CRow 类的设计主旨,即在于由众多的新类扩展,从而实现想要的结果。所示的 4 个衍生类只是简单的例子,告诉大家能做些什么、怎样做。

2. SpyAgent

我们从 SpyAgent 指标开始。它仅于多货币模式下使用,而且如有新订单号抵达其它图表,必须用它才能妥善更新此面板。对此概念,我不想过多深入。“MetaTrader 5 中多货币模式的实现”一文中有所详述。

SpyAgent 指标于指定交易品种的图表上运行,并发送两个事件:初始化事件与新订单号事件。两个事件均属 CHARTEVENT_CUSTOM 类型。想要处理上述事件,我们必须使用 OnChartEvent(...) 处理程序(稍后呈现)。

我们来看看 SpyAgent 的代码:

//+------------------------------------------------------------------
//|                                                     SpyAgent.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------
#property copyright "Marcin Konieczny"
#property indicator_chart_window
#property indicator_plots 0

input long   chart_id=0;        // 图表识别符
input ushort custom_event_id=0; // 事件识别符
//+------------------------------------------------------------------
//| 指标迭代函数                                     |
//+------------------------------------------------------------------
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {

   if(prev_calculated==0)
      EventChartCustom(chart_id,0,0,0.0,_Symbol); // 发送初始事件
   else
      EventChartCustom(chart_id,(ushort)(custom_event_id+1),0,0.0,_Symbol); // 发送新即时价格事件

   return(rates_total);
  }

非常简单。它只做一件事,那就是接收新数据并发送 CHARTEVENT_CUSTOM 事件。


3. CTable

CTable 是此面板的核心类。它会存储面板设置的相关信息并管理其组件。必要时,它会更新(重绘)面板。

我们看看 CTable 的声明:

//+------------------------------------------------------------------
//| CTable 类                                                     |
//+------------------------------------------------------------------
class CTable
  {
private:
   int               xDistance;    // 图表右边框的距离
   int               yDistance;    // 图表顶边框的距离
   int               cellHeight;   // 表格单元高度
   int               cellWidth;    // 表格单元宽度
   string            font;         // 字体名
   int               fontSize;
   color             fontColor;

   CList            *rowList;      // 行对象列表
   bool              tfMode;       // 是多时间帧模式吗?

   ENUM_TIMEFRAMES   timeframes[]; // 多时间帧模式数组
   string            symbols[];    // 多货币对模式数组

   //--- 私有方法
   //--- 设置表格默认参数
   void              Init();
   //--- 在指定表格单元内画文本标签
   void              DrawLabel(int x,int y,string text,string font,color col);
   //--- 返回给定的时间帧的文字表述
   string            PeriodToString(ENUM_TIMEFRAMES period);

public:
   //--- 多时间帧模式构造函数
                     CTable(ENUM_TIMEFRAMES &tfs[]);
   //--- 多货币对模式构造函数
                     CTable(string &symb[]);
   //--- 析构函数
                    ~CTable();
   //--- 重画表格
   void              Update();
   //--- 设置表格参数函数
   void              SetDistance(int xDist,int yDist);
   void              SetCellSize(int cellW,int cellH);
   void              SetFont(string fnt,int size,color clr);
   //--- 附加CRow对象至表格
   void              AddRow(CRow *row);
  };

正如您所见,所有的面板组件(行)均存储为一个 CRow 指针列表,因此,我们要向面板添加的每个组件,都必须扩展 CRow 类。CRow 可被视为此面板与其组件之间的一个协议。CTable 并不包含供其单元格计算的任何代码。那是 CRow 扩展类的责任。CTable 只是保留 CRow 组件并于必要时重绘它们的一种结构。

我们从头到尾看一看 CTable 的方法。此类拥有两个构造函数。第一个用于多时间表模式且非常简单。我们只需要提供一个想要显示的时间表数组。

//+------------------------------------------------------------------
//| 多时间帧模式构造函数                                 |
//+------------------------------------------------------------------
CTable::CTable(ENUM_TIMEFRAMES &tfs[])
  {
//--- 复制所有时间帧至数组
   ArrayResize(timeframes,ArraySize(tfs),0);
   ArrayCopy(timeframes,tfs);
   tfMode=true;
   
//--- 用当前图表交易品种填充数组
   ArrayResize(symbols,ArraySize(tfs),0);
   for(int i=0; i<ArraySize(tfs); i++)
      symbols[i]=Symbol();

//--- 设置默认参数
   Init();
  }

第二个构造函数用于多货币模式,并接受交易品种(工具)数组。此构造函数亦发送 SpyAgents。它会将 SpyAgents 一个一个地附至适当的图表。

//+------------------------------------------------------------------
//| 对货币对模式构造函数                                  |
//+------------------------------------------------------------------
CTable::CTable(string &symb[])
  {
//--- 复制所有交易品种至自己的数组
   ArrayResize(symbols,ArraySize(symb),0);
   ArrayCopy(symbols,symb);
   tfMode=false;
   
//--- 用当前时间帧填充数组
   ArrayResize(timeframes,ArraySize(symb),0);
   ArrayInitialize(timeframes,Period());

//--- 设置默认参数
   Init();

//--- 发送 SpyAgents 至每个请求交易品种
   for(int x=0; x<ArraySize(symbols); x++)
      if(symbols[x]!=Symbol()) // 不要发送 SpyAgent 至自己的图表
         if(iCustom(symbols[x],0,"SpyAgent",ChartID(),0)==INVALID_HANDLE)
           {
            Print("设置 SpyAgent 错误于 "+symbols[x]);
            return;
           }
  }

Init 方法会创建行列表(作为一个 CList 对象 - CList 是一种 CObject 类型的动态列表)并设置 CTable 内部变量(字体、字号、颜色、单元格尺寸以及与图表右上角的距离)的默认值。

//+------------------------------------------------------------------
//| 设置表格的默认参数                             |
//+------------------------------------------------------------------
CTable::Init()
  {
//--- 创建保存行对象列表
   rowList=new CList;

//--- 设置默认值
   xDistance = 10;
   yDistance = 10;
   cellWidth = 60;
   cellHeight= 20;
   font="Arial";
   fontSize=10;
   fontColor=clrWhite;
  }


析构函数相当简单了。它会删除行列表,并删除由此面板创建的所有图表对象(标签)。

//+------------------------------------------------------------------
//| 析构函数                                                       |
//+------------------------------------------------------------------
CTable::~CTable()
  {
   int total=ObjectsTotal(0);

//--- 从图表中删除所有文本标签 (所有对象名都以 nameBase前缀开始)
   for(int i=total-1; i>=0; i--)
      if(StringFind(ObjectName(0,i),nameBase)!=-1)
         ObjectDelete(0,ObjectName(0,i));

//--- 删除列表行并释放内存
   delete(rowList);
  }

AddRow 方法会将新行附加至行列表。请注意:rowList 是一个 CList 对象,可自动调整大小。此方法每次添加 CRow 对象时也会调用 Init 方法。想让对象妥善完成其内部变量的初始化,它是不可或缺的。比如说,它可以使用初始化调用创建指标或文件处理函数。

//+------------------------------------------------------------------
//| 在表格末尾插入新行                          |
//+------------------------------------------------------------------
CTable::AddRow(CRow *row)
  {
   rowList.Add(row);
   row.Init(symbols,timeframes);
  }

Update 方法稍微复杂一些。它用于重绘面板。

基本上,它由下述三部分构成:

  • 绘制第一列(行名称)
  • 绘制第一行(根据选定模式,可能是时间表或交易品种的名称)
  • 绘制内部单元格(组件值)

我们没有必要要求每个组件都根据提供的交易品种和时间表来计算其值。我们也让组件决定采用哪种字体和颜色。

//+------------------------------------------------------------------
//| 重画表格                                                |
//+------------------------------------------------------------------
CTable::Update()
  {
   CRow *row;
   string symbol;
   ENUM_TIMEFRAMES tf;

   int rows=rowList.Total(); // 行数量
   int columns;              // 列数量

   if(tfMode)
      columns=ArraySize(timeframes);
   else
      columns=ArraySize(symbols);

//--- 画第一列 (行名称)
   for(int y=0; y<rows; y++)
     {
      row=(CRow*)rowList.GetNodeAtIndex(y);
      //--- 注: 从行对象返回它自己的名称
      DrawLabel(columns,y+1,row.GetName(),font,fontColor);
     }

//--- 画第一行 (时间帧名或货币对名)
   for(int x=0; x<columns; x++)
     {
      if(tfMode)
         DrawLabel(columns-x-1,0,PeriodToString(timeframes[x]),font,fontColor);
      else
         DrawLabel(columns-x-1,0,symbols[x],font,fontColor);
     }

//--- 画内部表格单元
   for(int y=0; y<rows; y++)
      for(int x=0; x<columns; x++)
        {
         row=(CRow*)rowList.GetNodeAtIndex(y);

         if(tfMode)
           {
            //--- 在多时间帧模式使用当前交易品种及不同的时间帧
            tf=timeframes[x];
            symbol=_Symbol;
           }
         else
           {
            //--- 在多货币模式使用当前时间帧及不同的交易品种
            tf=Period();
            symbol=symbols[x];
           }

         //--- 注: 从行对象返回它自己的字体, 
         //--- 颜色和给定时间帧和交易品种的当前计算值
         DrawLabel(columns-x-1,y+1,row.GetValue(symbol,tf),row.GetFont(symbol,tf),row.GetColor(symbol,tf));
        }

//--- 强制重绘图表
   ChartRedraw();
  }

DrawLabel 方法用于在此面板的指定单元中绘制文本标签。首先,它会检查此单元的标签是否已存在。如果没有,则创建一个新的。

然后,它会设置所有必要的标签属性及其文本。

//+------------------------------------------------------------------
//| 在表格的指定单元格中绘制文本标签              |
//+------------------------------------------------------------------  
CTable::DrawLabel(int x,int y,string text,string font,color col)
  {
//--- 创建唯一单元名
   string name=nameBase+IntegerToString(x)+":"+IntegerToString(y);

//--- 创建标签
   if(ObjectFind(0,name)<0)
      ObjectCreate(0,name,OBJ_LABEL,0,0,0);

//--- 设置标签属性
   ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xDistance+x*cellWidth);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,yDistance+y*cellHeight);
   ObjectSetString(0,name,OBJPROP_FONT,font);
   ObjectSetInteger(0,name,OBJPROP_COLOR,col);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontSize);

//--- 设置标签文本
   ObjectSetString(0,name,OBJPROP_TEXT,text);
  }

其它方法不再于此赘述,因为那些都非常简单,而且不太重要。本文末尾处有完整代码可供下载。


4.扩展 CRow

CRow 是针对此面板使用的所有组件的一个基类。

我们看看 CRow 类的代码:

//+------------------------------------------------------------------
//| CRow类                                                       |
//+------------------------------------------------------------------
//| 创建自定义表格行的基本类                        |
//| 一个或多个CRow函数应被重写                  |
//| 当创建自己的表格行时                                     |
//+------------------------------------------------------------------
class CRow : public CObject
  {
public:
   //--- 默认初始化方法
   virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { }

   //--- 默认方法获得字符串值及显示表格单元
   virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf) { return("-"); }

   //--- 默认方法获得表格单元颜色值
   virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf) { return(clrWhite); }
   
   //--- 默认方法获得行名
   virtual string GetName() { return("-"); }

   //--- 默认方法获得表格单元字体值
   virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf) { return("Arial"); }
  };

它会扩展为 CObject,因为只有 CObjects 可存储于 CList 结构当中。它包含 5 种近空方法。更确切地说,其中的大多数都只返回默认值。上述方法均为扩展 CRow 时被覆写而设计。我们无需覆写其全部,而只是想要的部分。

作为示例,我们创建一个最简单的面板组件 - 当前买价组件。该组件可用于多货币模式下,显示各种工具的当前价格。

为此,我们如下创建一个 CPriceRow 类:

//+------------------------------------------------------------------
//| CPriceRow类                                                  |
//+------------------------------------------------------------------
class CPriceRow : public CRow
  {
public:
   //--- 重写 CRow 的默认 GetValue(..) 方法
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- 重写 CRow 的默认 GetName() 方法
   virtual string    GetName();

  };
//+------------------------------------------------------------------
//| 重写CRow的默认GetValue(..)方法                  |
//+------------------------------------------------------------------
string CPriceRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   MqlTick tick;

//--- 取当前价格
   if(!SymbolInfoTick(symbol,tick)) return("-");

   return(DoubleToString(tick.bid,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  }
//+------------------------------------------------------------------
//| 重写CRow的默认GetName()方法                     |
//+------------------------------------------------------------------
string CPriceRow::GetName()
  {
   return("Price");
  }

我们此处选择重写的方法是 GetValue 和 GetName。GetName 只会返回此行的名称,该名称将显示于此面板的第一栏中。GetValue 会于指定的交易品种获取最新订单号,并返回最新的买价。我们需要的就是这些。

真是非常简单。我们再做做别的。我们现在要构建一个显示当前 RSI 值的组件。

代码与之前的代码类似:

//+------------------------------------------------------------------
//| CRSIRow类                                                    |
//+------------------------------------------------------------------
class CRSIRow : public CRow
  {
private:
   int               rsiPeriod;        // RSI 周期
   string            symbols[];        // 交易品种数组
   ENUM_TIMEFRAMES   timeframes[];     // 时间帧数组
   int               handles[];        // RSI 句柄数组

   //--- 寻找给定交易品种及时间帧的指标句柄
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- 构造函数
                     CRSIRow(int period);

   //--- 重写 CRow 的默认 GetValue(..) 方法
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- 重写 CRow 的默认 GetName() 方法
   virtual string    GetName();

   //--- 重写 CRow 的默认 Init(..) 方法
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------
//| 构造函数                                                      |
//+------------------------------------------------------------------
CRSIRow::CRSIRow(int period)
  {
   rsiPeriod=period;
  }
//+------------------------------------------------------------------
//| 重写CRow的默认Init(..)方法                      |
//+------------------------------------------------------------------
void CRSIRow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
//--- 复制数组内容至它自己的数组
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- 取得所有交易品种或时间帧的 RSI 句柄
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iRSI(symbols[i],timeframes[i],rsiPeriod,PRICE_CLOSE);
  }
//+------------------------------------------------------------------
//| 重写CRow的默认GetValue(..)方法                  |
//+------------------------------------------------------------------
string CRSIRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];

//--- 获取 RSI 指标句柄
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- 取得当前 RSI 数值
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");

   return(DoubleToString(value[0],2));
  }
//+------------------------------------------------------------------
//| 重写CRow的默认GetName()方法                     |
//+------------------------------------------------------------------
string CRSIRow::GetName()
  {
   return("RSI("+IntegerToString(rsiPeriod)+")");
  }
//+------------------------------------------------------------------
//| 找出给定交易品种和时间帧的指标句柄      |
//+------------------------------------------------------------------
int CRSIRow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

我们还有几种新方法。构造函数允许提供 RSI 时期并将其存储为一个成员变量。使用 Init 方法创建 RSI 指标处理函数。这些处理函数均存储于 handles[] 数组中。GetValue 方法会从RSI 缓冲区复制最新值并将其返回。Private GetHandle 用于在 handles[] 数组中查找合适的指标。GetName 则即如其名。

正如我们所见,构建面板组件非常容易。我们可以相同方式,为几乎任何自定义条件创建组件。未必一定是指标值。下面,我会介绍一种基于 SMA 的自定义条件。它会检查当前价格是否超过了移动平均线,并显示 'Yes' (是)或 'No'(否)。

//+------------------------------------------------------------------
//| CPriceMARow类                                                |
//+------------------------------------------------------------------
class CPriceMARow : public CRow
  {
private:
   int               maPeriod; // 移动平均周期
   int               maShift;  // 移动平均平移
   ENUM_MA_METHOD    maType;   // SMA, EMA, SMMA 或 LWMA
   string            symbols[];        // 交易品种数组
   ENUM_TIMEFRAMES   timeframes[];     // 时间帧数组
   int               handles[];        // MA 句柄数组

   //--- 寻找给定交易品种及时间帧的指标句柄
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- 构造函数
                     CPriceMARow(ENUM_MA_METHOD type,int period,int shift);

   //--- 重写 CRow 的默认 GetValue(..) 方法
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   // 重写 CRow 的默认 GetName() 方法
   virtual string    GetName();

   //--- 重写 CRow 的默认 Init(..) 方法
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------
//| CPriceMARow类的构造函数                                    |
//+------------------------------------------------------------------
CPriceMARow::CPriceMARow(ENUM_MA_METHOD type,int period,int shift)
  {
   maPeriod= period;
   maShift = shift;
   maType=type;
  }
//+------------------------------------------------------------------
//| 重写 CRow 的默认Init(..) 方法                      |
//+------------------------------------------------------------------
void CPriceMARow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
//--- 复制数组内容至它自己的数组
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- 取得所有交易品种或时间帧的 MA 句柄
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iMA(symbols[i],timeframes[i],maPeriod,maShift,maType,PRICE_CLOSE);
  }
//+------------------------------------------------------------------
//| 重写 CRow 的默认GetValue(..)方法                    |
//+------------------------------------------------------------------
string CPriceMARow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];
   MqlTick tick;

//--- 获取 MA 指标句柄
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- 取最后 MA 数值
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");
//--- 取最后价格
   if(!SymbolInfoTick(symbol,tick)) return("-");

//--- 检查当前条件: 价格 > MA
   if(tick.bid>value[0])
      return("Yes");
   else
      return("No");
  }
//+------------------------------------------------------------------
//| 重写 CRow 的默认GetName()方法                       |
//+------------------------------------------------------------------
string CPriceMARow::GetName()
  {
   string name;

   switch(maType)
     {
      case MODE_SMA: name = "SMA"; break;
      case MODE_EMA: name = "EMA"; break;
      case MODE_SMMA: name = "SMMA"; break;
      case MODE_LWMA: name = "LWMA"; break;
     }

   return("Price>"+name+"("+IntegerToString(maPeriod)+")");
  }
//+------------------------------------------------------------------
//| 寻找给定交易品种及时间帧的指标句柄;     |
//+------------------------------------------------------------------
int CPriceMARow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

这个代码较长,因为移动平均线有三个参数:period(周期)、shift(转移) 和 type(类型)。GetName 稍微复杂一些,因其基于移动平均线类型与时期建立名称。GetValue 的作用方式与 CRSIRow 的情况几乎完全相同,只是如果价格超过了简单移动平均线,则会返回 'Yes' 而不是返回指标值;如在简单移动平均线以下,则返回 'No'。

最后一个示例有一点难。即显示当前柱价格变化的 CPriceChangeRow 类。它于三种模式下工作:

  • 显示箭头(绿上或红下);
  • 价格变化作为一个值显示(绿色或红色);
  • 价格变化作为一个百分比显示(绿色或红色)、

代码如下:

//+------------------------------------------------------------------
//| CPriceChangeRow类                                            |
//+------------------------------------------------------------------
class CPriceChangeRow : public CRow
  {
private:
   bool              percentChange;
   bool              useArrows;

public:
   //--- 构造函数
                     CPriceChangeRow(bool arrows,bool percent=false);

   //--- 重写 CRow 的默认GetName() 方法
   virtual string    GetName();

   //--- 重写 CRow 的默认GetFont() 方法
   virtual string    GetFont(string symbol,ENUM_TIMEFRAMES tf);

   //--- 重写 CRow 的默认GetValue(..) 方法
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- 重写 CRow 的默认GetColor(..) 方法
   virtual color     GetColor(string symbol,ENUM_TIMEFRAMES tf);

  };
//+------------------------------------------------------------------
//| CPriceChangeRow 类的构造函数                                |
//+------------------------------------------------------------------
CPriceChangeRow::CPriceChangeRow(bool arrows,bool percent=false)
  {
   percentChange=percent;
   useArrows=arrows;
  }
//+------------------------------------------------------------------
//| 重写 CRow 的默认GetName()方法                     |
//+------------------------------------------------------------------
 string CPriceChangeRow::GetName()
  {
   return("PriceChg");
  }
//+------------------------------------------------------------------
//| 重写 CRow 的默认GetFont()方法                     |
//+------------------------------------------------------------------
string CPriceChangeRow::GetFont(string symbol,ENUM_TIMEFRAMES tf)
  {
//--- 我们使用 Wingdings 字体画箭头 (上/下)
   if(useArrows)
      return("Wingdings");
   else
      return("Arial");
  }
//+------------------------------------------------------------------
//| 重写 CRow 的默认GetValue(..)方法                  |
//+------------------------------------------------------------------
string CPriceChangeRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- 取当前柱线的开盘价和收盘价
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(" ");
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(" ");

//--- 当前柱线的价格改变
   double change=close[0]-open[0];

   if(useArrows)
     {
      if(change > 0) return(CharToString(233)); // 返回上箭头代码
      if(change < 0) return(CharToString(234)); // 返回下箭头代码
      return(" ");
        }else{
      if(percentChange)
        {
         //--- 计算百分比变化
         return(DoubleToString(change/open[0]*100.0,3)+"%");
           }else{
         return(DoubleToString(change,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
        }
     }
  }
//+------------------------------------------------------------------
//| 重写 CRow 的默认GetColor(..) 方法                  |
//+------------------------------------------------------------------
color CPriceChangeRow::GetColor(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- 取当前柱线的开盘价和收盘价
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(clrWhite);
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(clrWhite);

   if(close[0] > open[0]) return(clrLime);
   if(close[0] < open[0]) return(clrRed);
   return(clrWhite);
  }

此构造函数有两个参数。第一个决定是否显示箭头。如果是 true,则丢弃第二个参数。如是 false,则第二个参数决定是显示百分比变化还是单纯的价格变化。

针对此类,我决定重写 CRow 的四种方法:GetName、GetValue、GetColor 和 GetFont。GetName 最简单,只返回名称。使用 GetFont,因为它会赋予显示箭头或源于 Wingdings 字体其它字符的能力。GetColor 会在价格上升时显示石灰色,下降时显示红色。保持不变或出错时,则返回白色。GetValue 会获取最新柱的开盘价与收盘价,计算差异并将其返回。在箭头模式下,它会返回上下箭头的 Wingdings 字符代码。


5. 如何整体运用

想要使用此面板,我们需要创建一个新指标。我们称其为 TableSample。

需要处理的事件有:

我们还需要一个指向 CTable 对象的指针,该指针将于 OnInit() 中动态创建。首先,我们必须决定使用哪种模式(多时间表还是多货币)。下述代码示例呈现的是多货币模式,但多时间表模式所需的一切内容也都存在于此处的注释中。针对多货币模式,我们需要创建一个交易品种数组,并将其传递给 CTable 构造函数。针对多时间表模式,我们需要创建一个时间表数组,并将其传递给第二 CTable 构造函数。

此后,我们就要创建所有必需的组件,并利用 AddRow 方法将其添加到此面板。面板参数可以选择性调整。我们毕竟还是第一次绘制面板,所以我们在 OnInit() 的末尾调用 Update。OnDeinit 很简单。它所做的唯一一件事情就是删除 CTable 对象,从而导致 CTable 析构函数被调用。

OnCalculate(...)OnChartEvent(...) 完全相同。它们只会调用Update方法。只有在面板处于多货币模式下时,才有必要使用OnChartEvent(...)。在此模式下,它会处理由SpyAgents产生的事件。而在多时间表模式下,则只需要 OnCalculate(...),因为我们只需监控当前图表的交易品种。

//+------------------------------------------------------------------
//|                                                  TableSample.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------
#property copyright "Marcin Konieczny"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0

#include <Table.mqh>
#include <PriceRow.mqh>
#include <PriceChangeRow.mqh>
#include <RSIRow.mqh>
#include <PriceMARow.mqh>

CTable *table; // CTable对象指针
//+------------------------------------------------------------------
//| 指标初始化函数                                |
//+------------------------------------------------------------------
int OnInit()
  {
//--- 表格使用的时间帧 (在多时间帧模式)
   ENUM_TIMEFRAMES timeframes[4]={PERIOD_M1,PERIOD_H1,PERIOD_D1,PERIOD_W1};

//--- 表格使用的交易品种(在多货币模式)
   string symbols[4]={"EURUSD","GBPUSD","USDJPY","AUDCHF" };
//-- CTable 对象创建 
//   table = new CTable(timeframes); // 多时间帧模式
   table=new CTable(symbols); // 多货币对模式

//--- 表格中加入行
   table.AddRow(new CPriceRow());               // 显示当前价格
   table.AddRow(new CPriceChangeRow(false));     // 显示最后柱线的价格变化
   table.AddRow(new CPriceChangeRow(false,true)); // 显示最后柱线的价格百分比
   table.AddRow(new CPriceChangeRow(true));      // 显示价格改变的箭头
   table.AddRow(new CRSIRow(14));                // 显示 RSI(14)
   table.AddRow(new CRSIRow(10));                // 显示 RSI(10)
   table.AddRow(new CPriceMARow(MODE_SMA,20,0));  // 显示如果 SMA(20) > 当前价格

//--- 设置表格参数
   table.SetFont("Arial",10,clrYellow);  // 字体, 大小, 颜色
   table.SetCellSize(60, 20);           // 宽度, 高度
   table.SetDistance(10, 10);           // 图表右上角的距离

   table.Update(); // 强制重绘图表

   return(0);
  }
//+------------------------------------------------------------------
//| 指标去初始化函数                              |
//+------------------------------------------------------------------
void OnDeinit(const int reason)
  {
//--- 调用析构函数并释放内存
   delete(table);
  }
//+------------------------------------------------------------------
//| 指标迭代函数                                     |
//+------------------------------------------------------------------
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//--- 更新表格: 重算/重绘
   table.Update();
   return(rates_total);
  }
//+------------------------------------------------------------------
//| OnChartEvent处理函数                                             |
//|处理SpyAgent指标发送的CHARTEVENT_CUSTOM事件     |
//| 只在多货币对模式!                              |
//+------------------------------------------------------------------
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   table.Update(); // 更新表格: 重算/重绘
  }
//+------------------------------------------------------------------

将此指标附至图表后,它就会开始更新,然后,我们就会看到面板终于工作了。


6. 安装

所有文件均需要编译。SpyAgent 和 TableSample 指标均应复制到 terminal_data_folder\MQL5\Indicators。其余文件为包含文件,应置于 terminal_data_folder\MQL5\Include 内。想要运行面板,则将 TableSample 指标附至任意图表。无需随附 SpyAgent 的指标。它们会自动启动。


总结

本文讲述了 MetaTrader 5 多时间表与多货币面板的一种面向对象实现法。展示了如何完成一项易于扩展、且允许轻松构建自定义面板的设计。

本文中展示的所有代码均可于下方下载。

由MetaQuotes Software Corp.从英文翻译成
原始文章: https://www.mql5.com/en/articles/357

附加的文件 |
面向对象编程基础 面向对象编程基础

您无需了解什么是多态性、什么是封装性,以及使用面向对象编程(OOP)相关的一切内容……您可能只需要使用这些功能就好了。本文中涵盖了 OOP 的基础知识,且带有亲身实践示例。

利用MQL5创建您自己的图形面板 利用MQL5创建您自己的图形面板

MQL5程序的可用性,是由其丰富的功能性和制作精细的图形用户界面所决定的。视觉感知有时比快速且稳定的运行更加重要。根据标准库类,您可以自行创建显示面板,以下即逐步操作指南。

6 步创建您自己的交易机器人! 6 步创建您自己的交易机器人!

如果您不清楚交易类如何构造,而且一看到面向对象编程之类的词就害怕,那么,本文正适合您。实际上,那些编写您自己的交易信号模块的细节,您无需知道。只需遵循几条简单法则即可。MQL5 向导会完成所有其余工作,而您则会得到一个即用型的交易机器人!

MQL5 Cookbook: 使用不限数量的参数开发多币种EA交易 MQL5 Cookbook: 使用不限数量的参数开发多币种EA交易

在本文中,我们将创建一种模式,它会使用一系列参数为交易系统作优化,而且允许不加数量限制的参数。交易品种的列表将在标准文本文件(*.txt)中创建,每个交易品种的输入参数也将存储于文件中。使用这种方法,我们将能够免除终端中对EA输入参数个数的限制。