English Русский Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
MQL5 Cookbook: 自定义信息面板上的仓位属性

MQL5 Cookbook: 自定义信息面板上的仓位属性

MetaTrader 5示例 | 18 十月 2013, 09:46
2 218 0
Anatoli Kazharski
Anatoli Kazharski

简介

这一次我们创建一个简单的EA交易,它可以取得当前交易品种的仓位属性并且在人工交易的时候在自定义信息面板上显示它们。信息面板将使用图形对象创建,显示的信息在每当有订单时都会刷新,这将比系列中的前一篇文章("MQL5 Cookbook: 获取仓位属性")中每次必须人工运行脚本要方便得多。

 

开发一个EA交易

让我们从图形对象开始,为了创建信息面板,我们需要用于背景,抬头,名称以及仓位属性值的对象。背景和抬头将需要一个不随价格移动的长方形,长方形可以通过长方形标签或者文本图形对象创建, 名字和对象属性值可以用文本标签来制作。

在我们继续编写代码之前,我们先要准备信息面板的布局,这样做的方便之处是我们可以通过在设置窗口中快速修改任何属性来自定义信息面板的外观。

每个对象都有一个设置窗口,可以通过选择对象的右键菜单中打开,设置窗口也可以从对象列表 (Ctrl+B)打开,您可以选择所需的对象并点击属性。信息面板的布局显示如下,它也可以用于在写代码的时候估算大小和坐标。当信息面板的代码完成以后,您需要人工删除这些布局对象,因为EA交易无法“看到”它们,也就无法从图表上删除它们。

图 1. 准备信息面板的布局.

图 1. 准备信息面板的布局.

现在我们需要为EA交易创建一个模板,这可以像创建脚本模板一样迅速。在MQL5向导中, Expert Advisor (template) 选项被默认选择,我们通过后面的步骤,不改变任何选项,因为此时不需要它们,然后点击“完成”,就看到了以下的模板: 

//+------------------------------------------------------------------+
//|                                      PositionPropertiesPanel.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| EA初始化函数                                                       |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| EA去初始化函数                                                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| EA订单函数                                                        |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

您可以马上发现,EA交易的模板和脚本的模板是不同的,除了程序属性(#property)之外,还有三个主要函数: OnInit(),OnDeinit() 和 OnTick()。

OnInit() 函数在程序载入时,修改外部参数,编译程序,增加程序到图表,以及改变交易品种或周期时都会被调用。如有必要, 您可以在这个函数中初始化某些变量或者数组,以便晚些时候可以用于操作。

OnDeinit()函数在您从图表上移除程序,改变账户、交易品种或者周期的时候会被调用。所有可能的去初始化原因在MQL5参考中提供,这个EA交易将使用一个用户定义的函数,GetDeinitReasonText(),来转换去初始化原因编号 (OnDeinit() 函数的参数)为文本。

最后, OnTick() 函数,它在EA交易正在操作图表的交易品种中每次有新订单的时候被调用。

让我们现在准备所有将要在EA交易中使用的常量,变量和数组,我们会把它们放置在程序的最开端。首先,定义整个程序范围内值不会改变的变量:

//---
#define INFOPANEL_SIZE 14 // 信息面板对象数组的大小
#define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME) // EA交易的名称
//---

之后是用于仓位属性的全局变量:

//--- 全局变量
bool                 pos_open=false;         // 有/无持仓的标志
string               pos_symbol="";          // 交易品种
long                 pos_magic=0;            // 幻数
string               pos_comment="";         // 注释
double               pos_swap=0.0;           // 库存费
double               pos_commission=0.0;     // 手续费
double               pos_price=0.0;          // 建仓价位
double               pos_cprice=0.0;         // 持仓的当前价位
double               pos_profit=0.0;         // 仓位的利润/亏损
double               pos_volume=0.0;         // 仓位交易量
double               pos_sl=0.0;             // 仓位的止损价格
double               pos_tp=0.0;             // 仓位的获利价格
datetime             pos_time=NULL;          // 建仓时间
long                 pos_id=0;               // 仓位编号
ENUM_POSITION_TYPE   pos_type=WRONG_VALUE;   // 仓位类型

在这些变量之后,我们将声明图形对象名称的数组,这些对象将在图表中显示仓位的属性和它们的值。为了这个目的,我们会创建两个字符串数组并且马上初始化其元素的值。在方括号中,我们使用在程序最开始声明的INFOPANEL_SIZE常量的值,也就是说,每个数组有14个元素。

// 显示仓位属性名称对象的名字的数组
string positionPropertyNames[INFOPANEL_SIZE]=
  {
   "name_pos_symbol",
   "name_pos_magic",
   "name_pos_comment",
   "name_pos_swap",
   "name_pos_commission",
   "name_pos_price",
   "name_pos_cprice",
   "name_pos_profit",
   "name_pos_volume",
   "name_pos_sl",
   "name_pos_tp",
   "name_pos_time",
   "name_pos_id",
   "name_pos_type"
  };
//---
// 显示仓位属性值对象的名字的数组
string positionPropertyValues[INFOPANEL_SIZE]=
  {
   "value_pos_symbol",
   "value_pos_magic",
   "value_pos_comment",
   "value_pos_swap",
   "value_pos_commission",
   "value_pos_price",
   "value_pos_cprice",
   "value_pos_profit",
   "value_pos_volume",
   "value_pos_sl",
   "value_pos_tp",
   "value_pos_time",
   "value_pos_id",
   "value_pos_type"
  };
//---

通过使用这些名字,您可以编程实现在图表中找到所需的对象或者修改它的属性,例如显示文本,颜色,大小等等,另外,在对象创建以后,这些名字也将显示在物件列表 (Ctrl+B) 窗口中。但是您无法看到它们,因为由MQL5程序创建的对象在默认条件下是隐藏的,要使它们可见,您应该在物件列表窗口中点击所有按钮,这个特性对区分通过人工创建和程序创建的对象很有帮助,也是很方便的。

下一步,我们将需要EA交易中用于创建图形对象的用户定义函数,MQL5所提供的用于创建图形对象的函数是ObjectCreate()。 但是因为我们也需要设置对象属性,而对象可能需要被多次创建,我们最好想一种更方便和简洁的方法来使用一行代码实现它。

为了创建面板背景和抬头,我们将使用文本图形对象。让我们写下CreateEdit() 函数:

//+------------------------------------------------------------------+
//| 创建文本对象                                                       |
//+------------------------------------------------------------------+
void CreateEdit(long             chart_id,         // 图表编号
                int              sub_window,       // (子)窗口编号
                string           name,             // 对象名称
                string           text,             // 显示的文本
                ENUM_BASE_CORNER corner,           // 图表拐角
                string           font_name,        // 字体名称
                int              font_size,        // 字体大小
                color            font_color,       // 字体颜色
                int              x_size,           // 宽度
                int              y_size,           // 高度
                int              x_distance,       // 横坐标
                int              y_distance,       // 纵坐标
                long             z_order,          // 深度顺序
                color            background_color, // 背景颜色
                bool             read_only)        // 只读标志
  {
// 如果对象被成功创建,...
   if(ObjectCreate(chart_id,name,OBJ_EDIT,sub_window,0,0))
     {
      // ...设置其属性
      ObjectSetString(chart_id,name,OBJPROP_TEXT,text);                 // 显示的文本
      ObjectSetInteger(chart_id,name,OBJPROP_CORNER,corner);            // 设置图表拐角
      ObjectSetString(chart_id,name,OBJPROP_FONT,font_name);            // 设置字体名称
      ObjectSetInteger(chart_id,name,OBJPROP_FONTSIZE,font_size);       // 设置字体大小
      ObjectSetInteger(chart_id,name,OBJPROP_COLOR,font_color);         // 字体颜色
      ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,background_color); // 背景颜色
      ObjectSetInteger(chart_id,name,OBJPROP_XSIZE,x_size);             // 宽度
      ObjectSetInteger(chart_id,name,OBJPROP_YSIZE,y_size);             // 高度
      ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x_distance);     // 设置横坐标
      ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y_distance);     // 设置纵坐标
      ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);         // 如果设为FALSE则不能选择
      ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,z_order);           // 对象深度顺序
      ObjectSetInteger(chart_id,name,OBJPROP_READONLY,read_only);       // 只读
      ObjectSetInteger(chart_id,name,OBJPROP_ALIGN,ALIGN_LEFT);         // 左对齐
      ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,"\n");              // 如果设为"\n"说明没有Tooltip提示
     }
  }

现在,文本图形对象(OBJ_EDIT)可以使用一行代码就能创建好了,在创建设置图表信息面板函数的时候,我们将以它为例,

现在让我们继续看文本标签对象,该对象将用于显示一系列仓位属性和它们的值,创建CreateLabel() 函数和之前是很接近的:

//+------------------------------------------------------------------+
//| 创建文本标签                                                       |
//+------------------------------------------------------------------+
void CreateLabel(long               chart_id,   // 图表编号
                 int                sub_window, // (子)窗口编号
                 string             name,       // 对象名称
                 string             text,       // 显示的文本
                 ENUM_ANCHOR_POINT  anchor,     // 锚点
                 ENUM_BASE_CORNER   corner,     // 图表拐角
                 string             font_name,  // 字体名称
                 int                font_size,  // 字体大小
                 color              font_color, // 字体颜色
                 int                x_distance, // 横坐标
                 int                y_distance, // 纵坐标
                 long               z_order)    // 深度顺序
  {
// 如果对象被成功创建,...
   if(ObjectCreate(chart_id,name,OBJ_LABEL,sub_window,0,0))
     {
      // ...设置其属性
      ObjectSetString(chart_id,name,OBJPROP_TEXT,text);              // 显示的文本
      ObjectSetString(chart_id,name,OBJPROP_FONT,font_name);         // 设置字体名称
      ObjectSetInteger(chart_id,name,OBJPROP_COLOR,font_color);      // 设置字体颜色
      ObjectSetInteger(chart_id,name,OBJPROP_ANCHOR,anchor);         // 设置锚点
      ObjectSetInteger(chart_id,name,OBJPROP_CORNER,corner);         // 设置图表拐角
      ObjectSetInteger(chart_id,name,OBJPROP_FONTSIZE,font_size);    // 设置字体大小
      ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x_distance);  // 设置横坐标
      ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y_distance);  // 设置纵坐标
      ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);      // 如果设为 FALSE 则无法选择对象
      ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,z_order);        // 对象的深度顺序
      ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,"\n");           // 如果设为"\n"则不显示提示信息
     }
  }

我们同样建议查阅一下MQL5 参考中这些函数的描述。

当EA交易被从图表上移除的时候,它必须按顺序删除所有之前它添加到图表上的对象,为了做到这一点,您可以简单地把对象名称传入DeleteObjectByName()函数,它会根据名称搜索对象,如果找到了,则删除它,使用内建的ObjectFind() 函数可以搜索对象,而ObjectDelete() 函数可以删除对象。

//+------------------------------------------------------------------+
//| 根据名称删除对象                                                   |
//+------------------------------------------------------------------+
void DeleteObjectByname(string name)
  {
   int  sub_window=0;      // 返回对象所在子窗口的编号
   bool res       =false;  // 尝试删除对象的结果
//--- 根据名称寻找对象
   sub_window=ObjectFind(ChartID(),name);
//---
   if(sub_window>=0) // 如果找到了,..
     {
      res=ObjectDelete(ChartID(),name); // ...删除它
      //---
      // 如果删除对象过程中出错,..
      if(!res) // ...打印相关信息
        {
         Print("删除对象出错: ("+IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError()));
        }
     }
  }

另外,在DeleteObjectByName() 函数中,我们也实现了对删除对象出错的检查,如果有错误发生,包含错误代码和描述的相关信息将会出现。从以上代码中您可以看到,我们使用了一个额外的用户定义函数把错误代码转换为文字描述 - 即ErrorDescription() 函数。因为有很多错误代码,我只是举例使用函数中的一部分 (参见以下代码),完整的代码可以在文章附件的源文件中找到。

//+------------------------------------------------------------------+
//| 返回错误描述                                                       |
//+------------------------------------------------------------------+
string ErrorDescription(int error_code)
  {
   string error_string="";
//---
   switch(error_code)
     {
      //--- 交易服务器返回代码

      case 10004: error_string="重新报价";                                                                break;
      case 10006: error_string="请求被拒绝";                                                       break;
      case 10007: error_string="交易者取消请求";                                             break;
      case 10008: error_string="已经下单";                                                           break;
      case 10009: error_string="请求被执行";                                                       break;
      case 10010: error_string="请求被部分执行";                                             break;
      case 10011: error_string="请求处理错误";                                               break;
      case 10012: error_string="请求超时";                                                      break;
      case 10013: error_string="请求无效";                                                        break;
      case 10014: error_string="无效请求交易量";                                                 break;
      case 10015: error_string="无效请求价位";                                                  break;
      case 10016: error_string="无效请求止损";                                     break;
      case 10017: error_string="交易被禁止";                                                      break;
      case 10018: error_string="市场已关闭";                                                       break;
      case 10019: error_string="资金不足";                                                     break;
      case 10020: error_string="价格已改变";                                                         break;
      case 10021: error_string="没有用于处理请求的报价";                                       break;
      case 10022: error_string="订单过期时间无效";                                break;
      case 10023: error_string="订单状态改变";                                                   break;
      case 10024: error_string="请求过多";                                                      break;
      case 10025: error_string="请求没有变化";                                              break;
      case 10026: error_string="自动化交易被交易者禁用";                                break;
      case 10027: error_string="自动化交易被客户终端禁用";                   break;
      case 10028: error_string="请求处理被阻止";                                         break;
      case 10029: error_string="订单或仓位被冻结";                                               break;
      case 10030: error_string="根据结余,指定的订单执行类型不支持";      break;
      case 10031: error_string="和交易服务器没有连接";                                        break;
      case 10032: error_string="事务仅在活动帐户允许";                          break;
      case 10033: error_string="您的挂单数已经达到上限";                  break;
      case 10034: error_string="您在此交易品种的订单或仓位交易量已经达到上限"; break;

      ...

     }
//---
   return(error_string);
  }

前文中, 我们使用GetPositionProperties() 函数来获取仓位属性,这一次,函数结构要更加复杂一些,我们将会检查一个当前开启的仓位,仓位是否存在的标志则使用一个全局变量pos_open来保存,这个信息可能在其他函数中也需要,我们就不需要每一次都调用PositionSelect() 函数了。

然后,如果有持仓,我们就读取它的属性,否则全部变量都清为零。现在让我们写一个简单的ZeroPositionProperties() 函数:

//+------------------------------------------------------------------+
//| 把仓位属性变量清零                                                  |
//+------------------------------------------------------------------+
void ZeroPositionProperties()
  {
   pos_symbol     ="";
   pos_comment    ="";
   pos_magic      =0;
   pos_price      =0.0;
   pos_cprice     =0.0;
   pos_sl         =0.0;
   pos_tp         =0.0;
   pos_type       =WRONG_VALUE;
   pos_volume     =0.0;
   pos_commission =0.0;
   pos_swap       =0.0;
   pos_profit     =0.0;
   pos_time       =NULL;
   pos_id         =0;
  }

进一步,在GetPositionProperties() 函数的最后,我们会调用一个用户定义的SetInfoPanel() 函数用来画/更新图表上的信息面板。

//+------------------------------------------------------------------+
//| 取得仓位属性                                                       |
//+------------------------------------------------------------------+
void GetPositionProperties()
  {
// 检查是否有持仓
   pos_open=PositionSelect(_Symbol);
//---
   if(pos_open) // 如果有持仓,则读取它的属性
     {
      pos_symbol     =PositionGetString(POSITION_SYMBOL);
      pos_comment    =PositionGetString(POSITION_COMMENT);
      pos_magic      =PositionGetInteger(POSITION_MAGIC);
      pos_price      =PositionGetDouble(POSITION_PRICE_OPEN);
      pos_cprice     =PositionGetDouble(POSITION_PRICE_CURRENT);
      pos_sl         =PositionGetDouble(POSITION_SL);
      pos_tp         =PositionGetDouble(POSITION_TP);
      pos_type       =(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      pos_volume     =PositionGetDouble(POSITION_VOLUME);
      pos_commission =PositionGetDouble(POSITION_COMMISSION);
      pos_swap       =PositionGetDouble(POSITION_SWAP);
      pos_profit     =PositionGetDouble(POSITION_PROFIT);
      pos_time       =(datetime)PositionGetInteger(POSITION_TIME);
      pos_id         =PositionGetInteger(POSITION_IDENTIFIER);
     }
   else // 如果没有持仓,则把属性变量清零
      ZeroPositionProperties();
//---
   SetInfoPanel(); // 设置/更新信息面板
  }

现在让我们写 SetInfoPanel() 函数。以下是带有详细注释的函数代码:

//+------------------------------------------------------------------+
//| 设置信息面板                                                       |
//|------------------------------------------------------------------+
void SetInfoPanel()
  {
   int               y_bg=18;             // 背景和抬头的纵坐标
   int               y_property=32;       // 属性列表和它们数值的纵坐标
   int               line_height=12;      // 行高度
//---
   int               font_size=8;         // 字体大小
   string            font_name="Calibri"; // 字体名称
   color             font_color=clrWhite; // 字体颜色
//---
   ENUM_ANCHOR_POINT anchor=ANCHOR_RIGHT_UPPER; // 锚点位于右上角
   ENUM_BASE_CORNER  corner=CORNER_RIGHT_UPPER; // 图表的右上角为坐标起点
//--- 横坐标
   int               x_first_column=120// 第一列 (属性名称)
   int               x_second_column=10;  // 第二列 (属性值)
//--- 仓位属性名称及其数值的纵坐标数组
   int               y_prop_array[INFOPANEL_SIZE]={0};
//--- 使用信息面板中每一行的坐标填充数组
   y_prop_array[0]=y_property;
   y_prop_array[1]=y_property+line_height;
   y_prop_array[2]=y_property+line_height*2;
   y_prop_array[3]=y_property+line_height*3;
   y_prop_array[4]=y_property+line_height*4;
   y_prop_array[5]=y_property+line_height*5;
   y_prop_array[6]=y_property+line_height*6;
   y_prop_array[7]=y_property+line_height*7;
   y_prop_array[8]=y_property+line_height*8;
   y_prop_array[9]=y_property+line_height*9;
   y_prop_array[10]=y_property+line_height*10;
   y_prop_array[11]=y_property+line_height*11;
   y_prop_array[12]=y_property+line_height*12;
   y_prop_array[13]=y_property+line_height*13;
//--- 信息面板背景
   CreateEdit(0,0,"InfoPanelBackground","",corner,font_name,8,clrWhite,230,190,231,y_bg,0,C'15,15,15',true);
//--- 信息面板抬头
   CreateEdit(0,0,"InfoPanelHeader","POSITION PROPERTIES",corner,font_name,8,clrWhite,230,14,231,y_bg,1,clrFireBrick,true);
//--- 仓位属性名称和数值的列表
//    属性名称
   CreateLabel(0,0,pos_prop_names[0],"交易品种 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[0],2);
//    属性值
   CreateLabel(0,0,pos_prop_values[0],GetValInfoPanel(0),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[0],2);
//---
   CreateLabel(0,0,pos_prop_names[1],"幻数:",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[1],2);
   CreateLabel(0,0,pos_prop_values[1],GetValInfoPanel(1),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[1],2);
//---
   CreateLabel(0,0,pos_prop_names[2],"注释 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[2],2);
   CreateLabel(0,0,pos_prop_values[2],GetValInfoPanel(2),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[2],2);
//---
   CreateLabel(0,0,pos_prop_names[3],"库存费 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[3],2);
   CreateLabel(0,0,pos_prop_values[3],GetValInfoPanel(3),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[3],2);
//---
   CreateLabel(0,0,pos_prop_names[4],"手续费 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[4],2);
   CreateLabel(0,0,pos_prop_values[4],GetValInfoPanel(4),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[4],2);
//---
   CreateLabel(0,0,pos_prop_names[5],"建仓价位 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[5],2);
   CreateLabel(0,0,pos_prop_values[5],GetValInfoPanel(5),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[5],2);
//---
   CreateLabel(0,0,pos_prop_names[6],"当前价位 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[6],2);
   CreateLabel(0,0,pos_prop_values[6],GetValInfoPanel(6),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[6],2);
//---
   CreateLabel(0,0,pos_prop_names[7],"利润 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[7],2);
   CreateLabel(0,0,pos_prop_values[7],GetValInfoPanel(7),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[7],2);
//---
   CreateLabel(0,0,pos_prop_names[8],"交易量 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[8],2);
   CreateLabel(0,0,pos_prop_values[8],GetValInfoPanel(8),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[8],2);
//---
   CreateLabel(0,0,pos_prop_names[9],"止损价位 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[9],2);
   CreateLabel(0,0,pos_prop_values[9],GetValInfoPanel(9),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[9],2);
//---
   CreateLabel(0,0,pos_prop_names[10],"获利价位 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[10],2);
   CreateLabel(0,0,pos_prop_values[10],GetValInfoPanel(10),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[10],2);
//---
   CreateLabel(0,0,pos_prop_names[11],"时间 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[11],2);
   CreateLabel(0,0,pos_prop_values[11],GetValInfoPanel(11),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[11],2);
//---
   CreateLabel(0,0,pos_prop_names[12],"编号 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[12],2);
   CreateLabel(0,0,pos_prop_values[12],GetValInfoPanel(12),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[12],2);
//---
   CreateLabel(0,0,pos_prop_names[13],"类型 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[13],2);
   CreateLabel(0,0,pos_prop_values[13],GetValInfoPanel(13),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[13],2);
//---
   ChartRedraw(); // 重画图表
  }

让我们仔细看一下SetInfoPanel()函数,和图形对象属性相关的变量(坐标,颜色,字体,显示的文本,等等)在函数的开头部分声明,请注意填充信息面板中仓位属性列表的纵坐标到数组的过程,它是以对初学者来说很清楚的方式来实现的,但是它可以通过使用循环来减少一些代码行数,你可以写成下面这样:

//--- 使用信息面板中每一行的坐标填充数组
   for(int i=0; i<INFOPANEL_SIZE; i++)
     {
      if(i==0) y_prop_array[i]=y_property;
      else     y_prop_array[i]=y_property+line_height*i;
     }

然后,面板中需要显示的对象的所有属性必须使用之前以CreateLabel() 和 CreateEdit() 函数创建的参数指定,一次设定一个对象。整个列表也可以使用循环以几行代码实现,为了做到这点,我们需要为这些在图表中显示为文字的仓位属性名称对象再创建一个数组,这就做你的家庭作业吧。

GetPropertyValue() 函数的参数是对象编号,它的返回值传给CreateLabel() 函数作为第四个参数 (显示文本),这关系到所有用于显示仓位属性数值的对象,这个函数返回的是一个调整过的字符串值,将最终显示在面板上。以下是带有详细注释的函数代码:

//+------------------------------------------------------------------+
//| 返回仓位属性值的字符串                                              |
//+------------------------------------------------------------------+
string GetPropertyValue(int number)
  {
//--- 没有持仓或某属性的符号
//    比如没有注释,止损价位或者获利价位
   string empty="-";
//--- 如果有持仓,返回所请求属性的值
   if(pos_open)
     {
      switch(number)
        {
         case 0  : return(pos_symbol);                                           break;
         case 1  : return(IntegerToString((int)pos_magic));                      break;
         //--- 如有注释返回注释的值,否则返回没有注释的符号
         case 2  : return(pos_comment!="" ? pos_comment : empty);                break;
         case 3  : return(DoubleToString(pos_swap,2));                           break;
         case 4  : return(DoubleToString(pos_commission,2));                     break;
         case 5  : return(DoubleToString(pos_price,_Digits));                    break;
         case 6  : return(DoubleToString(pos_cprice,_Digits));                   break;
         case 7  : return(DoubleToString(pos_profit,2));                         break;
         case 8  : return(DoubleToString(pos_volume,2));                         break;
         case 9  : return(pos_sl!=0.0 ? DoubleToString(pos_sl,_Digits) : empty); break;
         case 10 : return(pos_tp!=0.0 ? DoubleToString(pos_tp,_Digits) : empty); break;
         case 11 : return(TimeToString(pos_time,TIME_DATE|TIME_MINUTES));        break;
         case 12 : return(IntegerToString((int)pos_id));                         break;
         case 13 : return(PositionTypeToString(pos_type));                       break;
         
         default : return(empty);
        }
     }
//---
//如果没有持仓,返回缺少持仓的符号 "-"
   return(empty);
  }

以上代码表明,有持仓的时候,传入函数的每个数字都有值,而如果当前没有持仓,这个函数会针对所有需要处理仓位属性值的对象返回一个横线(-)。

SetInfoPanel() 函数的最后,我们调用ChartRedraw() 函数设计为强制图表重绘,除非它被调用,否则您无法看到改变发生。

现在我们需要写一个函数来删除所有EA交易创建的图形对象,让我们称它为 DeleteInfoPanel():

//+------------------------------------------------------------------+
//| 删除信息面板                                                       |
//+------------------------------------------------------------------+
void DeleteInfoPanel()
  {
   DeleteObjectByName("InfoPanelBackground");   // 删除面板背景
   DeleteObjectByName("InfoPanelHeader");       // 删除面板抬头
//--- 删除仓位属性及其值
   for(int i=0; i<INFOPANEL_SIZE; i++)
     {
      DeleteObjectByName(pos_prop_names[i]);    // 删除属性
      DeleteObjectByName(pos_prop_values[i]);   // 删除值
     }
//---
   ChartRedraw(); // 重画图表
  }

现在我们只需要把我们创建的方法发布到EA交易的主函数中,它们最初是出现在MQL5向导创建的模板中的。这是最简单的部分:

//+------------------------------------------------------------------+
//| EA初始化函数                                                       |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 读取属性并设置面板
   GetPositionProperties();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| EA去初始化函数                                                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 在日志中打印去初始化原因
   Print(GetDeinitReasonText(reason));
//--- 当从图表上移除时
   if(reason==REASON_REMOVE)
      //--- 从图表上删除所有有关信息面板的对象
      DeleteInfoPanel();

  }
//+------------------------------------------------------------------+
//| EA订单函数                                                        |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 读取属性并更新面板上的数值
   GetPositionProperties();

  }
//+------------------------------------------------------------------+

唯一您可能会顿一下的是GetDeinitReasonText() 函数,它返回去初始化原因的文本描述:

//+---------------------------------------------------------------------+
//| 返回去初始化原因代码的文本描述                                           |
//+---------------------------------------------------------------------+
string GetDeinitReasonText(int reason_code)
  {
   string text="";
//---
   switch(reason_code)
     {
      case REASON_PROGRAM :     // 0
         text="EA 交易通过调用 ExpertRemove()函数停止工作.";   break;
      case REASON_REMOVE :      // 1
         text="'"+EXPERT_NAME+"' 程序已被从图表上移除.";                break;
      case REASON_RECOMPILE :   // 2
         text="'"+EXPERT_NAME+"' 程序已经被重新编译.";                            break;
      case REASON_CHARTCHANGE : // 3
         text="图表的交易品种或周期已经改变.";                                      break;
      case REASON_CHARTCLOSE :  // 4
         text="图表被关闭.";                                                          break;
      case REASON_PARAMETERS :  // 5
         text="输入参数被用户改变.";                               break;
      case REASON_ACCOUNT :     // 6
         text="激活了一个不同的账户.";                                       break;
      case REASON_TEMPLATE :    // 7
         text="应用了一个不同的图表模板.";                                  break;
      case REASON_INITFAILED :  // 8
         text="有标志表明OnInit() 处理函数返回零值.";              break;
      case REASON_CLOSE :       // 9
         text="终端被关闭.";                                                 break;
      default : text="原因不明.";
     }
//---
   return text;
  }

如果您在当前没有持仓的交易品种图表上使用这个EA交易,您将会在面板上看到横线而不是仓位属性值,当您平掉某仓位时,面板看起来也会一样。

图 2. 没有持仓的信息面板.

图 2. 没有持仓的信息面板.

如果EA交易被添加到有持仓或者在添加EA交易后有新建仓位,所有横线会被对应的仓位属性值代替:

图 3. 信息面板显示持仓属性

图 3. 信息面板显示持仓属性

还有一点小小的特殊情况,平仓以后,直到有新的订单后面板上的数值才会更新,有一种方法可以使数值立即更新,但是所需要的实现会在本系列的下一篇文章中讨论。

 

结论

本文中介绍的一些函数也会在MQL5 Cookbook 系列中后面的文章中使用,另外一些会根据手边的任务进行修改或提高,我建议按照顺序阅读这些文章,一篇接另一篇,因为每篇新文章和之前的文章有逻辑上的联系,当然这也依赖于您的能力和技术,所以也许从更新发布的文章开始会更加合理和有趣。 

源文件在文章的附件中。

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

附加的文件 |
MQL5 Cookbook: 在MetaTrader 5策略测试器中分析仓位属性 MQL5 Cookbook: 在MetaTrader 5策略测试器中分析仓位属性
我们将会展示一个来自前一篇文章,"MQL5 Cookbook: 自定义信息面板上的仓位属性"的修改版的EA交易。我们将会解决一些问题,包括从柱中获得数据,在当前交易品种中检查新柱事件,在文件中包含标准库中的交易类,创建一个函数来搜索交易信号,还有一个执行交易操作的函数以及在OnTrade()函数中判断交易事件。
MQL5 Cookbook: 获取仓位属性 MQL5 Cookbook: 获取仓位属性
在本文中,我们将创建一个脚本来获得所有的仓位属性,并用对话框向用户显示它们。通过运行这个脚本,您可以从外部参数下拉列表的两种模式中选择:只看当前交易品种的仓位属性,或者查看所有交易品种的属性。
MQL5 Cookbook: 怎样在设置/修改交易参数时避免错误 MQL5 Cookbook: 怎样在设置/修改交易参数时避免错误
作为我们在系列前一篇文章,"MQL Cookbook: 在MetaTrader 5策略测试器中分析仓位属性"中EA交易工作的继续,我们将使用很多有用的函数,以及提高和优化已有的函数来增强它。这一次EA交易有可以在MetaTrader 5策略测试器中优化的外部参数,并且在某些方面组成了一个简单的交易系统。
MQL5 Cookbook: 使用不同的打印模式 MQL5 Cookbook: 使用不同的打印模式
这是 MQL5 Cookbook 系列的第一篇文章,我将会从简单的实例开始,让那些刚刚开始编程的人逐渐熟悉这门新语言。我还记得我开始设计和编写交易系统时的尝试,可以说是非常困难,事实上那是我所学的第一门编程语言,然而,后来还是比我想象的简单一些,我只用了几个月的时间就能够开发相对复杂的程序了。