English 中文 Español Deutsch 日本語 Português
preview
Разработка системы репликации - Моделирование рынка (Часть 07): Первые улучшения (II)

Разработка системы репликации - Моделирование рынка (Часть 07): Первые улучшения (II)

MetaTrader 5Примеры | 28 августа 2023, 11:34
558 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье, "Разработка системы репликации - Моделирование рынка (Часть 06): Первые улучшения (I), мы исправили некоторые моменты и добавили несколько тестов в нашу систему репликации. Данные тесты направлены на обеспечение максимально возможной стабильности. В то же время мы начали создавать и использовать конфигурационный файл для системы.

Несмотря на все наши усилия по созданию интуитивно понятной и стабильной системы репликации, мы всё еще сталкиваемся с некоторыми нерешенными проблемами. Пути решения некоторых из них простые, другие - сложнее.

Если вы посмотрели видеоролик, приведенный в конце предыдущей статьи, то наверняка заметили в системе некоторые недостатки, которые еще требуют доработки. Я решил оставить эти недостатки на виду, чтобы все понимали, что до начала более интенсивного использования остается еще много моментов для улучшения и исправления. Это не означает, что систему нельзя использовать для практики.

Однако я не хочу добавлять новые функциональные возможности, не устранив перед этим все нюансы, которые могут привести к нестабильности в системе или платформе. Я думаю, что кто-то захочет использовать репликацию и практиковаться пока рынок открыт, ожидая появления реальной сделки или используя автоматический советник на платформе.

Однако если система репликации недостаточно стабильна, то нецелесообразно поддерживать её в рабочем состоянии пока рынок открыт, если существует вероятность возникновения реальной сделки. Данное предупреждение связано с тем, что система репликации может стать причиной сбоев или помешать нормальной работе других функций платформы.

Поэтому, продолжая работу над повышением стабильности и удобства работы с репликацией рынка, мы внедрим в код некоторые дополнительные ресурсы для дальнейшего совершенствования системы.


Обеспечиваем сохранение на графике индикатора управления

Первым улучшением, которое мы будем реализовать, будет индикатор управления. В настоящее время при запуске репликации загружается шаблон. Этот файл шаблона должен содержать индикатор, используемый для мониторинга сервиса репликации. До этого момента всё хорошо; если этот индикатор отсутствует, то манипуляции с сервисом репликации невозможны. Поэтому данный индикатор крайне важен для сервиса.

Однако после загрузки и до сих пор мы никак не могли гарантировать, что этот индикатор останется на графике. Теперь ситуация изменится, и мы будем следить за тем, чтобы он оставался на графике, а если по каким-то причинам индикатор будет удален, то сервис репликации должен быть немедленно закрыт.

Но как мы можем это сделать? Как сделать так, чтобы индикатор оставался на графике, или как проверить, действительно ли он там находится? Есть несколько способов, но самый простой и элегантный, на мой взгляд, - это использовать для проверки саму платформу MetaTrader 5, причем с помощью языка MQL5.

Как правило, в индикаторе нет определенных событий, но есть одно, особенно полезное для нас, - событие DeInit

Это событие вызывается, если что-то происходит и необходимо закрыть индикатор, чтобы затем сразу же быть запущенным событием OnInit. Таким образом, можно ли нам при вызове функции OnDeInit сообщить сервису репликации, что индикатор был снят? Можно, но есть один важный момент. Событие OnDeInit вызывается не только при удалении индикатора или закрытии графика, он также срабатывает при изменении периода графика или изменении параметров индикатора. Так что, похоже, ситуация снова усложняется. Однако, если обратимся к документации по событию OnDeInit, то увидим то, что мы можем и будем использовать: коды причин деинициализации.

Рассматривая данные коды, отметим, что есть два, которые будут очень полезны при указании на то, что индикатор управления был удален с графика. Поэтому создадим следующий код:

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        break;
        }
}

Здесь мы проверяем, является ли удаление индикатора с графика причиной срабатывания события DeInit (и, соответственно, функции OnDeInit). В этом случае мы должны сообщить сервису репликации, что индикатор больше не присутствует на графике, и что нужно немедленно закрыть сервис.

Для этого необходимо удалить из терминала глобальную переменную, которая является связующим звеном между индикатором управления и сервисом репликации. Как только удалим эту переменную, сервис репликации интерпретирует, что деятельность завершилась, и он закроется.

Если посмотреть на код сервиса, то можно заметить, что при его закрытии также закрывается и график актива репликации. Это происходит именно тогда, когда сервис выполняет следующий код:

void CloseReplay(void)
{
        ArrayFree(m_Ticks.Info);
        ChartClose(m_IdReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        GlobalVariableDel(def_GlobalVariableReplay);
}

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

Для этого добавим еще одну строку в функцию OnDeInit индикатора управления.

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}

Теперь происходит следующее: как только индикатор управления по какой-либо причине удалится с графика, даже если сервис репликации не может удалить график, в таком случае уже осуществится попытка закрытия его самим индикатором. Это может показаться несколько нелогичным, но я хочу, чтобы в случае какого-либо сбоя или ошибки со стороны пользователя, график, как и сервис репликации, освобождал платформу и не причинял неудобств.

При такой реализации, по крайней мере, у нас есть хотя бы гарантия того, что если индикатор управления перестанет присутствовать на графике, то система будет закрыта и график будет закрыт. Однако, у нас есть и другая проблема, связанная с индикатором.


Предотвращение удаления индикатора управления

Это достаточно серьезная проблема, так как может получиться так, что индикатор останется на графике, но его составные элементы будут просто удалены или уничтожены, что не позволит правильно использовать индикатор.

К счастью, данную ситуацию достаточно просто исправить. Тем не менее, данный момент требует особой осторожности, чтобы он не стал источником проблем в будущем. Оно связано с тем, что, препятствуя разрушению индикатора или удалению его элементов и объектов, мы можем создать неуправляемого монстра, вызывающего массу проблем. Чтобы решить данную проблему, перехватим и обработаем событие уничтожения объекта на графике. Давайте посмотрим, как это делается на практике.

Начнем с того, что в классе C_Control добавим следующую строку кода:

void Init(const bool state = false)
{
        if (m_szBtnPlay != NULL) return;
        m_id = ChartID();
        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
        CreateBtnPlayPause(m_id, state);
        GlobalVariableTemp(def_GlobalVariableReplay);
        if (!state) CreteCtrlSlider();
        ChartRedraw();
}

Добавляя данную строку кода, мы запросим MetaTrader 5 посылать нам событие, когда объект графика удаляется с экрана. Простое выполнение этого условия не гарантирует, что объекты не будут удалены, но хотя бы гарантирует, что платформа MetaTrader 5 сообщит нам, когда это произойдет.

Для того чтобы при удалении класса C_Control удалялись и объекты, необходимо указать MetaTrader 5, когда не надо отправлять событие удаления объекта. Одна из точек, использующих данный вид функции, показана ниже:

~C_Controls()
{
        m_id = (m_id > 0? m_id : ChartID());
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(m_id, def_PrefixObjectName);
}

Таким образом, мы сообщим MetaTrader 5, что НЕ хотим, чтобы он отправил нам событие, когда объект удаляется с графика, и сможем удалить нужные объекты без дальнейших проблем.

Однако не всё так просто, и здесь возникает потенциальная проблема. Давайте рассмотрим, приведенный ниже фрагмент:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        u_Interprocess Info;
        static int six =-1, sps;
        int x, y, px1, px2;
                
        switch (id)
        {
                case CHARTEVENT_OBJECT_CLICK:
                        if (sparam == m_szBtnPlay)
                        {
                                Info.s_Infos.isPlay =(bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                {
                                        ObjectsDeleteAll(m_id, def_PrefixObjectName + "Slider");
                                        m_Slider.szBtnPin = NULL;
                                }
                                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                                ChartRedraw();
                        }else if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
                        else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);                                                   
                break;

// ... Остальной код ....

В этой строке необходимо удалить панель управления положением, что вызовет событие удаления объекта.

Можно подумать, что мы можем просто выключить событие и потом, убрав панель управления, снова включить его. Это действительно так, но надо иметь в виду, что по мере увеличения объема кода эти действия по включению и выключению становятся гораздо более обычными, чем может показаться на первый взгляд. Кроме того, есть еще один момент: для правильного представления нужно располагать объекты в определенном порядке.

Поэтому простое включение и выключение события удаления не гарантирует правильной обработки этого события. Нам необходимо создать более элегантное и устойчивое решение, которое бы поддерживало правильный порядок объектов, чтобы их представление всегда было одинаковым и пользователь не замечал разницы в системе позиционирования.

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

inline void RemoveCtrlSlider(void)
{                       
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(m_id, def_NameObjectsSlider);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
}

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

Хотя это может показаться тривиальным, на современном уровне развития эта функция используется не один, а два раза в одной и той же функции, как показано ниже:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
        {
                u_Interprocess Info;
                static int six =-1, sps;
                int x, y, px1, px2;
                        
                switch (id)
                {
                        case CHARTEVENT_OBJECT_DELETE:
                                if(StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName)
                                {
                                        if(StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider)
                                        {
                                                RemoveCtrlSlider();
                                                CreteCtrlSlider();
                                        }else
                                        {
                                                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                                                CreateBtnPlayPause(Info.s_Infos.isPlay);
                                        }
                                        ChartRedraw();
                                }
                                break;
                        case CHARTEVENT_OBJECT_CLICK:
                                if (sparam == m_szBtnPlay)
                                {
                                        Info.s_Infos.isPlay =(bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                        if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                        {
                                                RemoveCtrlSlider();
                                                m_Slider.szBtnPin = NULL;
                                        }
                                        Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                        GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                                        ChartRedraw();
                                }else if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
                                else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);                                                   
                                break;
                        case CHARTEVENT_MOUSE_MOVE:
                                x =(int)lparam;
                                y =(int)dparam;
                                px1 = m_Slider.posPinSlider + def_MinPosXPin - 14;
                                px2 = m_Slider.posPinSlider + def_MinPosXPin + 14;
                                if ((((uint)sparam & 0x01) == 1) && (m_Slider.szBtnPin != NULL))
                                {
                                        if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six ==-1))
                                        {
                                                6 = x;
                                                sps = m_Slider.posPinSlider;
                                                ChartSetInteger(m_id, CHART_MOUSE_SCROLL, false);
                                        }
                                        if (six > 0) PositionPinSlider(sps + x - six);
                                }else if (6 > 0)
                                {
                                        6 =-1;
                                        ChartSetInteger(m_id, CHART_MOUSE_SCROLL, true);
                                }
                                break;
                }
        }

Давайте остановимся немного подробнее на той части, которая отвечает за обработку событий удаления объектов. Когда мы сообщаем платформе MetaTrader 5, что хотим получать события при удалении объекта с графика, она генерирует событие удаления для каждого удаленного объекта. Затем мы фиксируем данное событие и можем проверить, какой объект был удален. 

Одна важная деталь - вы увидите не то, какой объект будет удален, а какой объект был удален на самом деле. В нашем случае мы проверяем, является ли он одним из тех, которые используются индикатором управления. Если это так, то мы еще раз проверим, был ли это один из объектов на панели управления или была ли это кнопка управления. Если это был один из объектов, входящих в состав панели управления, то панель полностью уничтожится и тут же создастся снова. 

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

Наконец, мы принудительно размещаем все объекты на графике: так пользователь даже не заметит, что они были удалены.

В принципе, это мы сделаем для того, чтобы всё встало на свои места. А теперь давайте рассмотрим еще кое-что, что также важно для работы системы репликации.


Только один график репликации, пожалуйста.

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

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

Если у нас есть несколько графиков, отражающих одну и ту же идею, в данном случае - репликация рынка, то может оказаться, что на одном из них мы имеем значение, созданное индикатором управления, а на другом - совершенно иное значение. Хотя эта проблема еще не решена до конца, но тот простой факт, что у нас больше не будет нескольких графиков, ссылающихся на один и тот же актив в одно и то же время, принесет много пользы.

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

long ViewReplay(ENUM_TIMEFRAMES arg1)
{
        if ((m_IdReplay = ChartFirst()) > 0) do
        {
                if(ChartSymbol(m_IdReplay) == def_SymbolReplay) ChartClose(m_IdReplay);
        }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
        m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        return m_IdReplay;
}

Здесь мы проверяем, есть ли открытый график в терминале платформы MetaTrader 5. Если такой есть, то мы используем его в качестве отправной точки для проверки того, какой символ открыт. Если символ является активом, используемым в качестве рыночной репликации, то мы закроем этот график.

Проблема заключается в том, что если график символа репликации открыт, то у нас есть два варианта: первый - закрыть график и это именно то, что мы и делаем. Второй вариант - завершить цикл, но в этом втором случае может случиться так, что будет открыто более одного графика одного и того же актива. Поэтому я предпочитаю закрывать все открытые графики; мы будем это делать до тех пор, пока не будет проверен последний график. В итоге у нас не будет открытого графика репликации рынка.

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

Несмотря на это, ничто нам не мешает открыть новые графики из актива репликации после того, как система уже загружена. Можно добавить в сервис дополнительный тест, чтобы в течение всего периода работы репликации открытым оставался только один график. Но я знаю, что есть трейдеры, которым нравится использовать одновременно несколько графиков одного и того же актива. В каждом из этих графиков будет использоваться свой временной интервал.

По этой причине мы не добавим этот дополнительный тест, а сделаем кое-что другое. Мы не допустим присутствия и работы индикатора управления на каком-либо ином графике, кроме того, который был открыт сервисом. Несмотря на это, сначала можно попробовать завершить работу индикатора на исходном графике и попытаться разместить его на другом графике. Но при этом график закроется, а сервис репликации остановится, не позволив выполнить изменение.

В следующей теме рассмотрим работу по обеспечению того, чтобы индикатор управления не открывался ни на каком другом графике, кроме исходного.


Только один индикатор управления за сессию

Данная часть довольно интересна и в некоторых случаях может помочь вам. Хорошо, теперь давайте рассмотрим, как сделать так, чтобы в рабочей сессии MetaTrader 5 индикатор принадлежал только одному графику.

Чтобы понять, как это сделать, мы просмотрим следующий код:

int OnInit()
{
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, "Market Replay");
        if(GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
}

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

Но есть одна важная деталь: функция OnInit вызывается всякий раз, когда что-то происходит - либо на графике, либо при обновлении параметров индикатора. В данном случае индикатор не содержит никаких параметров и не будет получать их. Таким образом, у нас остались только события графика, которые будут происходить каждый раз при изменении временного интервала графика, т.е. если мы перейдем с 5 минут на 4 минуты, то это вызовет OnInit. В этом случае, если просто заблокировать инициализацию индикатора при наличии глобальной переменной терминала, то у нас возникнет проблема. Причина в том, что график будет закрыт, а это значит, что и сервис будет закрыт. Сложно, не правда ли?

Но решение, которое мы будем использовать, будет достаточно простым и в то же время очень элегантным. Мы будем использовать именно глобальную переменную терминала, чтобы знать, существует ли уже на каком-либо графике индикатор управления или нет. Если он существует, то его нельзя поместить на другой график до тех пор, пока он не будет отсутствовать на любом открытом графике в текущей сессии MetaTrader 5.

Для этого первым делом мы внесем изменения в код, используемый для связи между процессами.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableReplay "Replay Infos".
//+------------------------------------------------------------------+
#define def_MaxPosSlider 400
//+------------------------------------------------------------------+
union u_Interprocess
{
        double Value;
        struct st_0
        {
                bool isPlay; // Указывает, в каком режиме мы находимся - Play или Pause ...
                bool IsUsing; // Указывает на наличие или отсутствие индикатора ...
                int iPosShift; // Значение от 0 до 400 ...
        }s_Infos;
};

Помните, что мы можем добавлять в структуру внутренние переменные, если это не превышает ограничения в 8 байт - именно такой размер занимает в памяти двойная переменная. Но поскольку тип boolean будет использовать только 1 бит для существования, а в байте, который использует переменная isPlay, у нас остается 7 свободных битов, мы сможем без проблем добавить еще 7 булевых чисел. Поэтому мы будем использовать один из этих 7 свободных битов для того, чтобы узнать, есть или нет на каком-либо графике индикатор управления.

ПРИМЕЧАНИЕ: Хотя этот механизм вполне соответствующий, здесь есть одна проблема. Но об этом пока говорить не будем: рассмотрим данный вопрос в другой статье в будущем, когда потребуется внести изменения в рассматриваемую сейчас структуру.

Итак, вы, наверное, думаете, что, сделав это, всё будет в порядке, но на самом деле нам придется кое-что добавить в код. Однако мы не будем беспокоиться о коде сервиса, а лишь изменим код индикатора так, чтобы добавленная переменная была нам действительно полезна.

Первое, что нам необходимо сделать - это добавить несколько дополнительных строк в код индикатора; начнем с пункта ниже:

void Init(const bool state = false)
{
        u_Interprocess Info;
                                
        if (m_szBtnPlay != NULL) return;
        m_id = ChartID();
        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
        CreateBtnPlayPause(state);
        GlobalVariableTemp(def_GlobalVariableReplay);
        if (!state) CreteCtrlSlider();
        ChartRedraw();
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        Info.s_Infos.IsUsing = true;
        GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
}

Здесь мы сообщим и запишем в глобальной переменной терминала, что индикатор управления создан. Мы делаем вызов для регистрации данного момента, но зачем нам нужен предыдущий вызов для создания глобальной переменной терминала? Нельзя ли пропустить его? Фактически этот первый вызов служит для уведомления платформы MetaTrader 5 о том, что глобальная переменная является временной и ее не следует поддерживать. Даже если вы попросите платформу сохранить данные глобальных переменных терминала, то данные переменные, считающиеся временными, не будут иметь сохраненных значений, они будут утеряны.

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

Итак, в этом вопросе необходимо соблюдать осторожность. Поскольку когда платформа повторно располагает индикатор на графике, то значение глобальной переменной терминала может быть другим, ведь мы уже продвинулись в репликации. Тогда, если данная линия не будет размещена, система просто начнет репликацию рынка с нуля.

Это уже дает нам некую гарантию, но необходимо сделать еще одно изменение.

case CHARTEVENT_OBJECT_CLICK:
        if (sparam == m_szBtnPlay)
        {
                Info.s_Infos.isPlay =(bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                {
                        RemoveCtrlSlider();
                        m_Slider.szBtnPin = NULL;
                }
                Info.s_Infos.IsUsing = true;
                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                ChartRedraw();
        }else if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
        else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);
        break;

При каждом нажатии на кнопку паузы/воспроизведения происходит изменение значения глобальной переменной терминала. Однако в том виде, в котором был код при нажатии на эту кнопку, сохраненное значение уже не будет содержать указания на присутствие индикатора управления на графике. В связи с этим моментом нам необходимо добавить строку кода. Теперь у нас будет правильная индикация, поскольку переключение с паузы на воспроизведение или наоборот больше не будет вызывать ложной индикации.

Часть, связанная с классом C_Replay, завершена, но нам предстоит еще немного поработать. Простое создание индикации не гарантирует ничего, кроме самого факта ее существования. Нам предстоит перейти к коду индикатора, и теперь необходимо быть немного внимательнее, чтобы всё работало правильно и не превратилось в нечто со странным поведением.

Поэтому нам нужно внимательно относиться к деталям. Первое, на что следует обратить внимание - это код OnInit:

int OnInit()
{
#define def_ShortName "Market Replay"
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if(GlobalVariableCheck(def_GlobalVariableReplay))
        {
                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                if (Info.s_Infos.IsUsing)
                {
                        ChartIndicatorDelete(ChartID(), 0, def_ShortName);
                        return INIT_FAILED;
                }
        } else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
#undef def_ShortName
}

Здесь, из практических соображений, мы создали определение, в котором указывается имя индикатора: это имя будет использоваться для того, чтобы мы могли удалить индикатор из списка индикаторов, присутствующих в окне, где мы можем видеть, какие индикаторы действительно находятся на графике. Даже те, которые не отображаются, могут быть изображены в этом окне. Мы не хотим, чтобы там остались бесполезные индикаторы.

Для этого мы удалим индикатор с графика и, чтобы узнать, присутствует ли он уже на каком-либо графике, проверяем значение, создаваемое нами в глобальной переменной терминала. Таким образом, проверка становится очень простой и эффективной. Существуют и другие способы выполнить данную проверку, но поскольку мы используем глобальную переменную терминала, то проще выполнить проверку через нее.

Остальная часть функции продолжается таким же образом, но теперь уже нельзя будет добавлять индикатор управления более чем на один график в одной сессии MetaTrader 5. Ни здесь, ни в коде, который будет приложен, не добавили предупреждение о том, что индикатор уже присутствует на другом графике, но можно добавить это предупреждение до того, как функция вернет ошибку инициализации.

Хотя вам может показаться, что этого достаточно, мы должны исправить кое-что еще. Следует помнить, что каждый раз MetaTrader 5 получает запрос о смене таймфрейма - это, пожалуй, самое распространенное событие на платформе. Все индикаторы, как и многое другое, будет удалено, а затем перезагружено.

Теперь подумайте о следующем. Если индикатор через глобальную переменную терминала указывает на то, что на каком-то графике выполняется его копия, а мы меняем таймфрейм этого конкретного графика, то индикатор будет удален. Но когда платформа MetaTrader 5 восстановит индикатор на графике, он будет лишен возможности быть размещенным на графике. Причина этого как раз в том, что мы увидели в коде функции OnInit. Нужно как-то изменить глобальную переменную терминала, чтобы она больше не сообщала о наличии индикатора управления.

Существуют довольно экзотические способы решения этой задачи, но, опять же, платформа MetaTrader 5 вместе с языком MQL5 предлагает довольно простые средства для ее решения. Давайте посмотрим на следующий код:

void OnDeinit(const int reason)
{
        u_Interprocess Info;
        
        switch (reason)
        {
                case REASON_CHARTCHANGE:
                        if(GlobalVariableCheck(def_GlobalVariableReplay))
                        {
                                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.IsUsing = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                        }
                        break;
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}

Помните следующее: При удалении индикатора формируется событие DeInit, которое вызывает функцию OnDeInit. Данная функция получает в качестве параметра значение, указывающее на причину вызова. Именно это значение мы будем использовать.

Эти значения можно найти в разделе коды причин деинициализации. Здесь мы видим, что REASON_CHARTCHANGE будет указывать на то, что графический период был изменен. Итак, теперь мы проведем тестирование - всегда полезно тестировать вещи, никогда не предполагайте, ПРОВЕРЯЙТЕ- есть ли глобальная переменная терминала с ожидаемым именем. Если это так, то мы зафиксируем значение переменной. Поскольку сервис может быть запущен, а мы не хотим вмешиваться в его работу, мы изменяем информацию здесь, чтобы указать: индикатор управления больше не будет присутствовать на графике. После этого мы снова записываем информацию в глобальной переменной терминала.

Здесь я должен предупредить о возможном недостатке этой системы. Хотя вероятность того, что может что-то получиться плохо, следует всегда помнить: в методе есть ошибка, и нужно быть готовым к возможным проблемам.

Проблема здесь заключается в том, что между чтением и записью переменной будет небольшой разрыв. Хоть он и небольшой, но он существует, когда сервис может записать значение в глобальную переменную терминала раньше, чем это сделает индикатор. При возникновении такого рода событий значение, ожидаемое сервисом при обращении к глобальной переменной терминала, будет отличаться от того, что находится в переменной на самом деле.

Есть способы обойти этот недостаток, но в этой системе, работающей с репликацией рынка, он не критичен, мы можем не обращать внимания на этот недостаток и пропускать его без опасений. Однако, если вы хотите использовать этот же механизм в чем-то более сложном, где хранимые значения являются критически важными, то я советую вам более подробно рассмотреть средства блокировки и разблокировки чтения и записи общей памяти, поскольку глобальная переменная терминала является именно такой, общей памятью.

Из приведенного ниже видеоролика вы сможете понять часть того, что было исправлено и что еще предстоит исправить. Обратите внимание на то, что ситуация начинает становиться действительно серьезной.




Заключение

Хотя описанная здесь система представляется идеальной для устранения сбоев в работе, вызванных неправильным использованием индикаторов управления, она всё же не является действительно эффективным решением, поскольку позволяет избежать лишь некоторых видов проблем, которые у нас могут реально возникнуть.

Думаю, посмотрев видеоролик, вы заметите, что нам предстоит решить еще одну проблему, которая намного сложнее, чем кажется на первый взгляд.


Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/10784

Прикрепленные файлы |
Market_Replay.zip (13057.92 KB)
Нейросети — это просто (Часть 56): Использование ядерной нормы для стимулирования исследования Нейросети — это просто (Часть 56): Использование ядерной нормы для стимулирования исследования
Исследование окружающей среды в задачах обучения с подкреплением является актуальной проблемой. Ранее мы уже рассматривали некоторые подходы. И сегодня я предлагаю познакомиться с ещё одним методом, основанным на максимизации ядерной нормы. Он позволяет агентам выделять состояния среды с высокой степенью новизны и разнообразия.
Брутфорс-подход к поиску закономерностей (Часть VI): Циклическая оптимизация Брутфорс-подход к поиску закономерностей (Часть VI): Циклическая оптимизация
В этой статье я покажу первую часть доработок, которые позволили мне не только замкнуть всю цепочку автоматизации для торговли в MetaTrader 4 и 5, но и сделать что-то гораздо интереснее. Отныне данное решение позволяет мне полностью автоматизировать как процесс создания советников, так и процесс оптимизации, а также минимизировать трудозатраты на поиск эффективных торговых конфигураций.
Разработка системы репликации - Моделирование рынка (Часть 08): Блокировка индикатора Разработка системы репликации - Моделирование рынка (Часть 08): Блокировка индикатора
В этой статье мы рассмотрим, как заблокировать индикатор при простом использовании языка MQL5, и сделаем это очень интересным и удивительным способом.
Нейросети — это просто (Часть 55): Контрастный внутренний контроль (CIC) Нейросети — это просто (Часть 55): Контрастный внутренний контроль (CIC)
Контрастное обучение (Contrastive learning) - это метод обучения представлению без учителя. Его целью является обучение модели выделять сходства и различия в наборах данных. В данной статье мы поговорим об использовании подходов контрастного обучения для исследования различных навыков Актера.