New Bar (新柱)事件处理程序·进阶篇
⏱️

New Bar (新柱)事件处理程序·进阶篇

(2/3)· 老办法在实盘常漏柱漏算,这篇把通用类与事件处理拆开给你看

新手友好 第 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() 重演首次调用场景。

MQL5 / C++
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 数清新柱子

第二种新柱判定走的是 SeriesInfoIntegerSERIES_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 线是否一致。外汇与贵金属波动剧烈,新柱计数仅作技术触发参考,实盘仍有滑点与时区错位风险。

MQL5 / C++
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) 显式探测价格跳动烛形图新柱,代码极短,证明这个类也能脱离主图表周期工作。

MQL5 / C++
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 跑一晚,就能看到新柱触发频率。外汇与贵金属杠杆高,回测和实盘滑点差异大,任何信号都只是概率倾向,别当成确定性入口。

MQL5 / C++
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));
 }
}
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到新柱触发与计算耗时,把重复劳动交给小布,你专注决策。

常见问题

这两个参数是 OnCalculate 的局部输入,仅反映当前图表数据变化,跨周期或跨品种必须改用时间追踪或 TimeCurrent 等技术。
第一次与第二次跳动间隔可能极短,强劲行情或差网络下来不及捕获生成柱,导致漏判,仅适合测试环境。
它封装了通用新柱探测逻辑,避免复制代码带来的边界错误,并支持更灵活的周期与品种检查。
可以,小布盯盘对应品种页已内置 AIGC 诊断,新柱触发与计算循环次数都能直观看到,省去自己写日志。
通常在 OnTick 中先做新柱判断再分发到 OnNewBar,确保每根柱逻辑只执行一次,降低重复开销。