DoEasy 函数库中的时间序列(第四十一部分):多品种多周期指标样品(基础篇)
📘

DoEasy 函数库中的时间序列(第四十一部分):多品种多周期指标样品(基础篇)

第 1/3 篇

「给 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() 返回值。

MQL5 / C++
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,把下面结构贴进去就能复用。

MQL5 / C++
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 把该宏去掉重新编译,参数面板语言会直接变,属于零风险验证点。

MQL5 / C++
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 跑一遍。

MQL5 / C++
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
  };

常见问题

先把指标数据结构拆成独立头文件,用枚举钉死扫描的品种和周期范围,再把计算上下文塞进一个结构体,就能脱离单缓冲限制。
独立头文件能让主逻辑只管调用,编译开关和扫描范围在枚举里一次定死,后期加品种周期不用动核心代码。
可以,小布能按你设定的品种和周期范围自动跑诊断,把异常缓冲和跨周期错位直接标在对应品种页上。
编译开关能决定数组按真实品种数还是固定上限分配,避免闲时占内存、忙时越界,回测和实盘切换只改开关。
不会,枚举只是集中定义边界,真要扩只要在枚举追加项并调对应数组长度,比散落在各函数里好改且不易漏。