New Bar (新柱)事件处理程序·进阶篇
(2/3)· 老办法在实盘常漏柱漏算,这篇把通用类与事件处理拆开给你看
接上篇,我们继续深挖新柱探测。很多人在 EA 里照搬原型函数,实盘遇到强波动或网络延迟就漏掉新柱,导致交易逻辑少跑一轮。本文把通用方法和事件处理程序讲清楚,避开那些纸面好用的坑。
「用类实例摆脱新柱检测的调用次数限制」
原型 isNewBar() 靠静态变量 last_time 记录末根柱时间,导致同一个 MQL5 程序里多次调用会互相串扰。设想一个盯 12 个货币对的多币种 EA,若仍用静态版函数,光是理清各处的 last_time 归属就够让人头大。 面向对象的解法是把检测逻辑封进 CisNewBar 类,每个实例自带受保护变量 m_lastbar_time,内存独立分配。这样在 EA 或指标任意位置 new 出实例,调用 isNewBar() 都不会踩到别的实例的脚印,调用次数不再受限。 通用性上,方法改成 isNewBar(datetime newbar_time),由调用方传入当前未完成柱的开盘时间。对比本次传入时间与实例内存储的 m_lastbar_time,前者更大即判定新柱。该设计不绑定交易品种和周期,连 Renko、Kagi 或价格跳动图这类开盘时间错位的非标准图表也能覆盖。 类里顺手加了 m_retcode、m_new_bars、m_comment 三个成员追踪运行错误与计数,源码里首次调用(m_lastbar_time==0)只做初始化并返回 false,避免误报新柱。 若只做标准周期,可用 SeriesInfoInteger(SYMBOL(),PERIOD(),SERIES_LASTBAR_DATE) 让类内重载方法自动填 newbar_time,省去手算。构造函数默认绑当前图表品种与周期,也可以用 Set 方法切到别的品种/周期,或 SetLastBarTime() 重演首次调用场景。
class CisNewBar { class="kw">protected: class="type">class="kw">datetime m_lastbar_time; class=class="str">"cmt">// 最后柱线开盘时间 class="type">uint m_retcode; class=class="str">"cmt">// 判断新柱线的结果代码 class="type">int m_new_bars; class=class="str">"cmt">// 新柱线的数量 class="type">class="kw">string m_comment; class=class="str">"cmt">// 执行注释 class="kw">public: class="type">void CisNewBar(); class=class="str">"cmt">// CisNewBar 构造器 class=class="str">"cmt">//--- 判断新柱线方法: class="type">bool isNewBar(class="type">class="kw">datetime new_Time); class=class="str">"cmt">// 新柱线的第一次请求类型 }; class="type">bool CisNewBar::isNewBar(class="type">class="kw">datetime newbar_time) { m_new_bars = class="num">0; class=class="str">"cmt">// 新柱线的数量 m_retcode = class="num">0; class=class="str">"cmt">// 判断新柱线的结果代码: class="num">0 - 无错 m_comment =__FUNCTION__+" 成功检查新柱线"; if(m_lastbar_time>newbar_time) { m_comment=__FUNCTION__+" 同步错误: 前柱线时间 "+TimeToString(m_lastbar_time)+", 请求的新柱线时间 "+TimeToString(newbar_time); m_retcode=-class="num">1; class=class="str">"cmt">// 判断新柱线的结果代码: 返回 -class="num">1 - 同步错误 class="kw">return(false); } if(m_lastbar_time==class="num">0) { m_lastbar_time=newbar_time; m_comment =__FUNCTION__+" 初始化 lastbar_time = "+TimeToString(m_lastbar_time); class="kw">return(false); } if(m_lastbar_time<newbar_time) {
用 SERIES_LASTBAR_DATE 数清新柱子
第二种新柱判定走的是 SeriesInfoInteger 拿 SERIES_LASTBAR_DATE,比直接比时间更不容易踩坑。先 ResetLastError 把 _LastError 归零,再请求最后一根柱子的开盘时间;若返回 false,就把错误码写进 m_retcode 并退出,返回 0 表示本次未确认新柱。
拿到 newbar_time 后,仍会调一次 isNewBar(newbar_time) 做前置过滤,过了才用 Bars(m_symbol,m_period,lastbar_time,newbar_time)-1 算新柱数量。注意减 1:因为 Bars 统计的是两个时间之间的柱线总数,包含边界,减掉上一根才是「新产生」的根数。
构造函数里默认绑定当前图表的 Symbol() 与 Period(),m_lastbar_time 初值 0。提供 SetSymbol / SetPeriod / SetLastBarTime 做外部注入,也提供一组 Get* 只读方法取内部状态。
在 MT5 里把这段挂到 EA 的 OnTick,打印 GetNewBars(),你能直接看到每分钟(或每根bar)实际新出的根数,验证和手工数 K 线是否一致。外汇与贵金属波动剧烈,新柱计数仅作技术触发参考,实盘仍有滑点与时区错位风险。
m_new_bars=class="num">1; class=class="str">"cmt">// 新柱线的数量 m_lastbar_time=newbar_time; class=class="str">"cmt">// 记住最后柱线时间 class="kw">return(true); } class=class="str">"cmt">//--- class=class="str">"cmt">//--- 如果我们执行到此行, 则柱线不是新的; 返回 false class="kw">return(false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 新柱的第二种请求类型 | class=class="str">"cmt">//| INPUT: no. | class=class="str">"cmt">//| OUTPUT: m_new_bars - 新柱数量 | class=class="str">"cmt">//| REMARK: no. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CisNewBar::isNewBar() { class="type">class="kw">datetime newbar_time; class="type">class="kw">datetime lastbar_time=m_lastbar_time; class=class="str">"cmt">//--- 请求最后柱线开盘时间: ResetLastError(); class=class="str">"cmt">// 设置预定义变量 _LastError 为 class="num">0 if(!SeriesInfoInteger(m_symbol,m_period,SERIES_LASTBAR_DATE,newbar_time)) { class=class="str">"cmt">// 如果请求失败, 打印错误消息: m_retcode=GetLastError(); class=class="str">"cmt">// 判断新柱线的结果代码: 写变量值 _LastError m_comment=__FUNCTION__+" 当获取最后柱线开盘时间时错误: "+IntegerToString(m_retcode); class="kw">return(class="num">0); } class=class="str">"cmt">//--- class=class="str">"cmt">//---新柱线下次使用的第一次类型, 来完成分析: if(!isNewBar(newbar_time)) class="kw">return(class="num">0); class=class="str">"cmt">//---调整新柱线数量: m_new_bars=Bars(m_symbol,m_period,lastbar_time,newbar_time)-class="num">1; class=class="str">"cmt">//--- 如果我们到此, 则柱线是新的; 返回它们的数量 class="kw">return(m_new_bars); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CisNewBar 构造函数 | class=class="str">"cmt">//| INPUT: no. | class=class="str">"cmt">//| OUTPUT: no. | class=class="str">"cmt">//| REMARK: no. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CisNewBar::CisNewBar() { m_retcode=class="num">0; class=class="str">"cmt">// 判断新柱线的结果代码 m_lastbar_time=class="num">0; class=class="str">"cmt">// 最后柱线开盘时间 m_new_bars=class="num">0; class=class="str">"cmt">// 新柱线的数量 m_comment=""; class=class="str">"cmt">// 执行注释 m_symbol=Symbol(); class=class="str">"cmt">// 交易品种名, 缺省是当前图表交易品种 m_period=Period(); class=class="str">"cmt">// 图表周期, 缺省是当前图表周期 } class=class="str">"cmt">//--- 初始化保护数据的方法: class="type">void SetLastBarTime(class="type">class="kw">datetime lastbar_time){m_lastbar_time=lastbar_time; } class="type">void SetSymbol(class="type">class="kw">string symbol) {m_symbol=(symbol==NULL || symbol=="")?Symbol():symbol; } class="type">void SetPeriod(ENUM_TIMEFRAMES period) {m_period=(period==PERIOD_CURRENT)?Period():period; } class=class="str">"cmt">//--- 存取保护数据的方法: class="type">uint GetRetCode() const {class="kw">return(m_retcode); } class=class="str">"cmt">// 判断新柱线的结果代码 class="type">class="kw">datetime GetLastBarTime() const {class="kw">return(m_lastbar_time);} class=class="str">"cmt">// 最后柱线开盘时间 class="type">int GetNewBars() const {class="kw">return(m_new_bars); } class=class="str">"cmt">// 新柱线的数量 class="type">class="kw">string GetComment() const {class="kw">return(m_comment); } class=class="str">"cmt">// 执行注释 class="type">class="kw">string GetSymbol() const {class="kw">return(m_symbol); } class=class="str">"cmt">// 交易品种名 ENUM_TIMEFRAMES GetPeriod() const {class="kw">return(m_period); } class=class="str">"cmt">// 图表周期
◍ CisNewBar 类实战跑分与坑点
把 CisNewBar 直接挂当前图表跑,和老式 isNewBar() 函数对比最直观。M1 图表上两个 EA 同时报新柱,断网几分钟后重连,传统函数沉默,CisNewBar 的 isNewBar() 仍能纠正漏报——这点对掉线重连后补算指标很关键。 但类实例在构造时绑定了图表周期,改图表为 M2 后,Example1 里的 current_chart 仍按 M1 节奏每分钟报一次新柱(标注 2),CheckLastBar 则每 2 分钟报一次。自给自足不受环境干扰,是利是弊看任务。 想在周期切换时同步,得在 OnInit() 里手动初始化受保护变量 m_symbol 与 m_period。Example2 照做之后,M2 下 EA 与 CheckLastBar 完全同步,切交易品种也跟着变(标注 3),且断网重连后能报出漏掉的柱数量,相当于白送一个重算计数器。 类里内置了错误追踪。Example3 故意切品种折磨终端,捕获到 4 次运行期错误,下载完成后提示只忽略了 1 根柱。规律很简单:只要 isNewBar() 返回值 ≤0,就该查错误。周期由小改大(如 M1 切 H1)会触发同步错误,因为 H1 开盘时间早于 59 根 M1 柱,需用 SetLastBarTime() 在 OnInit() 妥善初始化 m_lastbar_time。 多货币对下(EURUSD M1 + GBPUSD M1 + USDJPY M2)跑 Example4,演示账户和策略测试器行为明显不同,能暴露 EA 缺陷。Example5 用 isNewBar(datetime) 显式探测价格跳动烛形图新柱,代码极短,证明这个类也能脱离主图表周期工作。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Example1NewBar.mq5 | class=class="str">"cmt">//| Copyright class="num">2010, Lizar | class=class="str">"cmt">//| Lizar-class="num">2010@mail.ru | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2010, Lizar" class="macro">#class="kw">property link "Lizar-class="num">2010@mail.ru" class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <Lib CisNewBar.mqh> CisNewBar current_chart; class=class="str">"cmt">// CisNewBar 类实例: 当前图表 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA订单函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- if(current_chart.isNewBar()>class="num">0) { PrintFormat("新柱线: %s",TimeToString(TimeCurrent(),TIME_SECONDS)); } } class="macro">#class="kw">property copyright "Copyright class="num">2010, Lizar" class="macro">#class="kw">property link "Lizar-class="num">2010@mail.ru" class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <Lib CisNewBar.mqh> CisNewBar newbar_ind; class=class="str">"cmt">// CisNewBar 类实例: 判断新即时价格蜡烛条 class="type">int HandleIndicator; class=class="str">"cmt">// 指标句柄 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 获取指标句柄: HandleIndicator=iCustom(_Symbol,_Period,"TickColorCandles v2.class="num">00",class="num">16,class="num">0,""); if(HandleIndicator==INVALID_HANDLE) { Alert(" 创建指标句柄时错误, 错误代码: ",GetLastError()); Print(" EA 初始化不正常. 交易不允许."); class="kw">return(class="num">1); } class=class="str">"cmt">//--- 图表挂载指标: if(!ChartIndicatorAdd(ChartID(),class="num">1,HandleIndicator)) { Alert(" 图表挂载指标时错误, 错误代码: ",GetLastError()); class="kw">return(class="num">1); }
「用 OnTick 抓指标新柱线时机」
EA 初始化通过后,真正的交易逻辑落在 OnTick 里。这里用 CopyBuffer 去读指标句柄的第 5 缓冲区(HandleIndicator, 5),只取 1 根数据,拿到最后未收盘蜡烛的开盘时间。若返回值 ≤0,说明本次取数失败,直接 Print 报错并 return,等下一 tick 再试。 判断新柱的核心靠 newbar_ind.isNewBar((datetime)iTime[0])。一旦指标侧确认新柱生成,就打印出该柱开盘时间与当前 TimeCurrent 的秒级时间,方便你核对 MT5 日志里指标和报价流是否同步。 实操上,把这段直接塞进自己的 EA 模板,把缓冲区编号 5 换成你用的指标实际输出位,开 EURUSD 的 M5 跑一晚,就能看到新柱触发频率。外汇与贵金属杠杆高,回测和实盘滑点差异大,任何信号都只是概率倾向,别当成确定性入口。
class=class="str">"cmt">//--- 如果您到达此处, 初始化成功 Print(" EA 初始化成功. 交易允许."); class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA订单函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- class="type">class="kw">double iTime[class="num">1]; class=class="str">"cmt">//--- 得到最后未收盘蜡烛条开盘时间: if(CopyBuffer(HandleIndicator,class="num">5,class="num">0,class="num">1,iTime)<=class="num">0) { Print(" 获取指标时间值失败. "+ "\n下次尝试获取指标值将在下次即时价到达.",GetLastError()); class="kw">return; } class=class="str">"cmt">//--- 判断下一个即时价蜡烛条: if(newbar_ind.isNewBar((class="type">class="kw">datetime)iTime[class="num">0])) { PrintFormat("新柱线. 开盘时间: %s 最后即时价到达时间: %s", TimeToString((class="type">class="kw">datetime)iTime[class="num">0],TIME_SECONDS), TimeToString(TimeCurrent(),TIME_SECONDS)); } }