DoEasy 函数库中的时间序列(第四十一部分):多品种多周期指标样品(基础篇)
「给 DoEasy 接上多品种多周期指标样品」
DoEasy 函数库做到第四十一部分,核心落点是把抽象的时间序列类真正接到「多品种 + 多周期」的指标样品上。原文示例发布于 2020-08-18,在 MT5 官方示例板块拿到 2663 次查看,说明这类底层封装对实盘多图表监控有真实需求。 这一节没有给出现成 EA,而是先改函数库的类与数据结构,让后续指标能直接调用跨品种跨周期缓冲。对交易者来说,意义在于:以后写一个小布式盯盘指标,不必每次手写 SymbolSelect + 多 iCustom 周期搬运,直接复用库里的样品骨架即可。 外汇与贵金属市场杠杆高、跳空频繁,跨周期取数若不同步容易误判;建议在 MT5 里新建一个测试指标,只打印两个非当前图表的收盘价序列,先验证库的稳定性再扩写。
从单缓冲到多品种多周期指标的路子
前面两篇已经把函数库在指标里的能力跑通了:时间序列能正确下载历史数据,也能实时刷当前数据。上一篇文章把指标缓冲区做成屏幕显示用的数据结构,单个绘制缓冲区对应一个结构,想多缓冲就得每个都单独定义再塞进数组。 这一节要继续把“用结构操控缓冲区”这件事做细,目标是做一个多品种多周期指标,能按指定货币对和指定图表周期画出价格蜡烛。顺着做下来,你会自然意识到:为什么非得把指标缓冲区封装成类,而不是永远用手写结构数组。 函数库自带消息类,可以切函数库提示语的语言,也能加自定义语言。但输入参数(input)的描述翻译目前管不了——编译后显示的就是你写代码时的那种语言。要么只用一种语言,要么为每种要发布的语言各写一套相近的输入。 市场发布要求英语常驻,所以我们选第二种:单独建一个文件放枚举,给俄语和英语两种可能各提供常量。用户之后把俄文枚举描述翻成自己要的语言即可,英语那套别删。外汇和贵金属品种波动大、杠杆高,多周期指标只是辅助观察,信号失效概率不低。
◍ 把数据结构和枚举拆进独立头文件
重构 DoEasy 函数库时,先把 OnCalculate() 传进来的当前柱线数据从 Defines.mqh 挪到了 Datas.mqh。原结构只适合放“数据”定义,不适合塞预定义变量和静态值,拆出来后头文件职责更清晰,后续维护不容易踩坑。 程序输入的枚举单独建了 InpDatas.mqh,用宏替换控制编译语言:声明了英语宏就编英语描述,注释掉则编俄语(或自定义语言)常量枚举。这样不用反复改 Datas.mqh,加新枚举直接丢进新文件就行。 时间序列集合类补了一个方法,专门更新除当前品种外的所有时间序列。当前品种留在 OnCalculate() 里随即时报价刷,其余品种丢给计时器,避免计时器过度占用主图品种资源。 品种集合的 SetUsedSymbols() 现在会强制把当前品种塞进列表——哪怕用户在设置里没勾。屏幕定位数据时要持续访问当前品种,漏掉它会让时间序列集合缺一块。 返回两个数组里是否存在某品种/时间帧的标志方法很直白:循环比对,命中即 true,跑完没命中返回 false。这些数组由“写入所用品种和时间帧”方法填充,它扫一遍已建的时间序列就能把用到的符号和周期全收进去。 修复了一个实打实的 bug:往列表加对象失败后,原代码先删 series 指针再设用途标志,指针已释放必然报错。改成先挪标志位、再校验添加结果,错就不会冒出来了。另外更新事件列表时“新柱线”时间偶尔为零,新增变量区分指标运行于当前品种周期还是别处,分别取 OnCalculate 价格结构时间或 LastBarDate() 返回值。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Structures | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct SDataCalculate { class="type">int rates_total; class=class="str">"cmt">// size of input timeseries class="type">int prev_calculated; class=class="str">"cmt">// number of handled bars at the previous call class="type">int begin; class=class="str">"cmt">// where significant data starts class="type">class="kw">double price; class=class="str">"cmt">// current array value for calculation class="type">MqlRates rates; class=class="str">"cmt">// Price structure } rates_data; class=class="str">"cmt">//+------------------------------------------------------------------+
「把计算上下文装进一个结构体」
在 MT5 自定义指标里,每次 OnCalculate 被调用都会带回一堆上下文参数:总柱数、上回算到第几根、有效数据起点、当前价格以及整条报价结构。零散传参既易错又难维护,直接用一个结构体把这些信息归一。 下面这段定义把上下文收进 SDataCalculate,并顺手声明了全局实例 rates_data。rates_total 是输入序列长度,prev_calculated 标记上次已处理柱数,begin 指明有效数据从哪根开始,price 存当前用于计算的值,rates 挂上 MqlRates 完整报价结构。 顺带的两个宏也值得留意:INPUT_SEPARATOR 设成逗号,专门切分输入字符串;TOTAL_LANG 写死为 2,表示这套逻辑只接两种语言界面。改语言支持数量时,调这一个数字即可。 头文件引用走的是 InpDatas.mqh,真正的外参定义留在那边,本文件只管计算上下文的结构与常量。开 MT5 新建一个 mqh,把下面结构贴进去就能复用。
class="macro">#include "InpDatas.mqh" class="macro">#define INPUT_SEPARATOR(",") class=class="str">"cmt">// Separator in the inputs class="type">class="kw">string class="macro">#define TOTAL_LANG(class="num">2) class=class="str">"cmt">// Number of used languages class="kw">struct SDataCalculate { class="type">int rates_total; class=class="str">"cmt">// size of input timeseries class="type">int prev_calculated; class=class="str">"cmt">// number of handled bars at the previous call class="type">int begin; class=class="str">"cmt">// where significant data starts class="type">class="kw">double price; class=class="str">"cmt">// current array value for calculation class="type">MqlRates rates; class=class="str">"cmt">// Price structure } rates_data;
多品种多周期数组与编译开关的底层骨架
在 MT5 指标或 EA 的头部,通常会先声明两个全局数组来承载运行对象:一个是品种名字符串数组,一个是周期枚举数组。这样后续逻辑就能用循环批量处理多品种多周期,而不是写死在单一图表上。 下面这段声明是典型写法:string 类型存品种名,ENUM_TIMEFRAMES 类型存周期。注意数组未指定大小,实际常在 OnInit 里用 ArrayResize 按用户输入动态扩,外汇与贵金属多周期回测时内存占用可能随品种数线性增长,需留意终端性能。 代码中还出现 COMPILE_EN 的宏开关:#ifdef COMPILE_EN 决定编译英文界面输入。若注释掉这行,就切到俄文输入分支。开 MT5 把该宏去掉重新编译,参数面板语言会直接变,属于零风险验证点。
class="type">class="kw">string ArrayUsedSymbols[]; class=class="str">"cmt">// Array of used symbols&class="macro">#x27; names ENUM_TIMEFRAMES ArrayUsedTimeframes[]; class=class="str">"cmt">// Array of used timeframes class="macro">#define COMPILE_EN class=class="str">"cmt">// Comment out the class="type">class="kw">string for compilation in Russian class="macro">#ifdef COMPILE_EN enum ENUM_SYMBOLS_MODE { SYMBOLS_MODE_CURRENT, class=class="str">"cmt">// Work only with the current symbol
◍ 枚举里就把扫描范围钉死
多币种多周期扫描最怕范围漂移。把品种和时间框架的处理方式直接写成枚举,EA 启动时就能锁定边界,不会在运行中途误扫到无关品种。 品种侧给了四种模式:SYMBOLS_MODE_CURRENT 只盯当前图表品种;SYMBOLS_MODE_DEFINES 跑你写死的品种列表;SYMBOLS_MODE_MARKET_WATCH 只认「市场报价」窗口里那些;SYMBOLS_MODE_ALL 则是经纪商全品种池。时间框架侧对应三种:TIMEFRAMES_MODE_CURRENT 仅当前周期,TIMEFRAMES_MODE_LIST 用指定周期数组,TIMEFRAMES_MODE_ALL 覆盖全部周期。 原文用 #else / #endif 做了双语输入分支,俄语环境走带俄文注释的同一套枚举。你开 MT5 新建 ea 时,直接抄下面这段,把枚举当输入参数的类型,就能在外部参数里下拉切换扫描范围。外汇与贵金属杠杆高,全品种全周期扫描会吃掉不少资源,实盘前先在策略测试器里用 SYMBOLS_MODE_MARKET_WATCH 跑一遍。
enum ENUM_SYMBOLS_MODE { SYMBOLS_MODE_CURRENT, class=class="str">"cmt">// Work only with current symbol SYMBOLS_MODE_DEFINES, class=class="str">"cmt">// Work with a given list of symbols SYMBOLS_MODE_MARKET_WATCH, class=class="str">"cmt">// Working with Symbols from the "Market Watch" window SYMBOLS_MODE_ALL class=class="str">"cmt">// Work with a complete list of Symbols }; enum ENUM_TIMEFRAMES_MODE { TIMEFRAMES_MODE_CURRENT, class=class="str">"cmt">// Work only with the current timeframe TIMEFRAMES_MODE_LIST, class=class="str">"cmt">// Work with a given list of timeframes TIMEFRAMES_MODE_ALL class=class="str">"cmt">// Work with a complete list of timeframes };