开发回放系统(第 39 部分):铺平道路(三)·进阶篇
📘

开发回放系统(第 39 部分):铺平道路(三)·进阶篇

第 2/3 篇

销毁自定义指标句柄的干净做法

在 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 的内存曲线是否回平。外汇与贵金属波动剧烈,指标频繁重建属高风险操作,建议先在模拟盘验证。

MQL5 / C++
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 不必猜落点。外汇与贵金属市场杠杆高、波动剧烈,验证这段代码时请用模拟盘或极小仓位。

MQL5 / C++
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 这类数值即链路通了。外汇与贵金属杠杆高,指标连通不等于信号靠谱,参数翻倍结果仅作管道验证用。

MQL5 / C++
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 和前面创建指标时用的短名完全一致,卸载就不会残留画线。

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

常见问题

在 OnInit 或循环外统一用 IndicatorRelease(handle) 释放,别等到程序退出才管,避免句柄堆积拖慢终端。
是,正规做法只能走缓冲区,用 CopyBuffer 按索引回读,直接读内部变量拿不到也不稳。
可以,小布能扫描你贴的代码梳理 iCustom 与 IndicatorRelease 配对情况,标出只挂不卸的漏点。
在 OnDeinit 里对全部 iCustom 得到的句柄调 IndicatorRelease,保证品种切换或卸载时缓冲解绑。
顺手把缓存数组清掉、关闭可能开的文件或定时器,让自定义指标退出时不留后台残留。