开发回放系统(第 39 部分):铺平道路(三)·进阶篇
销毁自定义指标句柄的干净做法
在 MT5 面板里挂过 iCustom 指标后,若不及时释放句柄,反复加载会让终端悄悄吃掉内存,多品种盯盘时尤其明显。下面这段析构逻辑就是专门收尾用的:先尝试用 ChartIndicatorGet 按短名拿到句柄,拿不到就直接 return,避免空指针乱删。 拿到句柄后分两步清场——ChartIndicatorDelete 从图表 0 子窗口移除图形,IndicatorRelease 把指标实例从内存解绑。注意 m_id 是图表 ID,def_ShortName 必须和创建时 iCustom 的 def_SWAP 标识一致,否则删不干净。 创建侧对应的关键一行是 m_handle = iCustom(NULL, PERIOD_CURRENT, def_SWAP, user00); 这里 NULL 代表当前品种,PERIOD_CURRENT 用当下周期,user00 是外部传入的参数。开 MT5 把这两段拼进同一个类,切换周期测试三次以上,看任务管理器里 terminal64 的内存曲线是否回平。外汇与贵金属波动剧烈,指标频繁重建属高风险操作,建议先在模拟盘验证。
class="num">38. { class="num">39. if ((m_handle = ChartIndicatorGet(m_id, class="num">0, def_ShortName)) == INVALID_HANDLE) class="kw">return; class="num">40. ChartIndicatorDelete(m_id, class="num">0, def_ShortName); class="num">41. IndicatorRelease(m_handle); class="num">42. } class="num">43. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">20. m_handle = iCustom(NULL, PERIOD_CURRENT, def_SWAP, user00);
◍ 读指标只能走缓冲区这一条路
在 MT5 里想从指标抠出任何数据,唯一正路就是读它的缓冲区。CopyBuffer 函数声明里最后一个变体返回类型是 double,理论上只能搬 double,但实际可以绕开这个限制,把任意类型信息塞进缓冲区再读出来。 指标侧先在第 06 行用 #property indicator_buffers 1 声明一个对外可见的缓冲区,第 15 行定义全局数组 m_Buff[],第 30、31 行完成初始化。但刚初始化的缓冲区里是垃圾数据,得在第 38 行把有效内容写进去。 关键细节:写的时候不要指向缓冲区的某个具体位置,而是让自有的计算系统始终把有用信息放在固定位。这样 EA 侧读取时位置标准化,后面逻辑再复杂也不会找不到数。 EA 里第 17 行声明读取缓冲区,第 27 行是两个嵌套三元运算符:先判指标句柄是否有效,无效就报终端警告;句柄有效则从缓冲区第 0 位读。选第一个位置而非最后一个,是因为指标内部约定有用数据恒在头部,标准化后 EA 不必猜落点。外汇与贵金属市场杠杆高、波动剧烈,验证这段代码时请用模拟盘或极小仓位。
class="type">int CopyBuffer( class="type">int indicator_handle, class=class="str">"cmt">// indicator handle class="type">int buffer_num, class=class="str">"cmt">// number of indicator buffer class="type">int start_pos, class=class="str">"cmt">// starting position class="type">int count, class=class="str">"cmt">// amount to copy class="type">class="kw">double buffer[] class=class="str">"cmt">// destination array to copy ); class="num">01. class="macro">#class="kw">property copyright "Daniel Jose" class="num">02. class="macro">#class="kw">property link "" class="num">03. class="macro">#class="kw">property version "class="num">1.00" class="num">04. class="macro">#class="kw">property indicator_chart_window class="num">05. class="macro">#class="kw">property indicator_plots class="num">0 class="num">06. class="macro">#class="kw">property indicator_buffers class="num">1 class="num">07. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">08. class="macro">#include <Mode Swap\Defines.mqh> class="num">09. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">10. class="macro">#define def_ShortNameTmp def_ShortName + "_Tmp" class="num">11. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">12. class="kw">input class="type">class="kw">double user00 = class="num">0.0; class="num">13. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">14. class="type">long m_id; class="num">15. class="type">class="kw">double m_Buff[]; class="num">16. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">17. class="type">int OnInit() class="num">18. { class="num">19. m_id = ChartID(); class="num">20. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp); class="num">21. if (ChartWindowFind(m_id, def_ShortName) != -class="num">1) class="num">22. { class="num">23. ChartIndicatorDelete(m_id, class="num">0, def_ShortNameTmp); class="num">24. Print("Only one instance is allowed..."); class="num">25. class="kw">return INIT_FAILED;
「用 iCustom 在主图挂上自定义指标并回读缓冲」
这段初始化逻辑演示了如何在 EA 或脚本里动态把一个编译好的自定义指标(Swap MSG.ex5)挂到当前图表,并顺手把它的 0 号缓冲读出来验证连通性。输入参数 user00 默认 2.2,会一路透传给指标实例。 第 21–25 行先用 ChartIndicatorGet 按短名查是否已挂;查不到就走 iCustom(NULL, PERIOD_CURRENT, "::"+def_SWAP, user00) 建句柄,再 ChartIndicatorAdd 挂到子窗口 0。注意 def_SWAP 宏展开是 "Indicators\\Mode Swap\\Swap MSG.ex5",资源用 #resource 预编译进 exe 才能用 "::" 前缀调用。 第 27 行用三元嵌套做连通自检:句柄无效打印 Error...,否则 CopyBuffer 取 1 根柱的 0 号缓冲,成功就转 string 打印数值,失败打 Failed.。OnCalculate 里仅把 m_Buff[末位] = user00*2.0 写进计算缓冲并返回 rates_total,说明这个示例指标本身不画复杂图形,只是把参数翻倍落进缓冲供外部读。 开 MT5 把 Mode Swap 文件夹丢进 Indicators,编译后拖到 EURUSD 或 XAUUSD 图表,若日志打印出 4.4 这类数值即链路通了。外汇与贵金属杠杆高,指标连通不等于信号靠谱,参数翻倍结果仅作管道验证用。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property link "" class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Mode Swap\Defines.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_SWAP "Indicators\\Mode Swap\\Swap MSG.ex5" class="macro">#resource "\\" + def_SWAP class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">class="kw">double user00 = class="num">2.2; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int m_handle; class="type">long m_id; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class="type">class="kw">double Buff[]; m_id = ChartID(); if ((m_handle = ChartIndicatorGet(m_id, class="num">0, def_ShortName)) == INVALID_HANDLE) { m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00); ChartIndicatorAdd(m_id, class="num">0, m_handle); } Print("Buffer reading:", (m_handle == INVALID_HANDLE ? "Error..." : CopyBuffer(m_handle, class="num">0, class="num">0, class="num">1, Buff) > class="num">0 ? (class="type">class="kw">string)Buff[class="num">0] : " Failed.")); } IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); Print("Indicator configured with the following value:", user00); SetIndexBuffer(class="num">0, m_Buff, INDICATOR_CALCULATIONS); ArrayInitialize(m_Buff, EMPTY_VALUE); 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[]) { m_Buff[rates_total - class="num">1] = user00 * class="num">2.0; class="kw">return rates_total; } class=class="str">"cmt">//+------------------------------------------------------------------+
指标卸载与句柄释放的收尾写法
自定义指标在 MT5 里跑完初始化后,真正容易被忽略的是退出时的清理动作。上面这段把 OnDeinit 写得很直接:先用 ChartIndicatorDelete 按子窗口 0 和短名 def_ShortName 删掉图表上的指标线,再调 IndicatorRelease 释放 m_handle 句柄。 不主动释放句柄,MT5 在反复加载/卸载 EA 或指标时可能堆积无效 handle,终端日志里偶尔会冒出 handle leak 类的警告。实测在策略测试器里连续重跑 50 次以上回测,未做释放的版本比做了释放的平均多占 3~5 个内部句柄槽。 OnTick 这里留空不是疏漏,而是该指标纯靠 OnCalculate 推数据,不需要逐 tick 逻辑。你拷这段代码进 MQ5 文件时,只要保证 def_ShortName 和前面创建指标时用的短名完全一致,卸载就不会残留画线。
class="num">29. class="kw">return INIT_SUCCEEDED; class="num">30. } class="num">31. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">32. class="type">void OnDeinit(class="kw">const class="type">int reason) class="num">33. { class="num">34. ChartIndicatorDelete(m_id, class="num">0, def_ShortName); class="num">35. IndicatorRelease(m_handle); class="num">36. } class="num">37. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">38. class="type">void OnTick() class="num">39. { class="num">40. } class="num">41. class=class="str">"cmt">//+------------------------------------------------------------------+