简介
指标与 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
(1.82 KB)
(5.08 KB)
(5.62 KB)
(10.41 KB)
(3.95 KB)
(11.24 KB)
(1.59 KB)
(1.99 KB)
(18.32 KB)
MyFxtop迈投-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。