Удаление экземпляров индикаторов: 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// текущая позиция внутри массива MainLoop
      
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// текущий выбранный тип индикатора
   
void OnTimer()
{
   if(Handle != INVALID_HANDLE && ClearHandles)
   {
      IndicatorRelease(Handle);
      /*
      // дескриптор по-прежнему равен 10, но более недействителен
      // если раскомментировать фрагмент, получим указанную ниже ошибку
      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;
   
   // создаем дескриптор с параметрами по умолчанию
   // (т.к. передаем пустую строку в третьем аргументе конструктора)
   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(); // чистим буфера, т.к. будет отображен новый индикатор
   ChartSetSymbolPeriod(0NULL0); // запрашиваем полную перерисовку
   ...
   // далее настройка диаграмм - аналогично прежней
   ...
   Comment("DemoAll: ", (IndicatorSelector == iCustom_ ? IndicatorCustom : s),
      "(default-params)");
}

Главное отличие заключаются в том, что тип текущего создаваемого индикатора IndicatorSelector теперь задается не пользователем, а последовательно выбирается из массива MainLoop по индексу Cursor. При каждом вызове таймера этот индекс увеличивается циклически, то есть при достижении конца массива мы перескакиваем на его начало.

Для всех индикаторов строка с параметрами пуста. Так сделано для унификации их инициализации. В результате каждый индикатор будет создаваться с собственными умолчаниями.

В начале обработчика OnTimer делается вызов IndicatorRelease для предыдущего дескриптора. Однако мы предусмотрели входную переменную ClearHandles, чтобы отключить данную ветвь оператора if и посмотреть, что будет, если не чистить дескрипторы.

input bool ClearHandles = true;

По умолчанию ClearHandles равна true, то есть индикаторы будут удаляться как положено.

Наконец, еще одним дополнением настройки являются строки с очисткой буферов и запросом полной перерисовки графика — и то, и другое нужно, потому что мы подменили у себя подчиненный индикатор, поставляющий отображаемые данные.

Обработчик 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.

UseDemoAllLoop на шаге демонстрации Alligator

UseDemoAllLoop на шаге демонстрации Alligator

Если поменять значение 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).