请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3364712 新帖:0

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

牛市来了发表于:4 月 17 日 18:52回复(1)

简介

指标与 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 事件处理程序了。但这是另一篇文章的主题了。  

全部回复

0/140

达人推荐

量化课程

    移动端课程