一张图表上多个指标(第 04 部分):晋升为一款智能交易系统·进阶篇
◍ 把通用指标塞进 EA 的编译链
在 EA 源码顶部加两行预处理指令,就能把通用指标的编译产物直接打进 EA 里。下面这段就是最小可用写法:先用 #define 把资源路径定义成宏,再用 #resource 把它挂为资源。 #include 完成后,原来的 SubSupport.ex5 可以删掉,EA 运行不再依赖外部文件。但要注意一个坑:编译时若编译器在目录里发现了现成的 SubSupport.ex5,它会直接用这个旧文件,而不会重新编译你改过的 SubSupport.mq5。 所以只要你动过 .mq5 源码,就必须先手动删掉旧的 .ex5,否则改动永远不会进 EA。外汇和贵金属 EA 编译链出错往往就卡在这一个细节上,开 MT5 验证时建议先清一次残留 ex5 再编译。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_Resource "Resources\\SubSupport.ex5" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#resource def_Resource class=class="str">"cmt">//+------------------------------------------------------------------+
「给终端包一层:C_Terminal 子窗口类」
做子窗口没必要一上来就堆功能。先问清楚这个类到底要扛什么:在 EA 生命周期里,我们只需在启动阶段挂一个子窗口、退出时关掉它,所以 Init 与 Close 两个方法各跑一次就够,不值得为它们写单元测试。 但 EA 会越长越胖。现在把扩展性留好,比以后返工改架构便宜得多。于是我新开了一个 C_Terminal 类,专门承接和图形终端相关的通用能力,子窗口的增删只是它的第一块拼图,后面还会往里塞更多终端级操作。 这一节先只落子窗口的骨架。下一项任务里有一部分在现有方案下直接实现不了,我们留到后面拆。
用继承扩展图表类而不动原码
在 MT5 里做 EA 开发,若已有 C_TemplateChart 这类经过验证的图表管理类,最稳妥的扩展方式不是往里塞新函数,而是另写子类继承它。这样原类的测试状态保留,复杂度只随新增代码线性增加,新功能出问题也只需测新增部分。
MQL5 不支持 C++ 式的多重继承,子类不能同时从父类和祖父类取函数,但单继承已足够把子窗口逻辑隔离出来。声明写法就是 class C_SubWindow : public C_TemplateChart,公开继承后父类所有 public 成员直接可用。
头文件引用有个坑:用尖括号 < > 是绝对路径,编译器严格走你写的目录;用引号 " " 是相对路径,先从当前工作目录找。相同文件名不同内容散在各目录时,引号能避免误引。
Init 函数里 "::" + def_Resource 这个写法决定资源搜索范围:带 :: 前缀表示用 EA 内部资源,若只写资源名,EA 会去 MQL5 目录翻,文件没放对位置就算加进资源也会失败。下面代码逐行拆一下关键行。
#include "C_TemplateChart.mqh" 相对路径引入基类头文件;class C_SubWindow : public C_TemplateChart 公开继承,子类获得父类全部功能;if (m_handleSub != INVALID_HANDLE) return true; 避免重复创建子窗口句柄;iCustom(NULL, 0, "::" + def_Resource) 从 EA 内部加载自定义指标资源,失败返回 INVALID_HANDLE;ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL) 取当前图表子窗口总数作为新窗口 ID;ChartIndicatorAdd 把指标塞进该子窗口。Close 里 IndicatorRelease 和 ChartIndicatorDelete 配对释放,防止句柄泄漏。
外汇与贵金属杠杆交易高风险,这套继承结构只是降低代码维护成本,不预示任何行情方向,实盘前请在策略测试器用历史数据验证子窗口指标加载是否如预期。
class="macro">#include "C_TemplateChart.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class C_SubWindow : class="kw">public C_TemplateChart { class=class="str">"cmt">// ... Class code }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Init(class="type">void) { if (m_handleSub != INVALID_HANDLE) class="kw">return true; if ((m_handleSub = iCustom(NULL, class="num">0, "::" + def_Resource)) == INVALID_HANDLE) class="kw">return class="kw">false; m_IdSub = (class="type">int) ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL); if (!ChartIndicatorAdd(Terminal.Get_ID(), m_IdSub, m_handleSub)) class="kw">return class="kw">false; class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Close(class="type">void) { ClearTemplateChart(); if (m_handleSub == INVALID_HANDLE) class="kw">return; IndicatorRelease(m_IdSub); ChartIndicatorDelete(Terminal.Get_ID(), m_IdSub, ChartIndicatorName(Terminal.Get_ID(), m_IdSub, class="num">0)); ChartRedraw(); m_handleSub = INVALID_HANDLE; } class=class="str">"cmt">//+------------------------------------------------------------------+ input class="type">class="kw">string user01 = ""; class=class="str">"cmt">//Used indicators input class="type">class="kw">string user02 = ""; class=class="str">"cmt">//Assets to follows class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class="type">int m_handleSub; class=class="str">"cmt">//... if ((m_handleSub = iCustom(NULL, class="num">0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) class="kw">return INIT_FAILED;
◍ 把指标挂进独立子窗口的初始化尾段
在 EA 初始化末期,需要把副图指标句柄塞进图表的最后一个子窗口,否则指标不会显示。下面这段是 MT5 里最常见的收尾写法,直接复制就能用。 核心就一行:用 ChartIndicatorAdd 把 m_handleSub 加到 ChartGetInteger 取到的 CHART_WINDOWS_TOTAL 所指向的那个窗口下标。注意这里做了 (int) 强转,因为该函数返回的是 long,不转在某些编译严格环境下会报警。 若添加失败直接 return INIT_FAILED,成功则调 ChartRedraw() 强制重绘一次,最后 return INIT_SUCCEEDED 结束初始化。外汇与贵金属品种波动大,指标句柄若因品种未加载而为空,这一步会稳定失败,开 MT5 连真品种测一下比回测更靠谱。
if (!ChartIndicatorAdd(ChartID(), ((class="type">int) ChartGetInteger(ChartID(), CHART_WINDOWS_TOTAL)), m_handleSub)) class="kw">return INIT_FAILED; class=class="str">"cmt">//... ChartRedraw(); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//...The rest of the Expert Advisor code ...
「别急着下结论」
多色指标最容易踩的坑,是把绘制线数量(比如 2 条线)和对象属性编号(TypeN、ColorN、WidthN 中的 N)混为一谈。上面那段代码里,dataHi 和 dataLo 都用了 DRAW_COLOR_LINE 且各配了 4 色,但 LOW 线在 MT5 里不显示,根因往往是没给第二条线动态补齐全 Type2、Color2、Width2 三件套,只靠 #property indicator_color2 硬撑。 若你倾向于完全用 PlotIndexSetInteger 动态设色,就可以把 #property indicator_colorN 摘掉,否则两套机制并存只会让维护更乱。外汇与贵金属市场高杠杆,这类自定义指标验证务必先在策略测试器跑通再上真仓。 那位读者反馈“只画了部分点”,大概率是颜色与背景撞色,或 OnCalculate 返回了 -1 / i 而非 rates_total。改回返回 rates_total,换不重复的高对比色,大概率能显全。代码留给你自己改,比直接给答案更有用。
Buf=Set_IndexBuffer5(Buf, dataHi, INDICATOR_DATA , DRAW_COLOR_LINE , class="num">4 ,"High" , clrYellow , clrWhite , clrDodgerBlue ); Buf=Set_IndexBuffer5(Buf, dataHic, INDICATOR_COLOR_INDEX ); Buf=Set_IndexBuffer5(Buf, dataLo, INDICATOR_DATA , DRAW_COLOR_LINE , class="num">4 ,"Low" , clrYellow , clrWhite , clrDodgerBlue ); Buf=Set_IndexBuffer5(Buf, dataLoc, INDICATOR_COLOR_INDEX );