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

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面板看上去如下:

图 4. 面板2

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

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

2.3. EA和面板应该有交互

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

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

2.4. 第一步。修改EA

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

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


声明定义发送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加载并带有面板。

图. 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);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值