English 中文 Español Deutsch 日本語 Português
preview
Разработка системы репликации - Моделирование рынка (Часть 13): Появление СИМУЛЯТОРА (III)

Разработка системы репликации - Моделирование рынка (Часть 13): Появление СИМУЛЯТОРА (III)

MetaTrader 5Тестер | 28 сентября 2023, 10:05
600 0
Daniel Jose
Daniel Jose


Введение

Предыдущая статья "Разработка системы репликации - Моделирование рынка (Часть 12): Появление СИМУЛЯТОРА (II)", была подготовкой к данной статьи. Сегодня мы внесем некоторые изменения в систему моделирования, чтобы добиться большей последовательности предоставляемых данных. В то же время мы внесем некоторые радикальные изменения, чтобы сделать систему более эффективной с точки зрения обработки. Это понадобится нам на следующих этапах создания нашей системы репликации/моделирования. Но вопрос заключается в следующем: для того, чтобы система действительно могла бы использоваться или репликацией, или симулятором, нам нужно, чтобы она имела постоянное поведение или, по крайней мере, как можно более постоянное и последовательное. Мы не можем просто заставить систему работать каким-то определенным образом в один момент времени, а затем работать совершенно по-другому и непредсказуемо в другой момент.

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

Решение этой проблемы – далеко не простое, потому что нам придется изменить способ, по которому оно на самом деле построено. Можно подумать, что данная задача будет легкой, но это не так. Она включает в себя определенный тип моделирования, который делает ее довольно сложной, если мы не знаем, что делаем. Даже мне (а я показываю вам, как это делать) потребовалось всё это время, чтобы понять, что что-то не так с системой построения баров. Я заметил это только тогда, когда приступил к этапу моделирования, где различия с течением времени стали действительно очевидны, поскольку это включает в себя некоторые расчеты, которые мы еще увидим. Но даже сейчас не думайте, что я смогу решить данную проблему. Эта проблема будет решена на следующем этапе построения, то есть, в будущем. Начнем с внесения некоторых исправлений и представим новую систему построения 1-минутных баров.


Новый сервис репликации рынка

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

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"
#property description "Сервис репликации-моделирования для MT5."
#property description "Он независим от индикатора Market Replay."
#property description "Можно узнать по подробнее в данной статье:"
#property description "https://www.mql5.com/ru/articles/11034"
#property link "https://www.mql5.com/ru/articles/11034"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Config.txt";  //Конфигурационный файл "Replay".
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Начальный таймфрейм для графика.
input bool              user02 = true;          //Визуализация конструкции баров.
input bool              user03 = true;          //Визуализация метрик создания.
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        Replay;
        
        Replay.InitSymbolReplay();
        if (Replay.SetSymbolReplay(user00))
        {
                Print("Ожиданаем разрешения от индикатора [Market Replay] на начало репликации ...");
                if (Replay.ViewReplay(user01))
                {
                        Print("Разрешение получено. Сервис репликации теперь может быть использован...");
                        while (Replay.LoopEventOnTime(user02, user03));
                }
        }       
        Replay.CloseReplay();
        Print("Сервис репликации совершен...");
}
//+------------------------------------------------------------------+

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

Однако если посмотреть повнимательнее, то вы увидите, что для пользователя добавлена ​​новая переменная. Это позволяет нам создавать и проверять время, которое тратиться или требуется для создания 1-минутных баров. Обратите внимание, что теперь у нас есть только один вызов, который блокирует цикл создания баров. Данный вызов не вернется, за исключением двух очень специфических ситуаций. Но скоро мы поймем, о каких ситуациях идет речь. Тогда данный цикл будет работать как бесконечный цикл, но на самом деле он будет регулироваться самой процедурой, которая будет находиться внутри класса объекта. Из-за этого упрощения служебный файл на самом деле является только этим. Что касается класса объектов, то, как многие из вас, возможно, уже подумали, его сложность увеличилась. В результате несколько функций, которые ранее были общедоступными, больше не являются общедоступными и теперь являются частными для класса. Это называется сокрытием метода. Поэтому единственные действительно публичные элементы — это функции, которые присутствуют и появляются в служебном файле, который можно увидеть выше.

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

                bool ViewReplay(ENUM_TIMEFRAMES arg1)
                        {
                                u_Interprocess info;
                                
                                if ((m_IdReplay = ChartFirst()) > 0) do
                                {
                                        if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                                        {
                                                ChartClose(m_IdReplay);
                                                ChartRedraw();
                                        }
                                }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
                                info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
                                ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
                                ChartRedraw(m_IdReplay);
                                GlobalVariableDel(def_GlobalVariableIdGraphics);
                                GlobalVariableTemp(def_GlobalVariableIdGraphics);
                                GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
                                while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
                                
                                return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
                        }

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

                bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
                        {

                                u_Interprocess Info;
                                int iPos, iTest;
                                
                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;
                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               
                                return (m_ReplayCount == m_Ticks.nTicks);
                        }

Данная процедура — гораздо больше, чем кажется. Вы можете подумать, что она постоянно входит и выходит, но на самом деле она просто возвращается к коду служебного файла только в двух случаях. Первый — если пользователь приостанавливает создание 1-минутных баров. А второй — когда пользователь прекращает работу сервиса либо из-за закрытия графика, либо из-за того, что у нас больше нет тиков для использования. Короче говоря, когда сервис по каким-то причинам закрылся. В любом другом случае у нас будет значение TRUE, если запас тиков исчерпан, и значение FALSE в любой другой гипотезе. А значение FALSE завершит систему репликации/моделирования, как видно из анализа служебного кода.

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

// ... объявление переменных ...

                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;

//... остальной код (будет рассмотрен далее) ...

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

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

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

// ... Код из предыдущего цикла ...

                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               

// ... Остальная часть функции ...

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

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

Обратите внимание, что у нас есть часы, минуты и секунды, которые не имеют для нас большого значения: что действительно важно для нас здесь, при репликации/моделировании, это число, которое идет после секунд, то есть миллисекунд. Если посмотреть на эти цифры, то вы заметите, что они могут показаться долгими по времени. Однако на самом деле нам нужно понимать не время в миллисекундах, а разницу между временем последнего тика и тем, что мы собираемся показать далее. Посмотрев на это, мы заметим, что в некоторых случаях разница очень мала, иногда менее 2 миллисекунд. Предыдущая система не могла с этим справиться; нам нужна другая, более быстрая система. Несмотря на наши попытки, она не могла работать должным образом, когда времени было очень мало, как показано на изображении выше. Это время между одним вызовом и другим составляет намного меньше 10 миллисекунд.

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

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



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

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

                void AdjustPositionToReplay(const bool bViewBuider)
                        {
                                u_Interprocess Info;
                                MqlRates       Rate[def_BarsDiary];
                                int            iPos,   nCount;
                                
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
                                iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
                                Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
                                if (iPos < m_ReplayCount)
                                {
                                        CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
                                        if ((m_dtPrevLoading == 0) && (iPos == 0))
                                        {
                                                m_ReplayCount = 0;
                                                Rate[m_ReplayCount].close = Rate[m_ReplayCount].open = Rate[m_ReplayCount].high = Rate[m_ReplayCount].low = m_Ticks.Info[iPos].last;
                                                Rate[m_ReplayCount].tick_volume = Rate[m_ReplayCount].real_volume = 0;
                                                CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                                        }else
                                        {
                                                for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
                                                m_ReplayCount++;
                                        }
                                }else if (iPos > m_ReplayCount)
                                {
                                        if (bViewBuider)
                                        {
                                                Info.s_Infos.isWait = true;
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                        }else
                                        {
                                                for(; Rate[0].time > m_Ticks.Info[m_ReplayCount].time; m_ReplayCount++);
                                                for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
                                                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
                                        }
                                }
                                for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay();
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.isWait = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                        }

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

inline void CreateBarInReplay(const bool bViewMetrics = false)
                        {
#define def_Rate m_MountBar.Rate[0]

                                static ulong _mdt = 0;
                                int i;
                                
                                if (m_MountBar.bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)))
                                {
                                        if (bViewMetrics)
                                        {
                                                _mdt = (_mdt > 0 ? GetTickCount64() - _mdt : _mdt);
                                                i = (int) (_mdt / 1000);
                                                Print(TimeToString(m_Ticks.Info[m_ReplayCount].time, TIME_SECONDS), " - Metrica: ", i / 60, ":", i % 60, ".", (_mdt % 1000));
                                                _mdt = GetTickCount64();
                                        }
                                        m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                def_Rate.close = m_Ticks.Info[m_ReplayCount].last;
                                def_Rate.open = (m_MountBar.bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (m_MountBar.bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (m_MountBar.bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
                                def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                                def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
                                def_Rate.time = m_MountBar.memDT;
                                m_MountBar.bNew = false;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate, 1);
                                m_ReplayCount++;
                                
#undef def_Rate
                        }

Она практически не требует пояснений. Здесь мы создаем 1-минутные бары один за другим и затем отправляем их на график. Правда, с тиками мы пока не работаем, т.е. пока не можем использовать некоторые ресурсы MetaTrader 5 для репликации/моделирования. Мы это будем реализовать в будущем. Так что пока не беспокойтесь. При этом мы также проводим все необходимые проверки и замеры, чтобы определить, стоит ли приступать к построению нового бара или нет. Данную область можно пропустить, если мы захотим сделать это в будущем, поскольку всё, что она делает — это отображение метрик времени между одним баром и предыдущим. Сейчас это весьма полезно, поскольку помогает нам точно настроить значения задержки внутри цикла создания.

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


Просмотр графика СЛУЧАЙНОГО БЛУЖДАНИЯ

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

То, что мы действительно хотим и собираемся создать — это так называемое СЛУЧАЙНОЕ БЛУЖДАНИЕ. У данной «прогулки» есть некоторые правила. В отличие от того, что обычно программируется, здесь мы не можем допустить, чтобы система являлась полностью случайной. Нам нужно создать некоторые математические правила, чтобы постараться направить движение. Поймите меня правильно, случайное блуждание на самом деле является совершенно случайным и непредсказуемым движением, по крайней мере, в краткосрочной перспективе. Но поскольку мы не создаем совершенно непредсказуемое движение, и поскольку знаем, где оно начинается и где заканчивается, система не является полностью случайной. Тем не менее, мы всё равно добавим некоторую случайность внутри баров.

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

Можно подумать, что этот график (ДА, это график, который я покажу вам позже) совсем не похож на случайное движение. Это больше похоже на полный бардак, но на самом деле это случайное движение, которое достигается с помощью скачков между моментами времени. Чтобы добиться данного шага, мы будем использовать следующую строку данных, полученных с помощью платформы MetaTrader 5. Помните, что каждая строка представляет собой 1-минутный бар.

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

#define def_TEST_SIMULATION
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

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

Это определение позволит нам создать проверку моделирования, чтобы мы могли проанализировать сгенерированный график движения. С другой стороны, этот файл будет содержать данные, соответствующие моделируемому движению внутри 1-минутного бара. Позже мы посмотрим, где и когда будет создан этот файл. Как только это определено непосредственно в служебном файле, перед объявлением заголовочных файлов мы можем использовать это определение в файлах MQH. Теперь перейдем к файлу C_Replay.Mqh, чтобы понять, что будем делать, чтобы получить данные.

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

                bool LoadBarsToTicksReplay(const string szFileNameCSV)
                        {
                                int file;
                                MqlRates rate[1];
                                MqlTick tick[];
                                
                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Преобразуем бары в тики. Подождите...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                Simulation(rate[0], tick);
#ifdef def_TEST_SIMULATION
        FileClose(file);
        file = FileOpen(def_FILE_OUT_SIMULATION, FILE_ANSI | FILE_WRITE);
        for (long c0 = 0; c0 < m_Ticks.nTicks; c0++)
                FileWriteString(file, StringFormat("%0.f\n", m_Ticks.Info[c0].last));
        FileClose(file);
        ArrayFree(tick);
        
        return false;
#endif
                                        }
                                        FileClose(file);
                                        ArrayFree(tick);

                                        return (!_StopFlag);
                                }
                                
                                return false;
                        }

Одна деталь, которая может заинтересовать некоторых, менее опытных, заключается в том, всегда ли симулятор будет выполнять один и тот же тип моделирования движения внутри 1-минутного бара. На самом деле, НЕТ. Мы попытаемся создать способ, при котором каждый бар будет уникальным и будет иметь исключительный механизм. Однако, если вы хотите, чтобы движение между барами всегда было одинаковым или хотя бы похожим, вам просто нужно перевести систему в состояние, при котором она всегда запускает симулятор с определенного значения. Это можно делать добавлением вызова перед вызовом симулятора и установлением фиксированного значения в этом вызове, как показываем ниже:

// ... Код ...

                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Преобразуем бары в тики. Подождите...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                srand(5);
                                                Simulation(rate[0], tick);

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

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



Очень важно знать, как создать график, так как вам нужно будет каким-то образом проверить, правильно ли создается движение. Простого наблюдения за движением создаваемых баров нам будет недостаточно, чтобы определить, действительно ли оно имеет соответствующий уровень случайности. Еще один очень распространенный среди программистов момент: попытки усложнить расчеты, выполняемые в рамках процедуры моделирования, не гарантируют того, что у нас в самом деле есть действительно случайное движение, подобное СЛУЧАЙНОМУ БЛУЖДАНИЮ. Итак, давайте посмотрим видео. Оно короткое и, помимо всего прочего, очень поможет нам на следующих этапах, так как я покажу только полученный график. Нам нужно понять, почему график выглядит именно так. Это позволит выполнить локальные проверки.


Заключение

Чтобы не усложнять и не запутывать вас, дорогой читатель, относительно того, что мы увидим в реализации модели СЛУЧАЙНОГО БЛУЖДАНИЯ, я собираюсь завершить эту статью на данном пункте. В следующей статье мы реализуем модель СЛУЧАЙНОГО БЛУЖДАНИЯ, в которой сгенерированный график будет выглядеть по-другому. Мы также рассмотрим проблемы, которые представляет нам это «случайное блуждание», и лежащую в его основе идею. Но поскольку у нас уже есть что-то новое для многих и достаточно сложное для других, я не хочу еще больше усложнять ситуацию, несмотря на то, что в приложенном коде вам будет доступен материал, который я объясню только в следующей статье.

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

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

//#define def_TEST_SIMULATION // <<-- Оставьте эту строку в таком виде, чтобы иметь возможность использовать сервис репликации/моделировании ....
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

Если вы не оставите всё так, как показано выше, сервис всегда будет сообщать об ошибке при попытке просмотреть больше данных, помимо графика. ПОЭТОМУ обратите внимание на этот момент.

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

Прикрепленные файлы |
Готовые шаблоны для подключения индикаторов в экспертах (Часть 3): Трендовые индикаторы Готовые шаблоны для подключения индикаторов в экспертах (Часть 3): Трендовые индикаторы
В этой справочной статье рассмотрим стандартные индикаторы из категории "Трендовые индикаторы". Создадим готовые к применению шаблоны использования этих индикаторов в советниках — объявление и установка параметров, инициализация и деинициализация индикаторов и получение данных и сигналов из индикаторных буферов в советниках.
Разработка системы репликации - Моделирование рынка (Часть 12): Появление СИМУЛЯТОРА (II) Разработка системы репликации - Моделирование рынка (Часть 12): Появление СИМУЛЯТОРА (II)
Разработка симулятора может оказаться гораздо интереснее, чем кажется. Сегодня мы сделаем еще несколько шагов в этом направлении, потому что всё становится интереснее.
Разработка системы репликации - Моделирование рынка (Часть 14): Появление СИМУЛЯТОРА (IV) Разработка системы репликации - Моделирование рынка (Часть 14): Появление СИМУЛЯТОРА (IV)
В этой статье мы продолжим этап разработки симулятора. Однако сейчас мы увидим, как эффективно создать движение типа «СЛУЧАЙНОЕ БЛУЖДАНИЕ». Этот тип движения весьма интригующий, поскольку служит основой всего, что происходит на рынке капитала. Кроме того, мы начнем понимать некоторые концепции, основополагающие для тех, кто проводит анализ рынка.
Как обнаруживать тренды и графические паттерны с помощью MQL5 Как обнаруживать тренды и графические паттерны с помощью MQL5
В статье представлен метод автоматического обнаружения моделей ценовых действий с помощью MQL5, таких как тренды (восходящий, нисходящий, боковой) и графические модели (двойная вершина, двойное дно).