DoEasy 函数库中的时间序列(第五十二部分):多周期、多品种单缓冲区标准指标的跨平台性质·进阶篇
📘

DoEasy 函数库中的时间序列(第五十二部分):多周期、多品种单缓冲区标准指标的跨平台性质·进阶篇

第 2/3 篇

枚举结构里的交易属性分类

这段源码骨架把 MT5 里和订单、持仓、风控相关的属性拆成一串枚举分组,虽然具体实现被省略,但标题列出来的分类本身就是接口设计的地图。 从 StopLoss/TakeProfit 层级设定,到期权类型、行权权利、保证金计算方式,再到隔夜 swap 收取逻辑,覆盖了建仓前后两条线。外汇与贵金属杠杆高,swap 与保证金算法不同券商差异大,实盘前应在 MT5 的规格窗口逐类核对。 后面连续列出交易操作类型、订单填充策略、订单存活时长,以及持仓的整数/实数/字符串三类属性,最后收在技术指标类型上。打开 MetaEditor 搜这些注释对应的 ENUM_ 宏,能直接看到每个分组的取值清单,比看文档快。

◍ 指标与绘图枚举的底层常量映射

在 MT5 自定义指标开发里,系统用一组连续整数枚举来指代内置指标类型。从 AC 的 5 到 CUSTOM 的 43,覆盖了 39 个内置指标加自定义槽位,写 EA 或指标时直接传这些常量比硬写字符串稳。 绘图样式另有一套枚举,MQL5 与 MQL4 的起始值并不一致。比如 DRAW_COLOR_LINE 在 MQL5 底层等于 1,而 MQL4 里是 0;DRAW_COLOR_HISTOGRAM 两边都是 2。跨版本移植代码时,若直接抄枚举数值容易错,应走宏名。 下面这段枚举定义可直接贴进 MQ5 文件编译,用来统一管指标 ID 与画线方式,省去每次查文档。外汇与贵金属波动剧烈、杠杆高风险大,任何指标映射错误都可能让信号逻辑整体偏移,上真实账户前务必在策略测试器跑一遍。

MQL5 / C++
enum ENUM_INDICATOR
  {
   IND_AC         = class="num">5,
   IND_AD         = class="num">6,
   IND_ALLIGATOR  = class="num">7,
   IND_ADX        = class="num">8,
   IND_ADXW       = class="num">9,
   IND_ATR        = class="num">10,
   IND_AO         = class="num">11,
   IND_BEARS      = class="num">12,
   IND_BANDS      = class="num">13,
   IND_BULLS      = class="num">14,
   IND_CCI        = class="num">15,
   IND_DEMARKER   = class="num">16,
   IND_ENVELOPES  = class="num">17,
   IND_FORCE      = class="num">18,
   IND_FRACTALS   = class="num">19,
   IND_GATOR      = class="num">20,
   IND_ICHIMOKU   = class="num">21,
   IND_BWMFI      = class="num">22,
   IND_MACD       = class="num">23,
   IND_MOMENTUM   = class="num">24,
   IND_MFI        = class="num">25,
   IND_MA         = class="num">26,
   IND_OSMA       = class="num">27,
   IND_OBV        = class="num">28,
   IND_SAR        = class="num">29,
   IND_RSI        = class="num">30,
   IND_RVI        = class="num">31,
   IND_STDDEV     = class="num">32,
   IND_STOCHASTIC = class="num">33,
   IND_VOLUMES    = class="num">34,
   IND_WPR        = class="num">35,
   IND_DEMA       = class="num">36,
   IND_TEMA       = class="num">37,
   IND_TRIX       = class="num">38,
   IND_FRAMA      = class="num">39,
   IND_AMA        = class="num">40,
   IND_CHAIKIN    = class="num">41,
   IND_VIDYA      = class="num">42,
   IND_CUSTOM     = class="num">43,
   };

enum ENUM_DRAW_TYPE
  {
   DRAW_COLOR_LINE = DRAW_LINE,           class=class="str">"cmt">// MQL5 = class="num">1, MQL4 = class="num">0
   DRAW_COLOR_HISTOGRAM = DRAW_HISTOGRAM, class=class="str">"cmt">// MQL5 = class="num">2, MQL4 = class="num">2

「从 MQL4 到 MQL5 的绘图枚举映射细节」

把老 MQL4 指标往 MQL5 搬时,绘图类型常量不是一一对应的。比如 DRAW_COLOR_HISTOGRAM2、DRAW_COLOR_BARS、DRAW_COLOR_CANDLES 在 MQL5 里统一落到 DRAW_NONE(数值 0),而在 MQL4 中它们对应的内部编号是 12;DRAW_COLOR_SECTION 在 MQL5 是 4、MQL4 是 1。直接照抄旧常量可能在 MT5 上完全不画东西。 成交量枚举只保留了两个选项:VOLUME_TICK 与 VOLUME_REAL,比早期客户端精简很多。写自定义指标时若依赖真实成交手数,必须显式选 VOLUME_REAL,否则默认 tick 量会扭曲贵金属品种的量价结构。 指标缓冲区对象提供了 IndicatorBarsCalculated() 这种内联方法,底层调的是 BarsCalculated 并传入隐藏的 indicator handle。开 MT5 随便挂一个系统指标,用类似封装读一下返回值,就能确认当前品种计算了多少根有效柱——外汇和贵金属杠杆高、跳空多,这个数和图表可见柱数可能不一致。

MQL5 / C++
DRAW_COLOR_ARROW = DRAW_ARROW,           class=class="str">"cmt">// MQL5 = class="num">3, MQL4 = class="num">3
DRAW_COLOR_SECTION = DRAW_SECTION,         class=class="str">"cmt">// MQL5 = class="num">4, MQL4 = class="num">1
DRAW_COLOR_HISTOGRAM2 = DRAW_NONE,         class=class="str">"cmt">// MQL5 = class="num">0, MQL4 = class="num">12
DRAW_COLOR_ZIGZAG = DRAW_ZIGZAG,           class=class="str">"cmt">// MQL5 = class="num">6, MQL4 = class="num">4
DRAW_COLOR_BARS = DRAW_NONE,               class=class="str">"cmt">// MQL5 = class="num">0, MQL4 = class="num">12
DRAW_COLOR_CANDLES = DRAW_NONE,            class=class="str">"cmt">// MQL5 = class="num">0, MQL4 = class="num">12
class=class="str">"cmt">// DRAW_FILLING                                        MQL4 = class="num">5
DRAW_COLOR_NONE = DRAW_NONE,               class=class="str">"cmt">// MQL5 = class="num">0, MQL4 = class="num">12
};
enum ENUM_APPLIED_VOLUME
  {
   VOLUME_TICK,
   VOLUME_REAL
  };
class="type">int IndicatorBarsCalculated(class="type">void) class="kw">const { class="kw">return ::BarsCalculated((class="type">int)this.GetProperty(BUFFER_PROP_IND_HANDLE)); }

从缓冲区对象抠出指标身份与计算进度

在 MT5 自建指标或 EA 里接管缓冲区时,CBuffer 类提供了一组只读方法,用来反查当前缓冲区的归属指标与绘制属性。下面这段声明直接暴露了它能拿到的字段:线型模式、指标类型、全称、短名、附加线编号,全部走 GetProperty 读内置属性,不碰外部变量。 IndicatorLineMode 返回 BUFFER_PROP_IND_LINE_MODE 转成的 int,指示该缓冲画的是线、柱还是点;IndicatorType 拿 BUFFER_PROP_IND_TYPE 映射成 ENUM_INDICATOR,能区分是 MACD 还是自定义指标;IndicatorName 与 IndicatorShortName 分别取全名和短名,SubWindow 里排重或日志追踪时很实用。 真正影响计算节奏的是 IndicatorBarsCalculated。MQL5 分支调 BarsCalculated 并传入指标句柄,返回该指标已算出的 bar 数;MQL4 分支退化成当前品种周期的总 Bars 数。若返回 0 或 -1,说明句柄无效或数据未就绪,后续拷贝缓冲区可能读到空值。 构造函数 CBuffer(...) 用闭合参数表锁定缓冲区状态、类型、plot 索引、基础数组索引、数据量和总数组数,意味着一个 buffer 对象在诞生时就绑定了存储结构,运行期改不了这些底层设定。外汇与贵金属波动剧烈,这类底层对象若句柄失效,EA 可能误判行情,实盘前务必在策略测试器里跑一遍句柄生命周期。

MQL5 / C++
class="type">int IndicatorLineMode(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_IND_LINE_MODE); }
ENUM_INDICATOR IndicatorType(class="type">void) class="kw">const { class="kw">return (ENUM_INDICATOR)this.GetProperty(BUFFER_PROP_IND_TYPE); }
class="type">class="kw">string IndicatorName(class="type">void) class="kw">const { class="kw">return this.GetProperty(BUFFER_PROP_IND_NAME); }
class="type">class="kw">string IndicatorShortName(class="type">void) class="kw">const { class="kw">return this.GetProperty(BUFFER_PROP_IND_NAME_SHORT); }
class="type">int IndicatorLineAdditionalNumber(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_IND_LINE_ADDITIONAL_NUM); }
class="type">int IndicatorLineMode(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_IND_LINE_MODE); }
class="type">int IndicatorBarsCalculated(class="type">void);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the number of standard indicator calculated bars          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CBuffer::IndicatorBarsCalculated(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::BarsCalculated((class="type">int)this.GetProperty(BUFFER_PROP_IND_HANDLE)) class="macro">#else ::Bars(this.Symbol(),this.Timeframe()) class="macro">#endif);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Closed parametric constructor                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status,
                 ENUM_BUFFER_TYPE buffer_type,
                 class="kw">const class="type">uint index_plot,
                 class="kw">const class="type">uint index_base_array,
                 class="kw">const class="type">int num_datas,
                 class="kw">const class="type">uchar total_arrays,

◍ 指标缓冲区的构造与绘制类型推导

在 MT5 自定义指标里,缓冲区对象的构造函数负责把一组整数属性一次性落位。上面这段逻辑出现在某缓冲区类的初始化中,先通过 __MQL5__ 宏隔离 MQL5 专属分支,把集合类型标记为 COLLECTION_BUFFERS_ID,并打开激活触发开关 m_act_state_trigger。 随后用 m_long_prop 数组保存状态、类型、ID 等基础字段:BUFFER_PROP_ID 先填 WRONG_VALUE,主线模式默认 INDICATOR_LINE_MODE_MAIN,句柄留 INVALID_HANDLE。这些占位值会在后续绑定真实指标时改写,开 MT5 调试时若看到缓冲区不画线,优先查这几个字段是否仍为负值。 绘制类型 type 的推导值得细看:若类型或状态为空则 DRAW_NONE;状态是 BUFFER_STATUS_FILLING 时走 DRAW_FILLING(带状填充);否则把状态值加 8 强转成 ENUM_DRAW_TYPE。也就是说,状态枚举和绘图枚举在数值上偏移了 8,调参时别直接把状态当绘图类型用。 线宽由入参 width 决定,箭头代码默认 0x9F(即 Wingdings 里的向下箭头字形),偏移 0、起始绘制位 0。SHOW_DATA 仅在 buffer_type 大于 BUFFER_TYPE_CALCULATE 时才为真,COLOR_INDEXES 则按状态分 0/1/2 三档——填充态占两色,普通线占一色,无状态不占色。外汇与贵金属行情跳空频繁,这类缓冲区若用于实时 EA,需自测重绘开销,高风险环境别盲目堆缓冲层数。

MQL5 / C++
   class="kw">const class="type">int width,
   class="kw">const class="type">class="kw">string label)
  {
class="macro">#ifdef __MQL5__
   this.m_type=COLLECTION_BUFFERS_ID;
   this.m_act_state_trigger=true;
   this.m_total_arrays=total_arrays;
class=class="str">"cmt">//--- Save integer properties
   this.m_long_prop[BUFFER_PROP_STATUS]                       = buffer_status;
   this.m_long_prop[BUFFER_PROP_TYPE]                         = buffer_type;
   this.m_long_prop[BUFFER_PROP_ID]                           = WRONG_VALUE;
   this.m_long_prop[BUFFER_PROP_IND_LINE_MODE]                = INDICATOR_LINE_MODE_MAIN;
   this.m_long_prop[BUFFER_PROP_IND_HANDLE]                  = INVALID_HANDLE;
   this.m_long_prop[BUFFER_PROP_IND_TYPE]                    = WRONG_VALUE;
   this.m_long_prop[BUFFER_PROP_IND_LINE_ADDITIONAL_NUM]     = WRONG_VALUE;
   ENUM_DRAW_TYPE type=
     (
       !this.TypeBuffer() || !this.Status() ? DRAW_NONE      :
       this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING   :
       ENUM_DRAW_TYPE(this.Status()+class="num">8)
     );
   this.m_long_prop[BUFFER_PROP_DRAW_TYPE]                   = type;
   this.m_long_prop[BUFFER_PROP_TIMEFRAME]                   = PERIOD_CURRENT;
   this.m_long_prop[BUFFER_PROP_ACTIVE]                      = true;
   this.m_long_prop[BUFFER_PROP_ARROW_CODE]                  = 0x9F;
   this.m_long_prop[BUFFER_PROP_ARROW_SHIFT]                 = class="num">0;
   this.m_long_prop[BUFFER_PROP_DRAW_BEGIN]                  = class="num">0;
   this.m_long_prop[BUFFER_PROP_SHOW_DATA]                   = (buffer_type>BUFFER_TYPE_CALCULATE ? true : false);
   this.m_long_prop[BUFFER_PROP_SHIFT]                       = class="num">0;
   this.m_long_prop[BUFFER_PROP_LINE_STYLE]                  = STYLE_SOLID;
   this.m_long_prop[BUFFER_PROP_LINE_WIDTH]                  = width;
   this.m_long_prop[BUFFER_PROP_COLOR_INDEXES]               = (this.Status()>BUFFER_STATUS_NONE ? (this.Status()!=BUFFER_STATUS_FILLING ? class="num">1 : class="num">2) : class="num">0);

「正文」

&nbsp;&nbsp; <span class="keyword">this</span>.m_long_prop[BUFFER_PROP_COLOR]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="macro">clrRed</span>; &nbsp;&nbsp; <span class="keyword">this</span>.m_long_prop[BUFFER_PROP_NUM_DATAS]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = num_datas; &nbsp;&nbsp; <span class="keyword">this</span>.m_long_prop[BUFFER_PROP_INDEX_PLOT]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= index_plot; &nbsp;&nbsp; <span class="keyword">this</span>.m_long_prop[BUFFER_PROP_INDEX_BASE]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= index_base_array; &nbsp;&nbsp; <span class="keyword">this</span>.m_long_prop[BUFFER_PROP_INDEX_COLOR]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="keyword">this</span>.GetProperty(BUFFER_PROP_INDEX_BASE)+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp

常见问题

用枚举结构把交易属性(品种、周期、指标类型)抽成常量映射,避免硬编码,方便后续按属性批量筛选。
建立底层常量映射表,把旧平台绘图枚举逐条对应到新平台枚举,在初始化时做一次转换即可。
可以,小布能读取缓冲区对象里的计算进度字段,跨品种扫描后直接列出未同步或卡住的指标。
通过缓冲区对象暴露的元数据和进度属性直接读取,不需要外部记录,拿身份常量比对枚举就行。
根据枚举里的绘制类型推导规则,用缓冲区声明时的类型标签自动匹配线型或柱状,不必手动指定。