删除指标实例:IndicatorRelease

如本章导言所述,终端为每个创建的指标维护一个引用计数器,只要至少有一个 MQL 程序或图表在使用该指标,终端就会保持运行状态。在 MQL 程序中,需要指标的标志就是有效的句柄。通常,我们在初始化时请求句柄,并在算法中持续使用它,直到程序结束。

当程序卸载时,所有创建的唯一句柄都会被自动释放,即它们的计数器减 1(如果计数器归零,相应指标也会从内存中卸载)。因此无需显式释放句柄。

但在程序运行过程中有时会出现子指标不再需要的情况。此时,无用的指标会继续消耗资源。因此,必须使用 IndicatorRelease显式释放句柄。

bool IndicatorRelease(int handle)

该函数会删除指定指标句柄,并在没有其他对象使用该指标时将其卸载。卸载过程会有轻微延迟。

该函数返回操作状态标识:成功 (true) 或失败 (false)。

调用IndicatorRelease后,传入的句柄将立即失效,即使变量本身仍保留之前的值。如果尝试在其他指标函数(如CopyBuffer)中使用此类句柄将失败,同时触发错误 4807 (ERR_INDICATOR_WRONG_HANDLE)。为避免混淆,建议在释放句柄后立即将对应变量赋值为 INVALID_HANDLE。

但如果程序后续请求一个新指标的句柄,该句柄很可能与之前释放的句柄值相同,但此时该句柄已关联到新指标的数据。

在策略测试程序中运行时,IndicatorRelease函数不会执行。

为演示 IndicatorRelease的应用,我们准备了 UseDemoAllLoop.mq5 的一个特殊版本,该版本会基于列表周期性循环重新创建辅助指标,该列表仅包含主窗口指标(为简化演示)。

IndicatorType MainLoop[] =
{
   iCustom_,
   iAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price,
   iAMA_period_fast_slow_shift_price,
   iBands_period_shift_deviation_price,
   iDEMA_period_shift_price,
   iEnvelopes_period_shift_method_price_deviation,
   iFractals_,
   iFrAMA_period_shift_price,
   iIchimoku_tenkan_kijun_senkou,
   iMA_period_shift_method_price,
   iSAR_step_maximum,
   iTEMA_period_shift_price,
   iVIDyA_momentum_smooth_shift_price,
};
   
const int N = ArraySize(MainLoop);
int Cursor = 0// current position inside the MainLoop array
      
const string IndicatorCustom = "LifeCycle";

数组的第一个元素包含一个自定义指标作为例外,即来自各类 程序启动和停止的功能 章节的LifeCycle。虽然该指标不显示任何线条,但它在这里很合适,因为当OnInit/OnDeinit处理程序被调用时,该指标会在日志中显示消息,这将使你能够跟踪该指标的生命周期。其他指标的生命周期与此类似。

在输入变量中,我们仅保留渲染设置。默认的 DRAW_ARROW 标签输出格式最适合展示不同类型的指标。

input ENUM_DRAW_TYPE DrawType = DRAW_ARROW// Drawing Type
input int DrawLineWidth = 1// Drawing Line Width

为了动态重建指标,我们在 OnInit 中启动了一个 5 秒的 计时器 ,并将之前的整个初始化过程(含下文所述的部分修改)移至 OnTimer处理程序。

int OnInit()
{
   Comment("Wait 5 seconds to start looping through indicator set");
   EventSetTimer(5);
   return INIT_SUCCEEDED;
}
   
IndicatorType IndicatorSelector// currently selected indicator type
   
void OnTimer()
{
   if(Handle != INVALID_HANDLE && ClearHandles)
   {
      IndicatorRelease(Handle);
      /*
      // descriptor is still 10, but is no longer valid
      // if we uncomment the fragment, we get the following error
      double data[1];
      const int n = CopyBuffer(Handle, 0, 0, 1, data);
      Print("Handle=", Handle, " CopyBuffer=", n, " Error=", _LastError);
      // Handle=10 CopyBuffer=-1 Error=4807 (ERR_INDICATOR_WRONG_HANDLE)
      */
   }
   IndicatorSelector = MainLoop[Cursor];
   Cursor = ++Cursor % N;
   
   // create a handle with default parameters
   // (because we pass an empty string in the third argument of the constructor)
   AutoIndicator indicator(IndicatorSelector,
      (IndicatorSelector == iCustom_ ? IndicatorCustom : ""), "");
   Handle = indicator.getHandle();
   if(Handle == INVALID_HANDLE)
   {
      Print(StringFormat("Can't create indicator: %s",
         _LastError ? E2S(_LastError) : "The name or number of parameters is incorrect"));
   }
   else
   {
      Print("Handle="Handle);
   }
   
   buffers.empty(); // clear buffers because a new indicator will be displayed
   ChartSetSymbolPeriod(0,NULL,0); // request a full redraw
   ...
   // further setup of diagrams - similar to the previous one
   ...
   Comment("DemoAll: ", (IndicatorSelector == iCustom_ ? IndicatorCustom : s),
      "(default-params)");
}

主要区别在于,当前创建的指标类型 IndicatorSelector现在不由用户设置,而是通过 Cursor 索引从MainLoop 数组中按顺序选择。每次计时器触发时,该索引会循环递增,即到达数组末尾后自动跳回开头。

所有指标的参数行均为空。这是为了统一初始化流程。因此,每个指标都会使用其默认值创建。

OnTimer处理程序开始时,我们调用IndicatorRelease 释放前一个句柄。但我们提供了一个输入变量 ClearHandles来禁用给定 if 分支,观察不清理句柄会出现的情况。

input bool ClearHandles = true;

默认情况下,ClearHandlestrue,即指标会按预期被删除。

最后,还有两个额外设置:清空缓冲区和请求完整重绘图表。这两项操作都是必需的,因为我们替换了提供显示数据的从属指标。

OnCalculate处理程序保持不变。

我们使用默认设置运行UseDemoAllLoop。日志中将显示以下条目(仅显示开头部分):

UseDemoAllLoop (EURUSD,H1) Initializing LifeCycle() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) Handle=10

LifeCycle (EURUSD,H1) Loader::Loader()

LifeCycle (EURUSD,H1) void OnInit() 0 DEINIT_REASON_PROGRAM

UseDemoAllLoop (EURUSD,H1) Initializing iAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price requires 8 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=10

LifeCycle (EURUSD,H1) void OnDeinit(const int) DEINIT_REASON_REMOVE

LifeCycle (EURUSD,H1) Loader::~Loader()

UseDemoAllLoop (EURUSD,H1) Initializing iAMA_period_fast_slow_shift_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iAMA_period_fast_slow_shift_price requires 5 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=10

UseDemoAllLoop (EURUSD,H1) Initializing iBands_period_shift_deviation_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iBands_period_shift_deviation_price requires 4 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=10

...

需要注意的是,由于我们在创建新句柄前释放了旧句柄,因此每次都会得到相同的句柄“编号”(10)。

同样重要的是,LifeCycle指标会在句柄释放后立即卸载(假设该指标未被单独添加到同一图表中,否则其引用计数器将不会归零)。

下图展示了我们的指标渲染 Alligator 数据时的状态。

Alligator 演示步骤中的 UseDemoAllLoop

Alligator 演示步骤中的 UseDemoAllLoop

如果将 ClearHandles参数设置为false,日志将呈现完全不同的情况:此时,句柄编号会不断递增,这表明指标仍在终端持续运行,造成资源被白白浪费。特别是,不会收到来自 LifeCycle的反初始化消息。

UseDemoAllLoop (EURUSD,H1) Initializing LifeCycle() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) Handle=10

LifeCycle (EURUSD,H1) Loader::Loader()

LifeCycle (EURUSD,H1) void OnInit() 0 DEINIT_REASON_PROGRAM

UseDemoAllLoop (EURUSD,H1) Initializing iAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price requires 8 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=11

UseDemoAllLoop (EURUSD,H1) Initializing iAMA_period_fast_slow_shift_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iAMA_period_fast_slow_shift_price requires 5 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=12

UseDemoAllLoop (EURUSD,H1) Initializing iBands_period_shift_deviation_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iBands_period_shift_deviation_price requires 4 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=13

UseDemoAllLoop (EURUSD,H1) Initializing iDEMA_period_shift_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iDEMA_period_shift_price requires 3 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=14

UseDemoAllLoop (EURUSD,H1) Initializing iEnvelopes_period_shift_method_price_deviation() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iEnvelopes_period_shift_method_price_deviation requires 5 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=15

...

UseDemoAllLoop (EURUSD,H1) Initializing iVIDyA_momentum_smooth_shift_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iVIDyA_momentum_smooth_shift_price requires 4 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=22

UseDemoAllLoop (EURUSD,H1) Initializing LifeCycle() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) Handle=10

UseDemoAllLoop (EURUSD,H1) Initializing iAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price requires 8 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=11

UseDemoAllLoop (EURUSD,H1) Initializing iAMA_period_fast_slow_shift_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iAMA_period_fast_slow_shift_price requires 5 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=12

UseDemoAllLoop (EURUSD,H1) Initializing iBands_period_shift_deviation_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iBands_period_shift_deviation_price requires 4 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=13

UseDemoAllLoop (EURUSD,H1) Initializing iDEMA_period_shift_price() EURUSD, PERIOD_H1

UseDemoAllLoop (EURUSD,H1) iDEMA_period_shift_price requires 3 parameters, 0 given

UseDemoAllLoop (EURUSD,H1) Handle=14

UseDemoAllLoop (EURUSD,H1) void OnDeinit(const int)

...

当循环遍历指标类型数组的索引到达最后一个元素并从头开始循环时,终端将开始向我们的代码返回已存在指标的句柄(相同数值:句柄 22 之后会再次出现 10)。