创建多交易品种、多周期指标·进阶篇
多周期多品种指标的基类骨架
想在 MT5 里同时盯多个品种、多个周期的指标,第一步是把底层结构搭清楚。下面这段基类 CIndMSTF 直接继承 CObject,本身不画任何线,只负责给后续具体指标提供统一的对象容器与生命周期管理。 构造函数与析构函数留空实现,意味着子类要自己接管指标句柄的创建和释放。真正决定运行节奏的是两个定时器宏:TIMER_COUNT_1 设为 90,注释里写明不能超过 120(即两分钟);TIMER_COUNT_2 设为 20,原注释提示值太小会在活跃行情里频繁触发分时模拟,实战中黄金或欧元美元跳动快时容易拖慢回测。 类别与状态用枚举固化下来。ENUM_IND_CATEGORY 把趋势、震荡、成交量、比尔·威廉姆斯及自定义分成 6 类;ENUM_LINE_STATE 则定义了 14 种线态,从单纯向上/向下到穿越、触碰、等于某值都单列,方便后续做状态机判断而不是写一堆 if 比较价格。 比较模式枚举 ENUM_COMPARE_MODE 默认零值比全部属性,也可只按句柄、品种、周期、ID、描述或类别来比。开 MT5 把这段头文件塞进自己的 include 目录,先改 TIMER_COUNT_2 到 30~50 之间跑一晚欧美盘,感受下定时模拟频率对 CPU 占用的差异。
class=class="str">"cmt">//| Base class of the multi-symbol multi-period indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndMSTF : class="kw">public CObject { class="kw">private: class="kw">protected: class="kw">public: class=class="str">"cmt">//--- Constructor/destructor CIndMSTF(); ~CIndMSTF(); }; class=class="str">"cmt">//--- includes class="macro">#include <Object.mqh> class=class="str">"cmt">//--- defines class="macro">#define TIMER_COUNT_1(class="num">90) class=class="str">"cmt">// Timer class="num">1 size. Must not be more than two minutes(class="num">120) class="macro">#define TIMER_COUNT_2(class="num">20) class=class="str">"cmt">// Timer class="num">2 size. Too small values quickly trigger tick emulation, which is not desirable in an active market class=class="str">"cmt">//--- enums enum ENUM_IND_CATEGORY class=class="str">"cmt">// Indicator categories { IND_CATEGORY_NONE, class=class="str">"cmt">// Not set IND_CATEGORY_TREND, class=class="str">"cmt">// Trend IND_CATEGORY_OSCILLATOR, class=class="str">"cmt">// Oscillators IND_CATEGORY_VOLUME, class=class="str">"cmt">// Volume IND_CATEGORY_WILLIAMS, class=class="str">"cmt">// Bill Williams IND_CATEGORY_CUSTOM, class=class="str">"cmt">// Custom }; enum ENUM_COMPARE_MODE class=class="str">"cmt">// Comparison mode { class=class="str">"cmt">// By class="kw">default, the comparison mode is set to zero, which compares all properties COMPARE_MODE_HANDLE=class="num">1, class=class="str">"cmt">// Compare by handle COMPARE_MODE_SYMBOL, class=class="str">"cmt">// Compare by symbol COMPARE_MODE_TIMEFRAME, class=class="str">"cmt">// Compare by chart period COMPARE_MODE_ID, class=class="str">"cmt">// Compare by ID COMPARE_MODE_DESCRIPTION, class=class="str">"cmt">// Compare by custom description COMPARE_MODE_CATEGORY, class=class="str">"cmt">// Compare by category }; enum ENUM_LINE_STATE class=class="str">"cmt">// Indicator line state { LINE_STATE_NONE, class=class="str">"cmt">// Undefined LINE_STATE_UP, class=class="str">"cmt">// Upward LINE_STATE_DOWN, class=class="str">"cmt">// Downward LINE_STATE_TURN_UP, class=class="str">"cmt">// Upward reversal LINE_STATE_TURN_DOWN, class=class="str">"cmt">// Downward reversal LINE_STATE_STOP_UP, class=class="str">"cmt">// Upward stop LINE_STATE_STOP_DOWN, class=class="str">"cmt">// Downward stop LINE_STATE_ABOVE, class=class="str">"cmt">// Above value LINE_STATE_BELOW, class=class="str">"cmt">// Below value LINE_STATE_CROSS_UP, class=class="str">"cmt">// Upward value crossing LINE_STATE_CROSS_DOWN, class=class="str">"cmt">// Downward value crossing LINE_STATE_TOUCH_BELOW, class=class="str">"cmt">// Touching value from below LINE_STATE_TOUCH_ABOVE, class=class="str">"cmt">// Touching value from above LINE_STATE_EQUALS, class=class="str">"cmt">// Equal to value }; enum ENUM_ERR_TYPE class=class="str">"cmt">// Indicator calculation error type {
「指标缓冲区的错误态与结构体封装」
在自研 MT5 指标里,先把缓冲区可能出的岔子归类,比事后查 bug 更省时间。下面这组枚举把四种典型异常列清楚:0 号是无错误,1 号代表数据未同步(多发生在跨周期读取刚切换品种时),2 号是数据根本没加载,3 号是计算没跑完——实盘里若缓冲区返回 1 或 3,硬画线会出幽灵信号。 真正管缓冲区的是 SBuffer 结构。它把数组、初值、水平偏移、描述塞进一个包里:array[] 存指标值,init_value 决定空值填充什么,shift 控制整条线左右挪几根 K,descript 方便在数据窗口里辨认。 结构里顺手写了几个内联方法。SetInitValue / InitValue 管初值读写,SetShift / Shift 管偏移,BuffResize 用 ArrayResize 改大小且校验返回值是否等于目标尺寸,BufferSize 直接取 array.Size(),InitBuffer 拿 ArrayInitialize 把整段填成 init_value。这样在 OnCalculate 里调一句 InitBuffer() 就能复位,不用反复写循环。 别把缓冲区的空值当零处理 init_value 若设成 0,在 RSI 这类有真实 0 值的指标里会和「未初始化」混淆;贵金属与外汇波动快,建议设成 EMPTY_VALUE 之外的明显异常数(如 -1)以便肉眼排错。
ERR_TYPE_NO_ERROR, class=class="str">"cmt">// No error ERR_TYPE_NO_CYNC, class=class="str">"cmt">// Data not synchronized ERR_TYPE_NO_DATA, class=class="str">"cmt">// Data not loaded ERR_TYPE_NO_CALC, class=class="str">"cmt">// Calculation not completed }; class=class="str">"cmt">//--- class="kw">struct class="kw">struct SBuffer class=class="str">"cmt">// Structure of the indicator buffer { class="type">class="kw">double array[]; class=class="str">"cmt">// Indicator buffer array class="type">class="kw">double init_value; class=class="str">"cmt">// Initializing value class="type">int shift; class=class="str">"cmt">// Horizontal shift of the buffer class="type">class="kw">string descript; class=class="str">"cmt">// Buffer description class=class="str">"cmt">//--- (class="num">1) Sets, (class="num">2) returns the initializing value, class="type">void SetInitValue(const class="type">class="kw">double value) { init_value=value; } class="type">class="kw">double InitValue(class="type">void) { class="kw">return init_value; } class=class="str">"cmt">//--- (class="num">1) Sets, (class="num">2) returns the buffer offset class="type">void SetShift(const class="type">int value) { shift=value; } class="type">int Shift(class="type">void) { class="kw">return shift; } class=class="str">"cmt">//--- (class="num">1) Resizes the buffer array, (class="num">2) returns the size of the buffer array, class=class="str">"cmt">//--- (class="num">3) initializes the array with the set "empty" value class="type">bool BuffResize(const class="type">int new_size) { class="kw">return(ArrayResize(array,new_size)==new_size);} class="type">uint BufferSize(class="type">void) { class="kw">return array.Size(); } class="type">int InitBuffer(class="type">void) { class="kw">return ArrayInitialize(array,init_value); } };
◍ 线状态与错误码的枚举映射
这段 switch 把指标线在多维分析里可能处的状态全部翻译成可读字符串,方便在日志或面板上直接看,而不是对着数字发懵。从 NONE 到 EQUALS 一共 14 个分支,覆盖了方向(UP/DOWN)、停顿(STOP_UP/STOP_DOWN)、反转(TURN_UP/TURN_DOWN)、相对基准线的穿越与触碰(CROSS_UP/TOUCH_BELOW 等),default 兜底返回 Unknown。 另一个 TypeErrorcDescription 函数只处理 3 类计算期错误:ERR_TYPE_NO_CYNC 对应数据未同步,ERR_TYPE_NO_DATA 是数据没加载,ERR_TYPE_NO_CALC 是计算没跑完,其余情况归为 No error。实盘里若多品种多周期指标频繁吐出 Data is not synchronized,往往意味着跨品种历史数据还没补齐,外汇与贵金属品种尤其容易在跳空后触发,属于高概率但非必然的同步风险。 下面这段是基类 CIndMSTF 的私有成员声明,它继承自 CObject,专门用来承载多符号多周期指标的基础属性。m_program 记程序类型,m_type 记指标类型,m_timeframe 和 m_symbol 分别锁定时段与品种,后续扩展都从这四个字段起步。
case LINE_STATE_NONE : class="kw">return "None"; case LINE_STATE_UP : class="kw">return "Up"; case LINE_STATE_STOP_UP : class="kw">return "Stop Up"; case LINE_STATE_TURN_UP : class="kw">return "Turn Up"; case LINE_STATE_DOWN : class="kw">return "Down"; case LINE_STATE_STOP_DOWN : class="kw">return "Stop Down"; case LINE_STATE_TURN_DOWN : class="kw">return "Turn Down"; case LINE_STATE_ABOVE : class="kw">return "Above level"; case LINE_STATE_BELOW : class="kw">return "Below level"; case LINE_STATE_CROSS_UP : class="kw">return "Crossing Up"; case LINE_STATE_CROSS_DOWN : class="kw">return "Crossing Down"; case LINE_STATE_TOUCH_BELOW : class="kw">return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE : class="kw">return "Touch from Above"; case LINE_STATE_EQUALS : class="kw">return "Equals"; class="kw">default : class="kw">return "Unknown"; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return error description in indicator calculation | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string TypeErrorcDescription(ENUM_ERR_TYPE error_type) { class="kw">switch(error_type) { case ERR_TYPE_NO_CYNC : class="kw">return "Data is not synchronized"; case ERR_TYPE_NO_DATA : class="kw">return "Data not loaded"; case ERR_TYPE_NO_CALC : class="kw">return "Calculation not completed"; class="kw">default : class="kw">return "No error"; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Base class of the multi-symbol multi-period indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndMSTF : class="kw">public CObject { class="kw">private: ENUM_PROGRAM_TYPE m_program; class=class="str">"cmt">// Program type ENUM_INDICATOR m_type; class=class="str">"cmt">// Indicator type ENUM_TIMEFRAMES m_timeframe; class=class="str">"cmt">// Chart timeframe class="type">class="kw">string m_symbol; class=class="str">"cmt">// Chart symbol
指标基类里的成员与缓冲区接口
在 MQL5 自建指标框架里,通常会先放一个基类把通用字段收拢。下面这段声明里,公开部分存了句柄、标识、成败标记和错误类型,也带名称与参数描述,方便后续调试时直接打印。 m_handle 是 EA 或脚本向终端申请指标实例后拿到的整数句柄;m_success 与 m_type_err 配合,能在每 tick 计算后判断这次取值是否可靠,避免拿到的缓冲里混着空值。 保护段里的 m_param[] 用 MqlParam 结构装载参数,m_buffers[] 才是真正存序列数据的地方。m_limit 表示本 tick 最少要多少根 bar 才能算,m_rates_total 是可用总柱数,m_prev_calculated 记录上一次已算柱数——这三个 int 直接决定你的主循环从哪根 bar 开始写,省得每 tick 全量重算。 SetName 与 SetParameters 只是薄封装,把名字和参数字符串写进成员;真正吃算力的是 BufferResize / BuffersResize 与 BufferInitialize / BuffersInitialize,前者改指定或全缓冲长度,后者顺带填初值。开 MT5 新建一个空指标,把这段声明原样贴进头文件,编译后能在调试窗口看到 m_name 与 m_parameters 随 OnInit 赋值变化。
class="type">int m_handle; class=class="str">"cmt">// Indicator handle class="type">int m_id; class=class="str">"cmt">// Identifier class="type">bool m_success; class=class="str">"cmt">// Successful calculation flag ENUM_ERR_TYPE m_type_err; class=class="str">"cmt">// Calculation error type class="type">class="kw">string m_description; class=class="str">"cmt">// Custom description of the indicator class="type">class="kw">string m_name; class=class="str">"cmt">// Indicator name class="type">class="kw">string m_parameters; class=class="str">"cmt">// Description of indicator parameters class="kw">protected: ENUM_IND_CATEGORY m_category; class=class="str">"cmt">// Indicator category class="type">MqlParam m_param[]; class=class="str">"cmt">// Array of indicator parameters class="type">class="kw">string m_title; class=class="str">"cmt">// Title(indicator name + description of parameters) SBuffer m_buffers[]; class=class="str">"cmt">// Indicator buffers class="type">int m_digits; class=class="str">"cmt">// Digits in indicator values class="type">int m_limit; class=class="str">"cmt">// Number of bars required to calculate the indicator on the current tick class="type">int m_rates_total; class=class="str">"cmt">// Number of available bars for indicator calculation class="type">int m_prev_calculated; class=class="str">"cmt">// Number of calculated bars on the previous indicator call class=class="str">"cmt">//--- (class="num">1) Sets indicator name, (class="num">2) description of parameters class="type">void SetName(const class="type">class="kw">string name) { this.m_name=name; } class="type">void SetParameters(const class="type">class="kw">string str) { this.m_parameters=str; } class=class="str">"cmt">//--- Resizes the(class="num">1) specified, (class="num">2) all indicator buffers class="type">bool BufferResize(const class="type">uint buffer_num,const class="type">int new_buff_size); class="type">bool BuffersResize(const class="type">int new_buff_size); class=class="str">"cmt">//--- Initializes the(class="num">1) specified, (class="num">2) all indicator buffers class="type">bool BufferInitialize(const class="type">uint buffer_num,const class="type">int new_buff_size); class="type">bool BuffersInitialize(const class="type">int new_buff_size);
「MqlParam 结构比对的两个底层函数」
在 MT5 的 MqlParam 封装里,判断两个输入参数是否「同构」靠的是 IsEqualParameters。它不只比 type,还同时比对 integer_value、string_value,以及用 NormalizeDouble 把 double_value 差值精确到 8 位小数后是否归零——这意味着两个浮点参数只要误差小于 1e-8 就会被判相等。 CompareParams 则在这之上给出排序语义:相等返回 0;若当前参数在 type、整型、字符串或双精度任一维度上「大于」对比参数就返回 1,反之返回 -1。注意它是按字段顺序短路判断的,type 优先于数值和字符串。 实盘写 EA 参数校验时,这套逻辑可以直接复用:比如检测用户新载入的指标体系跟模板是否参数一致,调一下 IsEqualParameters 就能拦掉 8 位精度内的微小漂移,避免重复初始化指标句柄。外汇与贵金属杠杆高,参数误判可能引发异常下单,建议先在策略测试器里跑通再上真仓。
class=class="str">"cmt">//--- Returns the flag indicating equality of the structure of one parameter of two objects class="type">bool IsEqualParameters(const class="type">MqlParam &this_param,const class="type">MqlParam &compared_param) const { if(this_param.type==compared_param.type && this_param.integer_value==compared_param.integer_value && this_param.string_value==compared_param.string_value && ::NormalizeDouble(this_param.double_value-compared_param.double_value,class="num">8)==class="num">0 ) class="kw">return true; class="kw">return false; } class=class="str">"cmt">//--- Return the result of comparison on one parameter of two objects class="type">int CompareParams(const class="type">MqlParam &this_param,const class="type">MqlParam &compared_param) { if(this.IsEqualParameters(this_param,compared_param)) class="kw">return class="num">0; else if(this_param.type>compared_param.type || this_param.integer_value>compared_param.integer_value || this_param.string_value>compared_param.string_value || this_param.double_value>compared_param.double_value ) class="kw">return class="num">1; else if(this_param.type<compared_param.type || this_param.integer_value<compared_param.integer_value || this_param.string_value<compared_param.string_value || this_param.double_value<compared_param.double_value ) class="kw">return -class="num">1;
◍ 封装类的对外接口怎么用
上面那段代码是这个指标封装类 public 区的方法声明,真正在 EA 或脚本里调用的就是这些接口,而不是去碰内部的指标句柄。 CreateIndicator(void) 只负责建计算部分并返回句柄;Calculate(void) 跑一次计算,DataToBuffer(...) 把结果按目标品种和周期填进外部数组,limit 参数控制填充根数。实盘里若 limit 设得过大,可能拖慢 OnTick 节奏,外汇和贵金属波动快,这种开销要自己压到最小。 GetData 与 GetDataTo 的区别在于后者带 symbol_to / timeframe_to,能跨周期取数;BufferLineState 系列返回 ENUM_LINE_STATE,可判断线态而不必自己写阈值比较。SetBufferInitValue 和 SetBufferShift 分别管缓冲初值与偏移,调参时改这两个比改核心算法安全。 直接把这套声明拷进你的类头文件,MT5 里建个测试指标调用 Calculate + DataToBuffer,看不同 limit 下的耗时差异,比读文档直观。
else class="kw">return -class="num">1; } class="kw">public: class=class="str">"cmt">//--- Creates the calculation part of the indicator, returns the handle class="type">int CreateIndicator(class="type">void); class=class="str">"cmt">//--- (class="num">1) Calculates the indicator, (class="num">2) fills the passed buffer array(taking into account the chart period/symbol) with data from the indicator calculation buffer of this class class="type">bool Calculate(class="type">void); class="type">bool DataToBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">int limit,class="type">class="kw">double &buffer[]); class=class="str">"cmt">//--- (class="num">1) Sets(class="num">2) returns the initializing value for the specified buffer class="type">void SetBufferInitValue(const class="type">uint buffer_num,const class="type">class="kw">double value); class="type">class="kw">double BufferInitValue(const class="type">uint buffer_num) const; class=class="str">"cmt">//--- (class="num">1) Sets(class="num">2) returns the offset value for the specified buffer class="type">void SetBufferShift(const class="type">uint buffer_num,const class="type">int value); class="type">class="kw">double BufferShift(const class="type">uint buffer_num) const; class=class="str">"cmt">//--- Returns data of the specified buffer(class="num">1) as is, (class="num">2) relative to the specified symbol/timeframe, class=class="str">"cmt">//--- (class="num">3) amount of data in the specified buffer, (class="num">4) the state of the indicator line as it is in the calculation part buffer, class=class="str">"cmt">//--- (class="num">5) indicator line state taking into account the chart symbol/period, description of the line state(class="num">6) as is in the buffer(class="num">7) taking into account the chart symbol/period class="type">class="kw">double GetData(const class="type">uint buffer_num,const class="type">int index) const; class="type">class="kw">double GetDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">int index) const; class="type">uint DataTotal(const class="type">uint buffer_num) const; ENUM_LINE_STATE BufferLineState(const class="type">uint buffer_num,const class="type">int index) const; ENUM_LINE_STATE BufferLineState(const class="type">class="kw">string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const class="type">uint buffer_num,const class="type">int index) const; ENUM_LINE_STATE BufferLineStateRelative(const class="type">int buffer_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE);