从头开始开发智能交易系统(第 12 部分):时序与交易(I)·进阶篇
(2/3)· 传统看盘依赖双屏且易被成交噪音干扰,本篇用子窗口把时序直接叠进交易图表
◍ 给子窗口指标挂上短名与自动创建逻辑
在 MT5 写自定义指标时,先用 input string 把短名暴露给用户在属性框改,比写死在代码里灵活。下面这段把 "SubSupport" 作为默认短名,OnInit 里通过 IndicatorSetString(INDICATOR_SHORTNAME, user01) 写进指标缓存,图表上显示的名称就跟着用户输入走。 OnCalculate 这里直接 return rates_total,说明本小节只搭骨架,还没往价格数组里填任何计算值;rates_total 是当前图表可用 K 线总数,返回它就告诉终端“已处理完所有柱”。 类 C_FnSubWin 的私有成员 m_szIndicator 存指标名,m_SubWin 存子窗口索引。Create 方法里先用 ChartWindowFind 按名找窗口,返回 -1 表示还没挂;此时用 ChartGetInteger(CHART_WINDOWS_TOTAL) 拿到已有子窗口数,把 iCustom 生成的指标句柄通过 ChartIndicatorAdd 塞进新窗口,并把索引回写 m_SubWin。 注意外汇与贵金属杠杆高、滑点跳空频繁,这类自动挂窗口的代码在实盘前务必在策略测试器用至少 3 个月 tick 数据跑一遍,确认不会重复建窗导致资源泄漏。
class="kw">input class="type">class="kw">string user01 = "SubSupport"; class=class="str">"cmt">//短名 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { IndicatorSetString(INDICATOR_SHORTNAME, user01); class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">int begin, class="kw">const class="type">class="kw">double &price[]) { class="kw">return rates_total; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "C_Terminal.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class C_FnSubWin { class="kw">private : class="type">class="kw">string m_szIndicator; class="type">int m_SubWin; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Create(class="kw">const class="type">class="kw">string szIndicator) { class="type">int i0; m_szIndicator = szIndicator; if ((i0 = ChartWindowFind(Terminal.Get_ID(), szIndicator)) == -class="num">1) ChartIndicatorAdd(Terminal.Get_ID(), i0 = (class="type">int)ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL), iCustom(NULL, class="num">0, "::" + def_Resource, szIndicator)); m_SubWin = i0; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">public : class=class="str">"cmt">//+------------------------------------------------------------------+ C_FnSubWin() { m_szIndicator = NULL; m_SubWin = -class="num">1; } class=class="str">"cmt">//+------------------------------------------------------------------+
「子窗口句柄的清理与复用逻辑」
在 MT5 的 EA 架构里,自建副图子窗口如果只建不拆,切换周期或重载 EA 时会留下僵尸指标引用,导致 ChartIndicatorDelete 找不到目标而静默失败。下面这段把子窗口的生命周期收口放在析构和显式 Close 两道闸上。 ~C_FnSubWin() 析构直接调 Close(),Close() 里先判断 m_SubWin >= 0 才执行 ChartIndicatorDelete(Terminal.Get_ID(), m_SubWin, m_szIndicator),随后把 m_SubWin 置回 -1。这个 -1 就是「无窗口」哨兵值,GetIdSubWinEA 碰到 m_SubWin < 0 且传了指标名才会 Create,避免重复建窗。 ExistSubWin 用 return m_SubWin >= 0 一行给出布尔态,调用方不必碰内部变量。C_SubWindow 公有 Close 先转交 m_fnSubWin.Close() 再 CloseAlls(),说明子类在拆指标前还顺手清掉自己挂的浮动物件。 开 MT5 把这段塞进你的面板基类,重载 EA 十次观察副图数是否恒为 1;若偶尔变 2,八成是 m_SubWin 没在 OnInit 失败分支复位。外汇与贵金属品种上跑这类自绘界面前请先认清滑点与重连风险,副图泄漏本身不致命但会拖慢报价刷新。
~C_FnSubWin() { Close(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Close(class="type">void) { if (m_SubWin >= class="num">0) ChartIndicatorDelete(Terminal.Get_ID(), m_SubWin, m_szIndicator); m_SubWin = -class="num">1; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">int GetIdSubWinEA(class="kw">const class="type">class="kw">string szIndicator = NULL) { if ((szIndicator != NULL) && (m_SubWin < class="num">0)) Create(szIndicator); class="kw">return m_SubWin; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">bool ExistSubWin(class="type">void) class="kw">const { class="kw">return m_SubWin >= class="num">0; } class=class="str">"cmt">//+------------------------------------------------------------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "C_ChartFloating.mqh" class="macro">#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class C_SubWindow : class="kw">public C_ChartFloating { class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">private : C_FnSubWin m_fnSubWin; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">public : class=class="str">"cmt">//+------------------------------------------------------------------+ ~C_SubWindow() { Close(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Close(class="type">void) { m_fnSubWin.Close(); CloseAlls();
EA 子窗口的取号与存在判定
在 MT5 的 EA 封装里,支撑位子窗口的识别靠两个 inline 接口直连底层函数。GetIdSubWinEA 写死传入 "SubWinSupport" 字符串,回头拿到的是该子窗口在图表上的唯一整数 ID,方便后续往里画线或读坐标。 ExistSubWin 则是一行 const 包装,直接返 m_fnSubWin.ExistSubWin() 的布尔值。实盘里若子窗口没创建成功(比如手动删了指标窗),这句能让你在 OnTick 前就短路退出,避免空指针式报错。 外汇与贵金属波动剧烈、杠杆高风险大,这类窗口存在性检查建议放在初始化和周期切换时各跑一次,别只信启动那一下。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">int GetIdSubWinEA(class="type">void) { class="kw">return m_fnSubWin.GetIdSubWinEA("SubWinSupport"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">bool ExistSubWin(class="type">void) class="kw">const { class="kw">return m_fnSubWin.ExistSubWin(); } class=class="str">"cmt">//+------------------------------------------------------------------+ };
◍ 把成交流变成一分钟虚拟烛条
C_TimesAndTrade 类在 EA 里第一件事是确认自定义子窗口是否存在,没有就创建。初始化时调用 Init,传入缩放参数,默认 2,超过 5 按 5 封顶,低于 0 按 0 处理。 创建自定义品种的路径固定在 Custom\Robot\_<品名>,源数据来自真实品种,建完先塞进 Market Watch 才能画图。下面这段就是类里干这事的核心。 捕获函数只认带成交量的 tick:出价/要价跳动但没量就不存,纯挂单导致的价格强拉也不记。每根烛条对应一分钟虚拟时间,不是真实毫秒,只是按比例每分钟重绘一次,图形最小粒度就是 1 分钟。 烛条高低影线 = 成交瞬间的点差,开盘价 = 实际成交价。代码里对成交量做了小幅缩放避免比例溢出,你可以按具体资产改这个系数。系统跑起来能看到四种配置,它们分别对应什么下篇再拆,但当前版本已能密集使用。 外汇与贵金属杠杆高、滑点突变频繁,这类自定义时序图只作盘口微观参考,不构成方向判断。
class="type">void Init(class="kw">const class="type">int iScale = class="num">2) { if (!ExistSubWin()) { CreateCustomSymbol(); CreateChart(); } ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > class="num">5 ? class="num">5 : (iScale < class="num">0 ? class="num">0 : iScale))); } class="kw">inline class="type">void CreateCustomSymbol(class="type">void) { m_szCustomSymbol = "_" + Terminal.GetFullSymbol(); SymbolSelect(Terminal.GetFullSymbol(), true); SymbolSelect(m_szCustomSymbol, class="kw">false); CustomSymbolDelete(m_szCustomSymbol); CustomSymbolCreate(m_szCustomSymbol, StringFormat("Custom\\Robot\\%s", m_szCustomSymbol), Terminal.GetFullSymbol()); CustomRatesDelete(m_szCustomSymbol, class="num">0, LONG_MAX); CustomTicksDelete(m_szCustomSymbol, class="num">0, LONG_MAX); SymbolSelect(m_szCustomSymbol, true); };