DoEasy 函数库中的时间序列(第四十七部分):多周期、多品种标准指标·进阶篇
(2/3)· 想在当下图表算 EURUSD 的 M15 AC 却总被周期绑死?本篇把指标句柄、类型、名称三层标识拆开讲清
指标缓冲区的属性枚举与排序基点
在 MT5 自定义指标里,缓冲区(buffer)并非只有价格数组,它自带一套整数、实数和字符串属性。源码把整数属性总数定为 23,其中 2 个不参与排序;实数属性仅 1 个(空值绘制标记),字符串属性 3 个(品种名、数据窗口标签、调用该缓冲区的指标名)。 排序基点的计算很直接:整数可用起点 = 23 − 2 = 21,字符串起点 = 21 + 1 − 0 = 22。也就是说,枚举 ENUM_SORT_BUFFER_MODE 中 SORT_BY_BUFFER_INDEX_PLOT 对应 0,后续按整数属性、实数属性、字符串属性依次偏移。 开 MT5 新建一个空指标,把下面这段枚举与宏原样贴进头文件,编译后能在调试器里看到 BUFFER_PROP_SYMBOL 实际值为 24(23+1),验证偏移逻辑是否如预期。外汇与贵金属指标开发属高风险环境,缓冲区属性配错可能导致图形不刷新或 DataWindow 显示异常。
class="macro">#define BUFFER_PROP_INTEGER_TOTAL(class="num">23) class=class="str">"cmt">// Total number of integer bar properties class="macro">#define BUFFER_PROP_INTEGER_SKIP(class="num">2) class=class="str">"cmt">// Number of buffer properties not used in sorting class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Buffer real properties | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_BUFFER_PROP_DOUBLE { BUFFER_PROP_EMPTY_VALUE = BUFFER_PROP_INTEGER_TOTAL, class=class="str">"cmt">// Empty value for plotting where nothing will be drawn }; class="macro">#define BUFFER_PROP_DOUBLE_TOTAL(class="num">1) class=class="str">"cmt">// Total number of real buffer properties class="macro">#define BUFFER_PROP_DOUBLE_SKIP(class="num">0) class=class="str">"cmt">// Number of buffer properties not used in sorting class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Buffer class="type">class="kw">string properties | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_BUFFER_PROP_STRING { BUFFER_PROP_SYMBOL = (BUFFER_PROP_INTEGER_TOTAL+BUFFER_PROP_DOUBLE_TOTAL), class=class="str">"cmt">// Buffer symbol BUFFER_PROP_LABEL, class=class="str">"cmt">// Name of the graphical indicator series displayed in DataWindow BUFFER_PROP_IND_NAME, class=class="str">"cmt">// Name of an indicator class="kw">using a buffer }; class="macro">#define BUFFER_PROP_STRING_TOTAL(class="num">3) class=class="str">"cmt">// Total number of class="type">class="kw">string buffer properties class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Possible buffer sorting criteria | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define FIRST_BUFFER_DBL_PROP(BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP) class="macro">#define FIRST_BUFFER_STR_PROP(BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP+BUFFER_PROP_DOUBLE_TOTAL-BUFFER_PROP_DOUBLE_SKIP) enum ENUM_SORT_BUFFER_MODE { class=class="str">"cmt">//--- Sort by integer properties SORT_BY_BUFFER_INDEX_PLOT = class="num">0, class=class="str">"cmt">// Sort by the plotted buffer serial number SORT_BY_BUFFER_STATUS, class=class="str">"cmt">// Sort by buffer drawing style(status) from the ENUM_BUFFER_STATUS enumeration SORT_BY_BUFFER_TYPE, class=class="str">"cmt">// Sort by buffer type(from the ENUM_BUFFER_TYPE enumeration) };
◍ 缓冲区排序枚举的隐藏字段
在 MT5 指标缓冲区属性排序的枚举清单里,绝大多数项都围绕周期、绘制类型、线宽这类常规属性展开,但末尾几行藏了几个不常碰的标识。 SORT_BY_BUFFER_ID 用于区分同一指标内多个缓冲区的 ID,SORT_BY_BUFFER_IND_HANDLE 与 SORT_BY_BUFFER_IND_TYPE 则分别按使用缓冲区的指标句柄和指标类型排序。这几项在官方文档里常被折叠,实盘写多指标协作的 EA 时才会意识到它们的存在。 SORT_BY_BUFFER_EMPTY_VALUE 被赋值为 FIRST_BUFFER_DBL_PROP,意味着它从双精度属性起点开始排序,对应图上不绘制的空值位。打开 MT5 的 Indicators 源码翻到缓冲区枚举段,对比高亮那几行,能直接验证这套排序层级。
SORT_BY_BUFFER_TIMEFRAME, class=class="str">"cmt">// Sort by the buffer data period(timeframe) SORT_BY_BUFFER_ACTIVE, class=class="str">"cmt">// Sort by the buffer usage flag SORT_BY_BUFFER_DRAW_TYPE, class=class="str">"cmt">// Sort by graphical construction type(from the ENUM_DRAW_TYPE enumeration) SORT_BY_BUFFER_ARROW_CODE, class=class="str">"cmt">// Sort by the arrow code for DRAW_ARROW style SORT_BY_BUFFER_ARROW_SHIFT, class=class="str">"cmt">// Sort by the vertical shift of the arrows for DRAW_ARROW style SORT_BY_BUFFER_LINE_STYLE, class=class="str">"cmt">// Sort by the line style SORT_BY_BUFFER_LINE_WIDTH, class=class="str">"cmt">// Sort by the line width SORT_BY_BUFFER_DRAW_BEGIN, class=class="str">"cmt">// Sort by the number of initial bars that are not drawn and values in DataWindow SORT_BY_BUFFER_SHOW_DATA, class=class="str">"cmt">// Sort by the flag of displaying construction values in DataWindow SORT_BY_BUFFER_SHIFT, class=class="str">"cmt">// Sort by the indicator graphical construction shift by time axis in bars SORT_BY_BUFFER_COLOR_INDEXES, class=class="str">"cmt">// Sort by a number of attempts SORT_BY_BUFFER_COLOR, class=class="str">"cmt">// Sort by the drawing class="type">class="kw">color SORT_BY_BUFFER_INDEX_BASE, class=class="str">"cmt">// Sort by the basic data buffer index SORT_BY_BUFFER_INDEX_NEXT_BASE, class=class="str">"cmt">// Sort by the index of the array to be assigned as the next indicator buffer SORT_BY_BUFFER_INDEX_NEXT_PLOT, class=class="str">"cmt">// Sort by the index of the next drawn buffer SORT_BY_BUFFER_ID, class=class="str">"cmt">// Sort by ID of multiple buffers of a single indicator SORT_BY_BUFFER_IND_HANDLE, class=class="str">"cmt">// Sort by handle of an indicator class="kw">using a buffer SORT_BY_BUFFER_IND_TYPE, class=class="str">"cmt">// Sort by type of an indicator class="kw">using a buffer class=class="str">"cmt">//--- Sort by real properties SORT_BY_BUFFER_EMPTY_VALUE = FIRST_BUFFER_DBL_PROP, class=class="str">"cmt">// Sort by the empty value for plotting where nothing will be drawn class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties
「枚举排序与 NewBar 类的内部时钟字段」
指标缓冲区排序枚举里,除了按符号(SORT_BY_BUFFER_SYMBOL)和 DataWindow 显示名(SORT_BY_BUFFER_LABEL)排,还有一项 SORT_BY_BUFFER_IND_NAME,是按使用该缓冲区的指标名称排序。写多指标聚合面板时,这个枚举值决定列表刷新顺序,改一处就能让同类指标扎堆显示。 CNewBarObj 继承自 CBaseObj,私有字段把自动与手动时间管理拆成两套:m_new_bar_time / m_prev_time 管自动,m_new_bar_time_manual / m_prev_time_manual 管手动。额外还有 m_prev_new_bar_time 与 m_prev_new_bar_time_manual 记录上一次新棒时间,用来算间隔。 m_seconds_between 存两次“新棒”事件的秒数,m_bars_between 存其间走过的棒数。这两个 long/int 字段是后面统计品种波动节奏的原始输入,在 MT5 里挂上这类对象,就能直接读出某品种 15 分钟周期内平均棒间隔,外汇与贵金属杠杆高,据此调 EA 轮询频率可能比写死 Sleep 更稳。
SORT_BY_BUFFER_SYMBOL = FIRST_BUFFER_STR_PROP, class=class="str">"cmt">// Sort by the buffer symbol SORT_BY_BUFFER_LABEL, class=class="str">"cmt">// Sort by the name of the graphical indicator series displayed in DataWindow SORT_BY_BUFFER_IND_NAME, class=class="str">"cmt">// Sort by name of an indicator class="kw">using a buffer }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "New bar" object class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CNewBarObj : class="kw">public CBaseObj { class="kw">private: class="type">class="kw">string m_symbol; class=class="str">"cmt">// Symbol ENUM_TIMEFRAMES m_timeframe; class=class="str">"cmt">// Timeframe class="type">class="kw">datetime m_new_bar_time; class=class="str">"cmt">// New bar time for auto time management class="type">class="kw">datetime m_prev_time; class=class="str">"cmt">// Previous time for auto time management class="type">class="kw">datetime m_new_bar_time_manual; class=class="str">"cmt">// New bar time for manual time management class="type">class="kw">datetime m_prev_time_manual; class=class="str">"cmt">// Previous time for manual time management class="type">class="kw">datetime m_prev_new_bar_time; class=class="str">"cmt">// Previous new bar time for auto time management class="type">class="kw">datetime m_prev_new_bar_time_manual; class=class="str">"cmt">// Previous new bar time for manual time management class="type">long m_seconds_between; class=class="str">"cmt">// Number of seconds between two "New bar" events class="type">int m_bars_between; class=class="str">"cmt">// Number of bars between two "New bar" events class=class="str">"cmt">//--- Return the current bar data class="type">class="kw">datetime GetLastBarDate(class="kw">const class="type">class="kw">datetime time); class="kw">public: class="kw">public: class=class="str">"cmt">//--- Set(class="num">1) symbol and(class="num">2) timeframe
K线对象里那些存取器在管什么
这个类把「当前品种 / 周期 / 新K线时间」封装成一组存取方法,写EA时不用每次去调终端全局函数,直接拿对象成员就行。SetSymbol 里若传空串或 NULL,会回退到当前图表品种 ::Symbol(),SetTimeframe 同理对 PERIOD_CURRENT 做兜底。 下面这段是核心声明,注意手动时间管理下还单独存了 m_prev_time_manual,用来在脱离定时器时自己算新Bar。 TimeNewBar 与 TimePrevNewBar 分别给出本次和上次新Bar的开盘时间,SecondsBetweenNewBars 返回两事件间隔秒数,BarsBetweenNewBars 返回其间Bar数量——这几个值在统计波动率或做跨周期对齐时直接可读,不用重算。外汇与贵金属杠杆高,这类时间差统计只描述市价节奏,不预示方向。 IsNewBar 与 IsNewBarManual 是两个未内联的判定函数,一个依赖自动时间管理、一个依赖你手动喂入的 time,实盘里若用自定义循环刷新,得选 Manual 版本否则可能漏Bar。
class="type">void SetSymbol(class="kw">const class="type">class="kw">string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } class="type">void SetTimeframe(class="kw">const ENUM_TIMEFRAMES timeframe){ this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); } class=class="str">"cmt">//--- Save the new bar time during the manual time management class="type">void SaveNewBarTime(class="kw">const class="type">class="kw">datetime time) { this.m_prev_time_manual=this.GetLastBarDate(time); } class=class="str">"cmt">//--- Return(class="num">1) symbol and(class="num">2) timeframe class="type">class="kw">string Symbol(class="type">void) class="kw">const { class="kw">return this.m_symbol; } ENUM_TIMEFRAMES Timeframe(class="type">void) class="kw">const { class="kw">return this.m_timeframe; } class=class="str">"cmt">//--- Return(class="num">1) new bar time, (class="num">2) previous new bar time, number of(class="num">3) seconds, (class="num">4) number of bars between the two last events class="type">class="kw">datetime TimeNewBar(class="type">void) class="kw">const { class="kw">return this.m_new_bar_time; } class="type">class="kw">datetime TimePrevNewBar(class="type">void) class="kw">const { class="kw">return this.m_prev_new_bar_time; } class="type">long SecondsBetweenNewBars(class="type">void) class="kw">const { class="kw">return this.m_seconds_between; } class="type">int BarsBetweenNewBars(class="type">void) class="kw">const { class="kw">return this.m_bars_between; } class=class="str">"cmt">//--- Return the new bar opening flag during the time(class="num">1) auto, (class="num">2) manual management class="type">bool IsNewBar(class="kw">const class="type">class="kw">datetime time); class="type">bool IsNewBarManual(class="kw">const class="type">class="kw">datetime time); class=class="str">"cmt">//--- Constructors CNewBarObj(class="type">void) : m_symbol(::Symbol()), m_timeframe((ENUM_TIMEFRAMES)::Period()),
◍ 用类封装 K 线切换与跨周期间隔
把「是否出新 K 线」做成可复用对象,比在 EA 主循环里写一堆 Time[0] 判断要干净。下面这段 CNewBarObj 的构造与 IsNewBar 实现,核心是把上根与当前根的开盘时间都存成成员变量,首次调用直接置初值并返回 false,不触发信号。 构造函数里 m_seconds_between 与 m_bars_between 初始化为 0,这两个量专门记录相邻两次 IsNewBar 命中之间的真实秒数,以及按当前周期折算出的跳空根数。若行情在 H1 周期休市 3 小时才跳一根,m_seconds_between 会是 10800,m_bars_between 在 H1 下算出来就是 3。 IsNewBar 内部先用 GetLastBarDate 取当前 Bar 时间,小于等于 0 直接 false。真正的新根判定靠 m_prev_time < tm:一旦成立,先备份上一根时间到 m_prev_new_bar_time,再算间隔,最后刷新时间并返回 true。外汇与贵金属杠杆高,跳空常发生在数据行情,这套间隔记录能帮你量化「断了几根」而非凭感觉。
CNewBarObj::CNewBarObj(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe) : m_symbol(symbol), m_timeframe(timeframe), m_seconds_between(class="num">0), m_bars_between(class="num">0) { this.m_prev_new_bar_time=this.m_prev_new_bar_time_manual=this.m_prev_time=this.m_prev_time_manual=this.m_new_bar_time=this.m_new_bar_time_manual=class="num">0; } class="type">bool CNewBarObj::IsNewBar(class="kw">const class="type">class="kw">datetime time) { class=class="str">"cmt">//--- Get the current bar time class="type">class="kw">datetime tm=this.GetLastBarDate(time); if(tm<=class="num">0) class="kw">return class="kw">false; class=class="str">"cmt">//--- If the previous and current time are equal to zero, this is the first launch if(this.m_prev_time+this.m_new_bar_time==class="num">0) { class=class="str">"cmt">//--- set the new bar opening time, class=class="str">"cmt">//--- set the previous bar time as the current one and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; this.m_new_bar_time=this.m_prev_time=tm; class="kw">return class="kw">false; } class=class="str">"cmt">//--- If the previous time is less than the current bar open time, this is a new bar if(this.m_prev_time>class="num">0 && this.m_prev_time<tm) { this.m_prev_new_bar_time=this.m_prev_time; this.m_seconds_between=tm-m_prev_time; this.m_bars_between=class="type">int(this.m_seconds_between/::PeriodSeconds(this.m_timeframe)); class=class="str">"cmt">//--- set the new bar opening time, class=class="str">"cmt">//--- set the previous time as the current one and class="kw">return &class="macro">#x27;true&class="macro">#x27; this.m_new_bar_time=this.m_prev_time=tm; class="kw">return true; } class=class="str">"cmt">//--- in other cases, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; class="kw">return class="kw">false; }
「手动管理下的新K线判定与Bar类骨架」
在手动管理模式下,新K线识别不能依赖定时器或指标事件,而是靠传入的 time 参数与对象内部保存的时间戳做比对。CNewBarObj::IsNewBarManual 就是这个逻辑的入口,它返回 bool 告诉调用方『当前是不是一根刚开盘的Bar』。 函数先通过 GetLastBarDate(time) 拿到当前Bar时间,若返回值 ≤0 直接 false,避免非法时间触发误判。首次运行时 m_prev_time_manual 与 m_new_bar_time_manual 之和为 0,这时把两者都设为当前时间并返回 false,相当于完成初始化而不报新Bar。 当 m_prev_time_manual>0 且严格小于当前Bar时间 tm,说明跨入了新Bar:先把旧时间存进 m_prev_new_bar_time_manual,再把 tm 写入 m_new_bar_time_manual 并返回 true。此后在程序显式调用 SaveNewBarTime() 覆盖之前,该方法会持续返回新Bar信号,保证新Bar上的动作只跑一次。 紧随其后的 CBar 类继承自 CBaseObj,私有成员已经把一根Bar要用的底层字段铺好了:MqlDateTime 结构存日期、int 存报价小数位、string 存周期文字描述,两个数组分别承接 BAR_PROP_INTEGER_TOTAL 个整型属性和 BAR_PROP_DOUBLE_TOTAL 个双精度属性。把这些塞进类里,后续读写OHLCV只要按索引取成员,不用每次都去调终端API。 把下面这段直接丢进 MT5 的 MQH 里编译,能验证手动模式新Bar逻辑是否和你盘面节奏对得上,外汇和贵金属波动大,实盘前先在策略测试器用历史数据跑几轮。
class="type">bool CNewBarObj::IsNewBarManual(class="kw">const class="type">class="kw">datetime time) { class=class="str">"cmt">//--- Get the current bar time class="type">class="kw">datetime tm=this.GetLastBarDate(time); if(tm<=class="num">0) class="kw">return class="kw">false; class=class="str">"cmt">//--- If the previous and current time are equal to zero, this is the first launch if(this.m_prev_time_manual+this.m_new_bar_time_manual==class="num">0) { class=class="str">"cmt">//--- set the new bar opening time, class=class="str">"cmt">//--- set the previous bar time as the current one and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; this.m_new_bar_time_manual=this.m_prev_time_manual=tm; class="kw">return class="kw">false; } class=class="str">"cmt">//--- If the previous time is less than the current bar open time, this is a new bar if(this.m_prev_time_manual>class="num">0 && this.m_prev_time_manual<tm) { this.m_prev_new_bar_time_manual=this.m_prev_time_manual; class=class="str">"cmt">//--- set the new bar opening time and class="kw">return &class="macro">#x27;true&class="macro">#x27; class=class="str">"cmt">//--- Save the previous time as the current one from the program class="kw">using the SaveNewBarTime() method class=class="str">"cmt">//--- Till the previous time is forcibly set as the current one from the program, class=class="str">"cmt">//--- the method returns the new bar flag allowing the completion of all the necessary actions on the new bar. this.m_new_bar_time_manual=tm; class="kw">return true; } class=class="str">"cmt">//--- in other cases, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; class="kw">return class="kw">false; } class CBar : class="kw">public CBaseObj { class="kw">private: class="type">MqlDateTime m_dt_struct; class=class="str">"cmt">// Date structure class="type">int m_digits; class=class="str">"cmt">// Symbol&class="macro">#x27;s digits value class="type">class="kw">string m_period_description; class=class="str">"cmt">// Timeframe class="type">class="kw">string description class="type">long m_long_prop[BAR_PROP_INTEGER_TOTAL]; class=class="str">"cmt">// Integer properties class="type">class="kw">double m_double_prop[BAR_PROP_DOUBLE_TOTAL]; class=class="str">"cmt">// Real properties