下载MetaTrader 5

您喜欢这篇文章?
分享或-
发布链接

使用MetaTrader 5新的可能性

New Bar (新柱)事件处理程序

9 十二月 2013, 13:04
Konstantin Gruzdev
1
1 404

简介

指标与 EA 的编程员,始终都对编写紧凑代码(从执行时间角度考虑)饶有兴趣。您可以从不同的角度来处理这一问题。我们会从本文中这一宽泛主题,讨论似乎已经解决的问题:检查有无新柱。这是限制计算循环时十分常用的一种方式,因为图表上有新柱生成期间,则所有的计算和交易操作都会被执行一次。所以,待讨论内容如下:  

  • 探测新柱的方式。
  • 现有新柱探测算法的不足之处。
  • 创建新柱探测的通用方法。
  • 应用此方法的微妙细节与方式。
  • NewBar 事件及其处理程序 - OnNewBar()。

探测新柱的方式

现在,就如何探测新柱,已经有一些可接受的解决方案。比如说,可以在 《“EA 交易”中的限制与验证》《指标的经济计算原则》这里找到。顺便提一下,我推荐了解上述材料。它会促成您对我将要讲到内容的理解。  

上述材料利用了追踪当前未完成柱开盘时间的原则。此方法非常简单而且可靠。探测新柱还有其它的方法。

比如说,在作此用途的自定义指标中,您可以利用 OnCalculate() 函数的两个输入参数:rates_total 与 prev_calculated。此方法的局限 - 基本上已是公认事实:它只能用于探测当前图表上的新柱,且仅限指标。如果您想就另一周期或交易品种查找新柱,就必须采用其他技术。

或者,举个例子,如果 Tick Volume (价格跳动量) = 1,或是所有柱价格均相等,则您可以尝试就其第一次跳动捕获新柱:价格跳动量 = 1 即指:开盘价 = 最高价 = 最低价 = 收盘价。这些方法在测试中可能表现良好,但实际交易中却时常出错。这是因为,第一与第二次价格跳动之间的时长,有时不足以令其捕获到生成的柱。如果市场动态强劲、或是互联网连接质量差,这种问题就尤为显著。  

有一种基于 TimeCurrent() 函数探测新柱的方法。顺便提一下,如果您需要探测当前图表有无新柱,这种方法很不错。我们会在本文末尾处用到它。

嗯,您甚至可以问问邻居:“嘿,有新柱吗?”我很想知道他会怎么回答?嗯,好吧,您旨在探测新柱,对于当前未完成柱开盘时间的追踪原则的选择,就让它结束吧。其简单性、可靠性的的确确行之有效。

起点

如上所述的各种东西,对于探测新柱而言,都不错。但是……  

要了解“但是”什么,作为起点(或原型),我们会利用简单且行之有效的函数来探测新柱 - 源于 《“EA 交易”中的限制和验证》 一文。所示如下:

//+------------------------------------------------------------------+
//| 如果新柱出现一组交易品种/周期则返回                                   |
//+------------------------------------------------------------------+
bool isNewBar()
  {
//--- 在静态变量中记忆最后柱线的开盘时间
   static datetime last_time=0;
//--- 当前时间
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

//--- 如果是首次调用函数
   if(last_time==0)
     {
      //--- 设置时间并退出
      last_time=lastbar_time;
      return(false);
     }

//--- 如果时间不同
   if(last_time!=lastbar_time)
     {
      //--- 记忆时间并返回 true
      last_time=lastbar_time;
      return(true);
     }
//--- 如果我们执行到此行, 则柱线不是新的; 返回 false
   return(false);
  }

该原型函数确实有效,也确实有其存在的权利,但是……

原型函数的分析

我曾将此函数复制到我(舍我其谁)那最好最棒的“EA 交易”源代码中,却没有效果。我就开始研究。下述是我针对此函数的一些心得。

函数头。 所以,我们每一个都看一看。我们从函数头开始:

bool isNewBar()

我喜欢函数头,它非常简单、直观,而且无需处理传入参数。如果将来能以这种形式使用就好了。

调用次数限制。 函数头之后,是初始化静态变量的第一个语句:

//--- 在静态变量中记忆最后柱线的开盘时间
   static datetime last_time=0;

一切看起来都很不错,但是……

问题是我们使用的是静态变量。帮助主题告诉我们:帮助主题告诉我们:

静态变量从程序执行时起开始存在,而且,在指定的 OnInit() 函数被调用之前,只会初始化一次。如未指定初始值,则静态存储类的变量就会取零初始值。

利用静态关键词声明的局部变量,会在整个函数使用期间内保留其值。每下一个函数被调用,此类局部变量都会包含它们前一次调用期间的值。

如果您由一处调用该原型函数,则我们已经如愿以偿。但是,如果我们想要使用此函数,比如说,再一次换个地方在相同的计算循环中,它就会始终返回 false,也就意味着没有柱。这种情况未必总是对的。本例中的静态变量,对原型函数调用的次数施加了人为限制。

通用性问题。 原型函数中的下述语句大致如下:

//--- 当前时间
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

要获取最后一个未完成柱的开盘时间,将 SeriesInfoInteger() 函数与 SERIES_LASTBAR_DATE 修饰符搭配使用合情合理。

我们的原型 isNewBar() 最开始被设想为一种简单、默认使用交易工具和当前图表周期的函数。如果您只想追踪当前图表上的新柱,这样可以接受。但是,如果我使用的周期和工具不仅仅限于当前图表,那怎么办呢?再者说,如果我有一些复杂图表呢?比如说,我决定要标绘 Renko 或 Kagi 图时怎么办呢?

想难住我们也不容易。稍后我们再讨论如何修复这一问题。  

错误处理。 我们来看看 SeriesInfoInteger() 函数。如在图表尚未形成时运行,您认为它会返回什么?可能会出现此类情况:比如说,如果您已将“EA 交易”或指标附至图表,并决定更改周期或交易品种,或者是您重启终端时。那么,时间序列更新时会发生什么呢?顺便说一下,其在 Help (帮助)主题中的警告如下:

数据可用性

存在 HCC 格式(甚至是即用型 HC 格式)的数据, 并不能始终表明这些将于图表上显示或在 MQL5 程序中使用的数据绝对可用。

从 MQL5 程序访问价格数据或指标值时要记住,并不保证它们于特定时间或从特定时间起的可用性。这是因为,为了节约系统资源,MetaTrader 5 中 并不存储 mql5 程序所需数据的完整副本;而只是给出直接访问终端数据库的权限。

所有时间表的价格历史,均通过 HCC 格式的一般数据构建,而且,来自服务器的任何数据更新,都会导致所有时间表数据的更新及指标的重新计算。正因如此,就算是数据刚刚还可用,现在访问也可能遭拒。

那么,此函数会返回什么呢?要避免这种不确定性,您需要以某种方式开始捕获最后一个未完成柱开盘时间的查询错误。  

初始化的可能性。 我们继续研究我们原型函数的下述语句:

//--- 如果是首次调用函数
   if(last_time==0)
     {
      //--- 设置时间并退出
      last_time=lastbar_time;
      return(false);
     }

这里的一切都显得顺理成章。但是,还有一个细微差别。您是否注意到了上述来自 Help 的语句:“静态变量从程序执行时起便存在,而且,在指定的 OnInit() 函数之前,只会初始化一次”?如果初始化 last_time 变量需要更多的时间,该怎么办?更确切地说,如果您想手动创建一个首次调用的情境,该怎么做?又或者,某些其它的情境呢?如果您知道答案,那么,问问题就简单了。但后面还有更详细的说明。

柱的数量。 接下来,我们的原型函数代码如下:

//--- 如果时间不同
   if(last_time!=lastbar_time)
     {
      //--- 记忆时间并返回 true
      last_time=lastbar_time;
      return(true);
     }

您看到了,像我这样的程序员都能做到,所以 if 运算符一定会让客户端和策略测试程序“大吃一惊”。从逻辑上讲,过去的时间总是会早于当前。也就是说,last_time < lastbar_time。由于程序的意外错误,我们也会坐上时光机器,或者,更确切地说 - 相反的情况发生了:lastbar_time < last_time。够惊喜吧!一般来讲,这样的时间导论都能轻松探测出来,并显示错误消息。

但守得云开见月明。在观察我的“时光机器”的同时,我发现在 isNewBar() 的各次调用中不只可以显示一个新柱。图表周期越小,两次函数调用之间出现多个柱的机率就越高。造成这一情况的原因有很多:从计算时间长,到临时失去与服务器的连接,不一而足。有用的不仅是接收新柱信号的机会,还包括柱数。

我们的原型函数如下结束:

//--- 如果我们执行到此行, 则柱线不是新的; 返回 false
   return(false);

是的,如果我们已经过了这条线 - 柱就不是新的了。

创建新的 isNewBar() 函数

以下部分将会很有意思。我们来解决探测到的缺点。您知道,我这人有一点过度谦虚,竟然将此部分内容命名为“创建新的 isNewBar() 函数”。我们来点更实际的。

我们从摆脱函数调用次数的限制开始。

首先想到的,就是您可以使用与 《指标的经济计算原则》 中或来自这里 isNewBar 的 isNewBar() 一样名称的函数。也就是说,要将存储多个 last_time 值的数组纳入函数主体,放入来自不同地方的 isNewBar() 函数调用计数器,以此类推。当然,所有这些都是有效版本,且可实施。但是想像一下,如果我们编写一个根据 12 个货币对工作的多货币“EA 交易”呢?那样会有非常多的必要细节要考虑,不会混淆吗?

我们该怎么做呢?答案在此

面向对象编程的妙处即在于,某些类的对象或实例,可以独立于同类的其它实例“自给自足”。所以,我们开始创建一个类 CisNewBar,如此一来,我们就能够在“EA 交易”或指标中的任何地方不限次数地生成此类的实例。并让每个实例都能“自给自足”。

我们必须着手处理的内容如下:

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // 最后柱线开盘时间
      
   public:
      void              CisNewBar();      // CisNewBar 构造器
      //--- 判断新柱线方法:
      bool              isNewBar();       // 新柱线的第一次请求类型
  };  

bool CisNewBar::isNewBar()
  {
   //--- 此处定义静态变量

   //--- 此处是剩余方法代码   
   ...

   //--- 如果我们执行到此行, 则柱线不是新的; 返回 false
   return(false);
  }

原来的 isNewBar() 函数现在成了方法。注意:现在没有了静态变量 last_time - 换成了现在的受保护类函数 m_lastbar_time。如果我们当时将静态变量留在 isNewBar() 方法中,那么,我们所有的努力都将劳而无功,因为我们会面临之前 isNewBar() 函数同样的问题 - 都是静态变量的功能。

而现在,最后一个柱的时间会被存储到类的受保护变量 m_lastbar_time 中,而且,在每个类实例中,都会为此变量分配内存。由此,我们就能够去除原型函数中存在的调用次数限制。我们可以在 MQL 程序中的不同地方不限次数地调用 isNewBar() 方法,为每个地方创建类实例。

这种事情我们已经是手到擒来。现在,我们主攻通用性。向新类中添加东西之前,我想让您看看一个有趣的想法:

我们做个推论。我们想要什么? 想要获取新柱相关信号。我们想要怎么实现? 这样,如果最后一次价格跳动(或最后时刻)时当前未完成柱的开盘时间晚于前一次跳动(或前一时刻)时当前未完成柱的开盘时间,则会形成新柱。话很绕口,但绝对没错。总结起来,就是我们需要对比时间。因此,我认定将当前未完成柱的开盘时间 newbar_time 传递到 isNewBar() 方法中是符合逻辑的。之后,方法头如下所示:

bool isNewBar(datetime newbar_time)

先不要问把这个 newbar_time 带到哪里 - 假设这是已知的。稍后我们再深入研究。  

顺便说一下,将时间传递到 isNewBar() 方法中,我们就得到了一个探测新柱的非常灵活的工具。我们将具备利用所有交易工具来涵盖各种标准图表周期的能力。因为它的出现,我们现在不再依赖于交易品种名称和周期长短。  

我们还可以使用非标准图表。比如说,如果您要绘制价格跳动烛形图,或是 Renko 或 Kagi 图表,它们的柱开盘时间几乎从不会与标准图形周期的时间一致。在这种情况下,我们的函数不可或缺。

好,现在通用性也没有问题了。我们根据自己的想法来补充 CisNewBar 类:

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // 最后柱线开盘时间
      uint              m_retcode;        // 判断新柱线的结果代码
      int               m_new_bars;       // 新柱线的数量
      string            m_comment;        // 执行注释
      
   public:
      void              CisNewBar();      // CisNewBar 构造器
      //--- 判断新柱线方法:
      bool              isNewBar(datetime new_Time); // 新柱线的第一次请求类型
  };
   
//+------------------------------------------------------------------+
//| 新柱的第一种请求类型                                                |
//| INPUT:  newbar_time - 建立(假设)新柱的时间                          |
//| OUTPUT: true   - 如果新柱已经出现                                   |
//|         false  - 如果没有新柱或出错的情况                            |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CisNewBar::isNewBar(datetime newbar_time)
  {
   //---- 初始化保护变量
   m_new_bars = 0;      // 新柱线的数量
   m_retcode  = 0;      // 判断新柱线的结果代码: 0 - 无错
   m_comment  =__FUNCTION__+" 成功检查新柱线";
   //---
   
   //--- 进一步确认, 检查: 是否新柱线的时间 m_newbar_time 小于最后柱线的时间 m_lastbar_time? 
   if(m_lastbar_time>newbar_time)
     { // 如果新柱线旧于最后柱线, 打印错误消息
      m_comment=__FUNCTION__+" 同步错误: 前柱线时间 "+TimeToString(m_lastbar_time)+
                                                  ", 请求的新柱线时间 "+TimeToString(newbar_time);
      m_retcode=-1;     // 判断新柱线的结果代码: 返回 -1 - 同步错误
      return(false);
     }
   //---
        
   //--- 如果这是首次调用
   if(m_lastbar_time==0)
     {  
      m_lastbar_time=newbar_time; //--- 设置最后柱线时间并退出
      m_comment   =__FUNCTION__+" 初始化 lastbar_time = "+TimeToString(m_lastbar_time);
      return(false);
     }   
   //---

   //--- 检查新柱线:
   if(m_lastbar_time<newbar_time)       
     { 
      m_new_bars=1;               // 新柱线的数量
      m_lastbar_time=newbar_time; // 记住最后柱线时间
      return(true);
     }
   //---
   
   //--- 如果我们执行到此行, 则柱线不是新的; 返回 false
   return(false);
  }

查看类的源代码,您很可能已经注意到,我们已经考虑到了运行时间错误的追踪,也已经引入了存储新柱数量的变量。

一切就绪,但是我们的通用方法 isNewBar(datetime newbar_time) 却有一个重大的不便之处。不便之处在于,我们总是要担心 EA 或指标源代码中(假想) newbar_time 时间的计算。  

幸运的是,某些情况下我们可以简化您的工作,将此函数委托给类的新增方法。对于原型函数中的标准周期和交易品种而言,可以利用带有 SERIES_LASTBAR_DATE 修饰符的第二版 SeriesInfoInteger() 函数来实现,而所有其它情况则都采用泛型方法。得到的代码如下:

//+------------------------------------------------------------------+
//| 新柱的第二种请求类型                                                |
//| INPUT:  no.                                                      |
//| OUTPUT: m_new_bars - 新柱数量                                     |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
int CisNewBar::isNewBar()
  {
   datetime newbar_time;
   datetime lastbar_time=m_lastbar_time;
      
   //--- 请求最后柱线开盘时间:
   ResetLastError(); // 设置预定义变量 _LastError 为 0
   if(!SeriesInfoInteger(m_symbol,m_period,SERIES_LASTBAR_DATE,newbar_time))
     { // 如果请求失败, 打印错误消息:
      m_retcode=GetLastError();  // 判断新柱线的结果代码: 写变量值 _LastError
      m_comment=__FUNCTION__+" 当获取最后柱线开盘时间时错误: "+IntegerToString(m_retcode);
      return(0);
     }
   //---
   
   //---新柱线下次使用的第一次类型, 来完成分析:
   if(!isNewBar(newbar_time)) return(0);
   
   //---调整新柱线数量:
   m_new_bars=Bars(m_symbol,m_period,lastbar_time,newbar_time)-1;
   
   //--- 如果我们到此, 则柱线是新的; 返回它们的数量
   return(m_new_bars);
  }

那么,现在我们都有什么?现在,针对标准周期,我们无需再关注最后一个未完成柱开盘时间的确定。我们已将原型函数处理成了调用简单、不再存在原有缺点的函数。甚至还实现了额外的优势,其中包括错误代码、运行时间注释以及新柱数量。

还忘了什么吗?没错。还差最后一步 - 初始化。为此,我们会运用类构造函数和多个 Set 方法。我们的类构造函数如下所示:  

//+------------------------------------------------------------------+ 
//| CisNewBar 构造函数                                                |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CisNewBar::CisNewBar()
  {
   m_retcode=0;         // 判断新柱线的结果代码
   m_lastbar_time=0;    // 最后柱线开盘时间
   m_new_bars=0;        // 新柱线的数量
   m_comment="";        // 执行注释
   m_symbol=Symbol();   // 交易品种名, 缺省是当前图表交易品种
   m_period=Period();   // 图表周期, 缺省是当前图表周期
  }

Set 方法如下所示:

//--- 初始化保护数据的方法:
void              SetLastBarTime(datetime lastbar_time){m_lastbar_time=lastbar_time;                            }
void              SetSymbol(string symbol)             {m_symbol=(symbol==NULL || symbol=="")?Symbol():symbol;  }
void              SetPeriod(ENUM_TIMEFRAMES period)    {m_period=(period==PERIOD_CURRENT)?Period():period;      }

因为有类构造函数,我们无需再将心思花在交易品种的初始化和当前图表的周期上。因为在原型函数中,它们会被默认使用。但是如果我们需要使用另一个交易品种或图表周期,则可以将其用于创建的 Set 方法。此外,您还可以利用 SetLastBarTime(datetime lastbar_time) 重新创建“首次调用”的情境。

综上所述,我们来创建多个 Get 方法,以从“EA 交易”与指标的类中获取相应数据:

      //--- 存取保护数据的方法:
uint              GetRetCode()     const  {return(m_retcode);     }  // 判断新柱线的结果代码 
datetime          GetLastBarTime() const  {return(m_lastbar_time);}  // 最后柱线开盘时间
int               GetNewBars()     const  {return(m_new_bars);    }  // 新柱线的数量
string            GetComment()     const  {return(m_comment);     }  // 执行注释
string            GetSymbol()      const  {return(m_symbol);      }  // 交易品种名
ENUM_TIMEFRAMES   GetPeriod()      const  {return(m_period);      }  // 图表周期

现在,我们可以在 mql5 程序中获取所有必要信息。可以为创建 CisNewBar 类划上一个句号了。

类的完整源代码,请见 Lib CisNewBar.mqh 随附文件。

CisNewBar 类使用示例

我建议您仔细研究一下类的使用示例,领略由我们缔造的所有微妙细节。可能不只优点,还有缺点。

示例 1. 首先,我们为来自 《“EA 交易”中的限制和验证》 一文的 isNewBar() 函数创建一个完全相同的“EA 交易”。

//+------------------------------------------------------------------+
//|                                               Example1NewBar.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               Lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar current_chart; // CisNewBar 类实例: 当前图表

//+------------------------------------------------------------------+
//| EA订单函数                                                        |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(current_chart.isNewBar()>0)
     {     
      PrintFormat("新柱线: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

我们在带有相同货币对和周期的图表上运行两个“EA 交易”。看看结果如何:

首先,两个“EA 交易”同时报告新柱相关信息。之后,它们陷入沉默,仅四分钟过后,它们通知有一个新住(标注为 1)。情况正常 - 我断开互联网几分钟,再来看看什么情况。尽管已经形成了几个柱,但我们并未收到相关信息。我们的新型“EA 交易”可以纠正这一缺陷,因为 isNewBar() 方法允许这样。

接下来,我将图表周期改为 M2。“EA 交易”的反应有所不同。CheckLastBar 开始每 2 分钟报告一个新柱,而 Example1NewBar 则每分钟都汇报新柱信息,就像周期没改过一样(标注为 2)。

current_chart 实例已通过类构造函数初始化的这一情况,已附至图表。如您更改“EA 交易”的周期,已经附至图表的类构造函数不会启动,而“EA 交易”则会继续使用 M1 周期。这就说明,我们的类实例自给自足,不受环境变化的影响。这样有利有弊 - 皆取决于任务。  

要让我们的“EA 交易”充当 CheckLastBar,我们需要在 OnInit() 函数中初始化受保护类变量 m_symbol 与 m_period。动手吧。

示例 2. 我们向“EA 交易”引入一些附加项,然后再跟 CheckLastBar 对比其性能。“EA 交易”的源代码作为 Example2NewBar.mq5 文件随附。在带有相同货币对和周期的图表上运行“EA 交易”。为其创建与上一次相同的障碍。看看结果如何:

和上次差不多,“EA 交易”首先同时报告新柱。之后,我将互联网断开几分钟……再联网。我们的新“EA 交易”不仅报告新柱,还报告它们出现的数量(标注为 1)。对于大多数的指标和 EA 而言,此数字都意味着未被计算的柱的数量。由此,我们拥有了一个低成本高效益的重新计算算法的良好基础。  

接下来,我将图表周期改为 M2。与示例 1 不同的是,“EA 交易”同步运行(标注为 2)。OnInit() 函数中的受保护类变量 m_symbol 和 m_period 的初始化起到了作用!如更改交易品种(标注为 3),“EA 交易”也是一样。

示例 3. 我们在 CisNewBar 类中置入了追踪错误的可能性。有可能“EA 交易”就是为了无需追踪错误而设计。那么,就不要使用这种可能性。我们会试着手工创建一种可能出现错误的情境,并努力捕获它。为此,我们再稍微补充一下“EA 交易”的源代码(Example3NewBar.mq5 文件)。

接下来我要做什么呢?与往常一样,我会在分钟图表上运行 Example3NewBar。之后,我就开始更改图表的工具,希望会出现终端在“EA 交易”请求之前没时间累积时间序列的情况。总之,我会折磨折磨客户端,看看能怎样……  

几次尝试之后,我们的“EA 交易”捕获到一个错误:

 

现在我们可以自信地说:我们能够捕获运行时间错误了。如何处理它们,就是个人喜好问题了。注意,我们已经四次追踪到这个错误了。下载结束且图表形成时,“EA 交易”提示说我们只忽略了 1 个柱。

顺便提一下,如果查看“EA 交易”源代码,您可能已经发现:只要 isNewBar() 方法返回值小于等于零,就有必要检查有无错误。

警告:如果您在试验期间更改图表周期,那么,在您将图表周期由小改大时,就会得到一个同步错误。这是因为 H1 的柱开盘时间(仅作示例)早于 59 种情况下的 M1。要在切换图表周期时避免这个错误,您需要在 OnInit() 函数中利用 SetLastBarTime (datetime lastbar_time) 方法完成 m_lastbar_time 变量的妥善初始化。

示例 4. 我们在本例中将“EA 交易”的任务复杂化。采用三种货币对:M1 上的 EURUSD,M1 上的 GBPUSD 以及 M2 上的 USDJPY。带首个货币对的为当前图表,而且我们也只就其留意新柱。通过第二个货币对,我们计算“EA 交易”启动之后形成的柱的数量。我们会计数,直到首个货币对发出有新柱的信号。而就第三种货币对,我们会持续(EURUSD 上如果出现柱) 执行受保护类变量 m_lastbar_time 的初始化。“EA 交易”的源代码作为 Example4NewBar.mq5 文件随附。

我想通过创建本示例,找出 CisNewBar 类在多货币模式下的运行方式。好了,将其启动……得到的代码如下:

结果带来了问题。我趁热打铁,在策略测试程序中以此时间间隔运行。策略测试程序结果:

之后,您可以玩一玩“找出 10 处不同”这个游戏。除了“EA 交易”在演示账户上工作的怪事之外,很明显,演示账户与策略测试程序之间亦有不同 - 而且它们显而易见。利用正确方法实施的相似对比,不仅会揭示“EA 交易”的缺陷,还允许将其消除。可能我会分析其发生原因、发生方式,以及需要在“EA 交易”中做出哪些修复。  

示例 5. 我们从未在示例中显式使用最为通用的探测新柱的方法 - isNewBar(datetime newbar_time)。为此,我会取下 《 MQL5 中创建价格跳动指标》 文中的价格跳动烛形图,并添加一个缓冲区,以存储柱线开盘时间(文件 TickColorCandles v2.00.mq5)。我会编写一个非常简短的“EA 交易”,指明新的价格跳动烛形图的时间(文件 Example5NewBar.mq5):

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar newbar_ind; // CisNewBar 类实例: 判断新即时价格蜡烛条
int HandleIndicator;  // 指标句柄
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 获取指标句柄:
   HandleIndicator=iCustom(_Symbol,_Period,"TickColorCandles v2.00",16,0,""); 
   if(HandleIndicator==INVALID_HANDLE)
     {
      Alert(" 创建指标句柄时错误, 错误代码: ",GetLastError());
      Print(" EA 初始化不正常. 交易不允许.");
      return(1);
     }

//--- 图表挂载指标:  
   if(!ChartIndicatorAdd(ChartID(),1,HandleIndicator))
     {
      Alert(" 图表挂载指标时错误, 错误代码: ",GetLastError());
      return(1);
     }
//--- 如果您到达此处, 初始化成功
   Print(" EA 初始化成功. 交易允许.");
   return(0);
  }
//+------------------------------------------------------------------+
//| EA订单函数                                                        |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double iTime[1];

//--- 得到最后未收盘蜡烛条开盘时间:
   if(CopyBuffer(HandleIndicator,5,0,1,iTime)<=0)
     {
      Print(" 获取指标时间值失败. "+
            "\n下次尝试获取指标值将在下次即时价到达.",GetLastError());
      return;
     }
//--- 判断下一个即时价蜡烛条:
   if(newbar_ind.isNewBar((datetime)iTime[0]))
     {
      PrintFormat("新柱线. 开盘时间: %s  最后即时价到达时间: %s",
                  TimeToString((datetime)iTime[0],TIME_SECONDS),
                  TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

当然,您也注意到了我们如何获取价格跳动烛形图开盘的时间。非常简单,不是吗?我将指标和“EA 交易”放入其文件夹,编译并运行“EA 交易”。正常,结果如下:  

 

"New Bar" (新柱)事件处理程序


本文接近尾声,我还有一个想法要与您分享。论坛(俄语)上提出了一个想法:如果有一个标准的 "new bar" (新柱)事件处理程序该多好啊。可能会有开发人员来做,也可能没有。但 MQL5 的妙处即在于,它可以简洁优雅地实现最令人瞠目结舌的想法。

如果您想有一个"new bar" 事件处理程序(或 NewBar) - 那么就动手吧!尤其是,利用我们的类,现在就可以捕获该事件了。我们的 EA(带 NewBar 事件处理程序 OnNewBar())如下所示:

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include "OnNewBar.mqh" // 此处是启动 "新柱线" 事件处理器的秘密

//+------------------------------------------------------------------+
//| 新柱事件处理函数                                                   |
//+------------------------------------------------------------------+
void OnNewBar()
  {
   PrintFormat("新柱线: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
  }

看起来相当不错。我们的“EA 交易”看起来非常简单。该处理程序会打印新柱相关字符串。这就是它的全部工作。欲知如何追踪 NewBar 事件及如何运行处理程序,您需要查阅 OnNewBar.mqh 文件:

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar@mail.ru"

#include <Lib CisNewBar.mqh>
CisNewBar current_chart; // CisNewBar 类实例: 当前图表

//+------------------------------------------------------------------+
//| EA订单函数                                                        |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int period_seconds=PeriodSeconds(_Period);                     // 当前柱线周期的秒数
   datetime new_time=TimeCurrent()/period_seconds*period_seconds; // 当前图表柱线开盘时间
   if(current_chart.isNewBar(new_time)) OnNewBar();               // 当新柱线出现 - 启动新柱线事件处理器
  }

看到了吧,这里也没什么复杂的。但是,我想请您注意两个时刻:

第一个。您也注意到了,我利用 TimeCurrent() 函数来计算柱开盘时间,利用第一种方法来检查来自类的 NewBar 事件。搭配得很不错。它基于此方法的下述事实:在使用带有 SERIES_LASTBAR_DATE 修饰符的 SeriesInfoInteger() 时,不需要任何错误处理。这对于我们来讲很重要,因为我们的 OnNewBar() 处理程序要尽量可靠。

第二个。利用 TimeCurrent() 函数来计算柱开盘时间是最快的方法。而出于同样的目的,利用 SeriesInfoInteger() 函数,甚至在没有错误控制的情况下,也会较慢。

我们处理程序的结果:

   

总结

  在材料展示的过程中,我们对探测新柱的方法进行了一次透彻的分析。我们向您展示了探测新柱各种现有方法的利与弊。我们基于现有的条件创建了 CisNewBar 类,实现了在无额外编程成本条件下对于几乎所有任务中 "new bar" 事件的捕获。与此同时,我们还摆脱了之前解决方案的诸多不便。    

上述示例有助于我们掌握自己发明方法的利与弊。在正确工作要求多货币模式方面要特别注意。对于已识别低效之处,您必须进行一次全盘分析,并制订解决问题的方式。

  所创建的 "new bar" 事件处理程序仅适用于单货币“EA 交易”。但我们已经学会了达此目的最可靠、最快速的方式。现在,您就可以继续制作一个多货币 NewBar 事件处理程序了。但这是另一篇文章的主题了。  

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/159

附加的文件 |
example1newbar.mq5 (1.82 KB)
example2newbar.mq5 (5.08 KB)
example3newbar.mq5 (5.62 KB)
example4newbar.mq5 (10.41 KB)
example5newbar.mq5 (3.95 KB)
onnewbar.mq5 (1.59 KB)
onnewbar.mqh (1.99 KB)
最近评论 | 前往讨论 (1)
chunhua Zhao
chunhua Zhao | 9 12月 2013 在 15:10

    在新柱线的第一种请求方式中,不明白新柱的数量为什么可以直接赋值为1,请指教;

   

 //--- 检查新柱线:
   if(m_lastbar_time<newbar_time)       
     { 
      m_new_bars=1;               // 新柱线的数量
      m_lastbar_time=newbar_time; // 记住最后柱线时间
      return(true);
     }
   //--- //--- 检查新柱线:
   if(m_lastbar_time<newbar_time)       
     { 
      m_new_bars=1;               // 新柱线的数量
      m_lastbar_time=newbar_time; // 记住最后柱线时间
      return(true);
     }
   //---
查找错误和记录 查找错误和记录

MetaEditor 5 具备调试功能。但是在编写 MQL5 程序时,您通常都希望不要显示个别的值,而是测试与在线工作期间出现的所有信息。如果日志文件内容庞大,所需信息快速便捷检索自动化的重要性就显而易见了。本文中,我们会研究 MQL5 程序中查找错误的方式以及记录方法。我们也会简单地记录到文件中,并了解一款方便日志查看的简单程序 - LogMon。

“EA 交易”运行期间平衡曲线斜率的控制 “EA 交易”运行期间平衡曲线斜率的控制

找到交易系统的规则,再于“EA 交易”中进行编程,任务就完成一半了。随着交易结果的累积,您需要通过某种方式纠正“EA 交易”的操作。本文讲述一种方法,通过创建平衡曲线斜率的测量反馈,改善“EA 交易”的性能。

一个用于通过 Google Chart API 构建图表的库 一个用于通过 Google Chart API 构建图表的库

构建各种类型的图表是分析市场情形及测试交易系统的一个基本部分。通常,为了构建一个精致的图表,必须将数据输出到一个文件,然后在 MS Excel 等应用程序中使用该文件。这样并不是非常方便,并且使我们无法动态更新数据。Google Charts API 通过向服务器发送特别请求,提供了在线创建图表的方式。在本文中,我们将尝试让创建此类请求和从 Google 服务器获得图表的过程实现自动化。

生长型神经气:MQL5 中的实施 生长型神经气:MQL5 中的实施

本文会举例说明如何开发一个可以实施名为“生长型神经气” (GNG) 自适应聚类算法的 MQL5 程序。本文针对已研究过语言文档、且已具备一定编程能力和神经信息学基础知识的用户。