向指标或者EA中快速添加控制面板

Vladimir Karputov | 28 三月, 2016

使用图形面板

你的MQL4/MQL5指标或EA可能是世界上最有效的但是它仍旧有改进的空间。在大多数情况下,你需要进入程序设置来改变其输入参数。然而,这一步可以绕过去。

基于标准类库来开发你自己的控制面板。这将允许您更改设置而无需重新启动程序。此外,这将使你的程序更具吸引力,让它从竞争对手中脱颖而出。您可以在市场中浏览多种图形面板。

在本文中,我将向你展示如何向您的MQL4/MQL5程序添加简易面板。您还将了解到如何让程序读取输入参数并对它们的改变进行响应。

 

1. 将指标和面板结合起来


1.1. 指标

NewBar.mq5指标执行一个单一的操作。当新的柱形来到时在终端的EA日志中打印一条消息。指标代码如下:

//+------------------------------------------------------------------+
//|                                                       NewBar.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property description "The indicator identifies a new bar"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                               
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 指标缓存映射

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 自定义指标迭代函数                                                  
//+------------------------------------------------------------------+
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[])
  {
   static datetime prev_time;
//--- 转置time[]数组的访问顺序 - 就像在时间系列中的一样 
   ArraySetAsSeries(time,true);
//--- 首次计算或者已经改变的柱形数量
   if(prev_calculated==0)// 首次计算
     {
      prev_time=time[0];
      return(rates_total);
     }
//---
   if(time[0]>prev_time)
      Print("New bar!");
//---
   prev_time=time[0];
//---返回prev_calculated的值用于下次调用
   return(rates_total);
  }
//+------------------------------------------------------------------+

现在我们深入研究NewBar.mq5运作的一些细节。

prev_time static今天变量在OnCalculate()函数中声明。此变量保存time[0]开始时间。下一步,将time[0]开始时间和prev_time变量进行比较。换句话说,当前tick的time[0]开始时间和前一个tick的开始时间相比较。如果下述条件满足:

if(time[0]>prev_time)

那么认为这是一个新的柱形。

下面的例子详细显示了NewBar.mq5是如何检测新的柱形到来的:

新柱形

图 1. 在指标中检测新的柱形

让我们考虑非常平静市场环境下的10个tick。

Ticks 1-3:索引为0的柱形的开始时间(time[0])等于存储在prev_time静态变量中的时间,意味着没有新的柱形来到。

Tick 4:新柱形的tick到来了。当进入OnCalculate()函数中,time[0]的柱形开始时间为(2015.12.01 00:02:00),而 prev_time变量仍旧存储着前一个tick所在柱形的开始时间(2015.12.01 00:01:00)。因此,当time[0]>prev_time条件满足时,我们检测到新的柱形到来了。在退出OnCalculate()之前,prev_time变量从time[0] (2015.12.01 00:02:00)中获得新的值。

Ticks 5-8:索引为0的柱形的开始时间(time[0])同存储在prev_time静态变量中的相等,也就是说不是新的柱形。

Tick 9:新柱形的tick到来了。当进入OnCalculate()函数时,time[0]的值为柱形开始时间(2015.12.01 00:03:00),而 prev_time变量仍旧存储着前一个tick所在柱形的开始时间(2015.12.01 00:02:00)。因此,当time[0]>prev_time条件满足时,我们检测到新的柱形到来了。在退出OnCalculate()之前,prev_time变量被赋值为time[0] (2015.12.01 00:03:00)。

Tick 10:索引为0的柱形的开始时间(time[0])同存储在prev_time静态变量中的相等,也就是说不是新的柱形


1.2. 面板

所有面板的绘图参数(数量,尺寸以及控件元素的坐标)都汇聚在一个单一的include文件 PanelDialog.mqh中,它是一个面板实现类。

面板如下:

面板

图 2. 面板

PanelDialog.mqh包含文件的代码如下:

//+------------------------------------------------------------------+
//|                                                  PanelDialog.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <Controls\Dialog.mqh>
#include <Controls\CheckGroup.mqh>
//+------------------------------------------------------------------+
//| 定义                                       
//+------------------------------------------------------------------+
//--- 缩进和间隔
#define INDENT_LEFT                         (11)      // 左间距(留出边界宽度)
#define INDENT_TOP                          (11)      // 顶间距(留出边界宽度)
#define INDENT_BOTTOM                       (11)      // 上边距(留出边界宽度)
//--- 按钮
#define BUTTON_WIDTH                        (100)     // X坐标的尺寸
//+------------------------------------------------------------------+
//| CControlsDialog 类                  
//| 用法:控件应用的主对话框         
//+------------------------------------------------------------------+
class CControlsDialog : public CAppDialog
  {
private:
   CCheckGroup       m_check_group;                   // CCheckGroup对象

public:
                     CControlsDialog(void);
                    ~CControlsDialog(void);
   //--- 创建
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- 图表事件处理函数
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

protected:
   //--- 创建独立控件
   bool              CreateCheckGroup(void);
   //--- 独立控件事件处理函数
   void              OnChangeCheckGroup(void);
  };
//+------------------------------------------------------------------+
//| 事件处理                                                      
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CControlsDialog)
ON_EVENT(ON_CHANGE,m_check_group,OnChangeCheckGroup)
EVENT_MAP_END(CAppDialog)
//+------------------------------------------------------------------+
//| 构造函数                                                      
//+------------------------------------------------------------------+
CControlsDialog::CControlsDialog(void)
  {
  }
//+------------------------------------------------------------------+
//| 析构函数                                                       
//+------------------------------------------------------------------+
CControlsDialog::~CControlsDialog(void)
  {
  }
//+------------------------------------------------------------------+
//| 创建                               
//+------------------------------------------------------------------+
bool CControlsDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
//--- 创建独立控件
   if(!CreateCheckGroup())
      return(false);
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建 "CheckGroup" 元素
//+------------------------------------------------------------------+
bool CControlsDialog::CreateCheckGroup(void)
  {
//--- 坐标
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP;
   int x2=x1+BUTTON_WIDTH;
   int y2=ClientAreaHeight()-INDENT_BOTTOM;
//--- 创建
   if(!m_check_group.Create(m_chart_id,m_name+"CheckGroup",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!Add(m_check_group))
      return(false);
   m_check_group.Alignment(WND_ALIGN_HEIGHT,0,y1,0,INDENT_BOTTOM);
//--- 用字符填充
   if(!m_check_group.AddItem("Mail",1<<0))
      return(false);
   if(!m_check_group.AddItem("Push",1<<1))
      return(false);
   if(!m_check_group.AddItem("Alert",1<<2))
      return(false);
   Comment(__FUNCTION__+" : Value="+IntegerToString(m_check_group.Value()));
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 事件处理                                                
//+------------------------------------------------------------------+
void CControlsDialog::OnChangeCheckGroup(void)
  {
   Comment(__FUNCTION__+" : Value="+IntegerToString(m_check_group.Value()));
  }
//+------------------------------------------------------------------+

正如你所看到的,我们的面板类不包含设置和读取独立固定开关状态的方法。

我们的目标是将NewBar.mq5作为主文件,添加输入参数,例如,能够选择新柱形出现时的报警方法(MailPush,或Alert)。另外,PanelDialog.mqh包含文件要含有用于设置和读取MailPush,或 Alert独立固定开关状态的方法。


1.3. 修改指标

注意:所有做出的修改都用颜色标记了。

首先,我们要实现PanelDialog.mqh包含文件:

#property indicator_chart_window
#property indicator_plots 0
#include "PanelDialog.mqh"
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                               
//+------------------------------------------------------------------+
int OnInit()

然后添加输入参数:

#property indicator_chart_window
#property indicator_plots 0
#include "PanelDialog.mqh"
//--- 输入参数
input bool     bln_mail=false;      // 通过email通知
input bool     bln_push=false;      // 通过短信push通知
input bool     bln_alert=true;      // 通过alert通知
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                               
//+------------------------------------------------------------------+
int OnInit()

编译指标(MetaEditor中按F7)并确定终端中输入参数显示正常:

输入参数

图 3. 指标输入参数


1.4. 修改面板

现在,我们要添加用于设置和读取独立固定开关状态的MailPushAlert方法到面板中。

让我们向面板类中添加新方法:

class CControlsDialog : public CAppDialog
  {
private:
   CCheckGroup       m_check_group;                   // CCheckGroup对象

public:
                     CControlsDialog(void);
                    ~CControlsDialog(void);
   //--- 创建
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- 图表事件处理函数
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- 元素的SetCheck()函数
   virtual bool      SetCheck(const int idx,const int value);
   //--- 元素的GetCheck()函数
   virtual int       GetCheck(const int idx) const;

protected:
   //--- 创建独立控件
   bool              CreateCheckGroup(void);

实现方法:

//+------------------------------------------------------------------+
//| 为元素设置检查
//+------------------------------------------------------------------+
bool CControlsDialog::SetCheck(const int idx,const bool check)
  {
   return(m_check_group.Check(idx,check));
  }
//+------------------------------------------------------------------+
//| 获取元素的检查结果
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)
  {
   return(m_check_group.Check(idx));
  }


1.5. 将指标和面板结合的最后一步

NewBar.mq5指标全局变量声明模块中声明面版类的变量

#property indicator_chart_window
#property indicator_plots 0
#include "PanelDialog.mqh"
//+------------------------------------------------------------------+
//| 全局变量                                                            |
//+------------------------------------------------------------------+
CControlsDialog ExtDialog;
//--- 输入参数
input bool     bln_mail=false;      // 通过email通知
input bool     bln_push=false;      // 通过短信push通知
input bool     bln_alert=true;      // 通过alert通知

在最后添加OnChartEvent()函数:

//+------------------------------------------------------------------+
//| ChartEvent函数                                                    |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }

NewBar.mq5指标的OnInit()函数中创建面板,程序根据输入参数选择选项框:

int OnInit()
  {
//--- 指标缓存映射
//--- 创建程序对话框
   if(!ExtDialog.Create(0,"Notification",0,50,50,180,160))
      return(INIT_FAILED);
//--- 运行程序
   if(!ExtDialog.Run())
      return(INIT_FAILED);
//---
   ExtDialog.SetCheck(0,bln_mail);
   ExtDialog.SetCheck(1,bln_push);
   ExtDialog.SetCheck(2,bln_alert);
//---
   return(INIT_SUCCEEDED);
  }

这样我们就将指标和面板结合起来了。我们已经实现了确定一个选项框状态的方法 – 选中/释放(SetCheck),以及接收它的方法 (GetCheck)。

 

2. 将EA和面板结合


2.1. EA

让我们使用标准的EA...\MQL5\Experts\Examples\MACD\MACD Sample.mq5,作为样例。


2.2. 面板

最终的PanelDialog2.mqh面板看上去如下:

面板2

图 4. 面板2

MACD Sample.mq5EA和PanelDialog2.mqh面板结合的好处是什么?这允许我们快速的修改EA参数(LotsTrailing Stop Level (in pips),及其他),以及加载在当前时间框架上的EA的事件发生通知方式(Mail, Push, 和 Alert)。

被修改EA的参数(LotsTrailing Stop Level (in pips),及其他)在点击Apply changes按钮后生效。 交易事件通知设置的改变(MailPush,和Alert)自动生效。没有必要按Apply changes按钮。


2.3. EA和面板应该有交互

EA和面板之间的交互

图. 5. EA和面板之间的交互

加载后,EA应该将它的参数传递给面板。在点击Apply changes按钮并改变其参数之后,面板应向EA返回改变后的参数,并用新参数进行初始化。


2.4. 第一步。修改EA

将标准样例EA ...\MQL5\Experts\Examples\MACD\MACD Sample.mq5复制到你的文件夹中。例如,你可以创建Notification文件夹并将EA复制到其中:

创建一个新的文件夹

图. 6. 创建一个新的文件夹


EA的全局变量区域(不要和终端全局变量混淆),声明定义发送EA交易活动通知方法的新变量。请注意这些变量具有Inp前缀,就像其他外部变量一样:

#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
//--- 输入参数
input bool     InpMail=false;          //通过email进行通知
input bool     InpPush=false;          // 通过短信push通知
input bool     InpAlert=true;          // 通过alert通知
//---
input double InpLots          =0.1; // 交易量
input int    InpTakeProfit    =50;  // 止赢(点数)

添加下面所有EA的外部变量的副本。副本前缀Ext

input int    InpMACDCloseLevel=2;   // MACD水平(以点数计)
input int    InpMATrendPeriod =26;  // MA周期
//--- ext 变量
bool           ExtMail;
bool           ExtPush;
bool           ExtAlert;

double         ExtLots;
int            ExtTakeProfit;
int            ExtTrailingStop;
int            ExtMACDOpenLevel;
int            ExtMACDCloseLevel;
int            ExtMATrendPeriod;
//---
int ExtTimeOut=10; // 交易操作之间的间隔时间(秒)
//+------------------------------------------------------------------+
//| MACD 样例EA类
//+------------------------------------------------------------------+

使用OnInit()来复制外部变量:

//| EA初始化函数                                                 
//+------------------------------------------------------------------+
int OnInit(void)
  {
   ExtMail=InpMail;
   ExtPush=InpPush;
   ExtAlert=InpAlert;

   ExtLots=InpLots;
   ExtTakeProfit=InpTakeProfit;
   ExtTrailingStop=InpTrailingStop;
   ExtMACDOpenLevel=InpMACDOpenLevel;
   ExtMACDCloseLevel=InpMACDCloseLevel;
   ExtMATrendPeriod=InpMATrendPeriod;
//--- 创建所有需要的对象
   if(!ExtExpert.Init())

在这个阶段,带有Inp前缀的EA外部变量被使用在EA的CSampleExpert::InitIndicatorsCSampleExpert::InitCheckParameters,和CSampleExpert::Init函数中。我们要用副本变量(带有Ext前缀的)替换这些函数中的外部变量。在此我建议一个非常规的解决方案:


替换完成后,编译该文件以确保所有这些过程都已正确完成。不要有任何错误。


2.5. 第二步。修改面板

图4所示面板是空的。既没有同EA进行“交互”的函数,也没有处理输入数据的函数。复制面板的空文件PanelDialog2Original.mqhNotification文件夹下。

向面板类中添加外部变量。它们将用于存储所有输入数据的状态。注意mModification变量。我将在p中给出关于它的更多细节。2.7.

private:
   //--- 元素的GetCheck()函数
   virtual int       GetCheck(const int idx);
   //---
   bool              mMail;
   bool              mPush;
   bool              mAlert_;
   double            mLots;               // 交易量
   int               mTakeProfit;         // 止赢(点数)
   int               mTrailingStop;       // 追踪止损水平(点数)
   int               mMACDOpenLevel;      // MACD开始水平(点数)
   int               mMACDCloseLevel;     //MACD结束水平(点数)
   int               mMATrendPeriod;      // MA 周期
   //---
   bool              mModification;       // 值是否改变
  };
//+------------------------------------------------------------------+
//| 事件处理                                                      

在下面的面板类构造函数中初始化内部变量:

//+------------------------------------------------------------------+
//| 构造函数                                                      
//+------------------------------------------------------------------+
CControlsDialog::CControlsDialog(void) : mMail(false),
                                         mPush(false),
                                         mAlert_(true),
                                         mLots(0.1),
                                         mTakeProfit(50),
                                         mTrailingStop(30),
                                         mMACDOpenLevel(3),
                                         mMACDCloseLevel(2),
                                         mMATrendPeriod(26),
                                         mModification(false)
  {
  }
//+------------------------------------------------------------------+
//| 析构函数                                                       

根据内部变量,向CControlsDialog::Create函数添加开关元素组:

if(!CreateButtonOK())
      return(false);

//---
   SetCheck(0,mMail);
   SetCheck(1,mPush);
   SetCheck(2,mAlert_);

//--- 成功
   return(true);
  }

 

2.6. 第三步。修改EA

直到现在,EA和面板是两个相互独立的文件,彼此没有关联。让我们连结他们并声明面板的ExtDialog变量。

#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include "PanelDialog2Original.mqh"
//+------------------------------------------------------------------+
//| 全局变量                                                            |
//+------------------------------------------------------------------+
CControlsDialog ExtDialog;
//--- 输入参数
input bool     InpMail=false;          //通过email进行通知
input bool     InpPush=false;          // 通过短信push通知

为了使得面板能运行和可见,要创建并加载它。此外,确保添加了OnChartEvent()(处理图表事件)和 OnDeinit() 函数。EA中的OnInit()函数看上去像这样:

int OnInit(void)
  {
   ExtMail=InpMail;
   ExtPush=InpPush;
   ExtAlert=InpAlert;

   ExtLots=InpLots;
   ExtTakeProfit=InpTakeProfit;
   ExtTrailingStop=InpTrailingStop;
   ExtMACDOpenLevel=InpMACDOpenLevel;
   ExtMACDCloseLevel=InpMACDCloseLevel;
   ExtMATrendPeriod=InpMATrendPeriod;
//--- 创建所有需要的对象
   if(!ExtExpert.Init())
      return(INIT_FAILED);
//--- 创建程序对话框
   if(!ExtDialog.Create(0,"Notification",0,100,100,360,380))
      return(INIT_FAILED);
//--- 运行程序
   if(!ExtDialog.Run())
      return(INIT_FAILED);
//--- 成功
   return(INIT_SUCCEEDED);
  }

我们在OnDeinit()中销毁面板,OnDeinit()紧跟着OnInit():

//--- 成功
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| EA反初始化函数                                               
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 
   Comment("");
//--- 销毁对话框
   ExtDialog.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| EA中处理新报价到来的函数
//+------------------------------------------------------------------+
void OnTick(void)

在EA的结尾添加OnChartEvent()函数(在OnTick函数之后)。

//--- 如果EA执行则改变超时的限制时间(秒)
         if(ExtExpert.Processing())
            limit_time=TimeCurrent()+ExtTimeOut;
        }
     }
  }
//+------------------------------------------------------------------+
//| ChartEvent函数                                                    |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

现在,EA可以被编译并在图表上运行了。EA加载并带有面板。

EA和面板

图. 7. EA和面板


2.7. 第四步。修改面板。整合

首先加载EA然后,它的输入参数由用户定义。之后面板被加载。因此,面板应该含有同EA进行数据交互的功能。

让我们添加 Initialization() 方法,它用于接收参数并用接收的参数初始化面板内部变量。声明:

virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
      //--- 初始化
   virtual bool      Initialization(const bool Mail,const bool Push,const bool Alert_,
                                    const double Lots,const int TakeProfit,
                                    const int  TrailingStop,const int MACDOpenLevel,
                                    const int  MACDCloseLevel,const int MATrendPeriod);

protected:
   //--- 创建独立控件
   bool              CreateCheckGroup(void);

方法体(在CControlsDialog::GetCheck前插入):

//+------------------------------------------------------------------+
//| 初始化    
//+------------------------------------------------------------------+
bool CControlsDialog::Initialization(const bool Mail,const bool Push,const bool Alert_,
                                     const double Lots,const int TakeProfit,
                                     const int  TrailingStop,const int MACDOpenLevel,
                                     const int  MACDCloseLevel,const int MATrendPeriod)
  {
   mMail=Mail;
   mPush=Push;
   mAlert_=Alert_;

   mLots=Lots;
   mTakeProfit=TakeProfit;
   mTrailingStop=TrailingStop;
   mMACDOpenLevel=MACDOpenLevel;
   mMACDCloseLevel=MACDCloseLevel;
   mMATrendPeriod=MATrendPeriod;
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| 获取元素的检查结果
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)

既然面板的内部变量已经被数据初始化,我们要正确的填充面板的控件元素(所有字段)。因为我们有六个输入字段,我将基于m_edit1提供一个样例。文本字符串看上去像这样:

...
   if(!m_edit1.Text("Edit1"))
...

但现在它看上去不一样了:

...
   if(!m_edit1.Text(DoubleToString(mLots,2)))
...

因此,每一个完整的字段对应一个特定的内部变量。

下一个方法为GetValues()返回内部变量的值:

virtual bool      Initialization(const bool Mail,const bool Push,const bool Alert_,
                                    const double Lots,const int TakeProfit,
                                    const int  TrailingStop,const int MACDOpenLevel,
                                    const int  MACDCloseLevel,const int MATrendPeriod);
   //--- 获取值
   virtual void      GetValues(bool &Mail,bool &Push,bool &Alert_,
                               double &Lots,int &TakeProfit,
                               int &TrailingStop,int &MACDOpenLevel,
                               int &MACDCloseLevel,int &MATrendPeriod);

protected:
   //--- 创建独立控件
   bool              CreateCheckGroup(void);

在CControlsDialog::Initialization())之后插入方法体:

//+------------------------------------------------------------------+
//| 获取值
//+------------------------------------------------------------------+
void CControlsDialog::GetValues(bool &Mail,bool &Push,bool &Alert_,
                                double &Lots,int &TakeProfit,
                                int &TrailingStop,int &MACDOpenLevel,
                                int &MACDCloseLevel,int &MATrendPeriod)
  {
   Mail=mMail;
   Push=mPush;
   Alert_=mAlert_;

   Lots=mLots;
   TakeProfit=mTakeProfit;
   TrailingStop=mTrailingStop;
   MACDOpenLevel=mMACDOpenLevel;
   MACDCloseLevel=mMACDCloseLevel;
   MATrendPeriod=mMATrendPeriod;
  }
//+------------------------------------------------------------------+
//| 获取元素的检查结果
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)

因为面板根据EA执行的任何交易动作发送与其对应的通知,应有一个特殊的方法来处理它。让我们来声明它:

virtual void      GetValues(bool &Mail,bool &Push,bool &Alert_,
                               double &Lots,int &TakeProfit,
                               int &TrailingStop,int &MACDOpenLevel,
                               int &MACDCloseLevel,int &MATrendPeriod);   //--- 发送通知
   virtual void      Notifications(const string text);

protected:
   //--- 创建独立控件
   bool              CreateCheckGroup(void);

在CControlsDialog::GetValues())之后插入方法体:

//+------------------------------------------------------------------+
//|  发送通知
//+------------------------------------------------------------------+
void CControlsDialog::Notifications(const string text)
  {
   int i=m_check_group.ControlsTotal();
   if(GetCheck(0))
      SendMail(" ",text);
   if(GetCheck(1))
      SendNotification(text);
   if(GetCheck(2))
      Alert(text);
  }
//+------------------------------------------------------------------+
//| 获取元素的检查结果
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)

mModification标识(在p. 2.5中提到的) 用于记忆面板中的参数是否被修改。

virtual void      Notifications(const string text);
   //---
   virtual bool      Modification(void) const { return(mModification);          }
   virtual void      Modification(bool value) { mModification=value;            }

protected:
   //--- 创建独立控件
   bool              CreateCheckGroup(void);

在CControlsDialog::OnClickButtonOK中控制参数的修改,它用于处理按下Apply changes按钮的事件。

//+------------------------------------------------------------------+
//| 事件处理                                                
//+------------------------------------------------------------------+
void CControlsDialog::OnClickButtonOK(void)
  {
//--- 验证修改
   if(m_check_group.Check(0)!=mMail)
      mModification=true;
   if(m_check_group.Check(1)!=mPush)
      mModification=true;
   if(m_check_group.Check(2)!=mAlert_)
      mModification=true;

   if(StringToDouble(m_edit1.Text())!=mLots)
     {
      mLots=StringToDouble(m_edit1.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit2.Text())!=mTakeProfit)
     {
      mTakeProfit=(int)StringToDouble(m_edit2.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit3.Text())!=mTrailingStop)
     {
      mTrailingStop=(int)StringToDouble(m_edit3.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit4.Text())!=mMACDOpenLevel)
     {
      mMACDOpenLevel=(int)StringToDouble(m_edit4.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit5.Text())!=mMACDCloseLevel)
     {
      mMACDCloseLevel=(int)StringToDouble(m_edit5.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit6.Text())!=mMATrendPeriod)
     {
      mMATrendPeriod=(int)StringToDouble(m_edit6.Text());
      mModification=true;
     }
  }

此外,面板在处理程序中检查输入数据:

void              OnChangeCheckGroup(void);
   void              OnChangeEdit1(void);
   void              OnChangeEdit2(void);
   void              OnChangeEdit3(void);
   void              OnChangeEdit4(void);
   void              OnChangeEdit5(void);
   void              OnChangeEdit6(void);
   void              OnClickButtonOK(void);

我将忽略他们的描述。

2.8. 第五步。更改EA。最后的编辑

当前,面板是不能在策略测试器中运行的,因此我们需要实现保护并引入内部变量 – bool_tester标识。

//---
int ExtTimeOut=10; // 交易操作之间的间隔时间(秒)
bool           bool_tester=false;      // true - 测试器模式
//+------------------------------------------------------------------+
//| MACD 样例EA类
//+------------------------------------------------------------------+
class CSampleExpert

向OnInit()中插入更改 – 防止在策略测试器中加载。在可视化前初始化面板的参数:

//--- 创建所有需要的对象
   if(!ExtExpert.Init())
      return(INIT_FAILED);
//--- 
   if(!MQLInfoInteger(MQL_TESTER))
     {
      bool_tester=false;
      //---
      ExtDialog.Initialization(ExtMail,ExtPush,ExtAlert,
                               ExtLots,ExtTakeProfit,ExtTrailingStop,
                               ExtMACDOpenLevel,ExtMACDCloseLevel,ExtMATrendPeriod);
      //--- 创建应用程序对话框
      if(!ExtDialog.Create(0,"Notification",0,100,100,360,380))
         return(INIT_FAILED);
      //--- 运行程序
      if(!ExtDialog.Run())
         return(INIT_FAILED);
     }
   else
      bool_tester=true;
//--- 成功
   return(INIT_SUCCEEDED);
  }

检查面板参数是否在OnChartEvent()中改变。如果是,EA要用新的参数初始化:

//+------------------------------------------------------------------+
//| ChartEvent函数                                                    |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
// 查询面板中的bool变量,参数是否已经改变
// 如果是,查询面板参数并调用
// CSampleExpert::Init(void)
   if(ExtDialog.Modification())
     {
      ExtDialog.GetValues(ExtMail,ExtPush,ExtAlert,
                          ExtLots,ExtTakeProfit,ExtTrailingStop,
                          ExtMACDOpenLevel,ExtMACDCloseLevel,ExtMATrendPeriod);
      if(ExtExpert.Init())
        {
         ExtDialog.Modification(false);
         Print("Parameters changed, ",ExtLots,", ",ExtTakeProfit,", ",ExtTrailingStop,", ",
               ExtMACDOpenLevel,", ",ExtMACDCloseLevel,", ",ExtMATrendPeriod);
        }
      else
        {
         ExtDialog.Modification(false);
         Print("Parameter change error");
        }
     }
  }
//+------------------------------------------------------------------+

 

总结

将面板和指标结合变得非常容易了。要做到这一点,我们已经在面板类中实现了完整的功能(控制元素的尺寸和位置,对事件的响应),并声明了面板类的变量以及在指标中添加了OnChartEvent()函数。

将EA和更加复杂的面板结合起来更具挑战,主要是因为需要组织EA和面板之间的“通信”。问题的复杂性主要在取决于面板是否做好通信的准备。换句话说,如果面板一开始就具有用于同其他程序集成的功能函数,那将会更易于将其同其他应用相结合(指标或EA)。

以下文件附到文本结尾: