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

Разработка системы репликации - Моделирование рынка (Часть 05): Предварительный просмотр

MetaTrader 5Примеры | 14 июля 2023, 11:19
570 0
Daniel Jose
Daniel Jose

Введение

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

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

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

Можно подумать об этом так: у нас есть файл со всеми торговыми тиками, выполненными в определенный день. Однако, используя только содержимое этого файла, мы не сможем получить действительно полезную информацию ни от одного индикатора. Даже если мы используем, например, 3-периодную скользящую среднюю, которая как раз и используется в системе JOE DI NAPOLI, сигнал не будет сгенерирован, пока не будет создано как минимум 3 бара. Только после этого скользящая средняя отобразится на графике. С точки зрения практичности, до сегодняшнего дня эта система совершенно бесполезна и неработоспособна.

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


Разбираем некоторые детали

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

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

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

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

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

Чтобы определить количество предыдущих баров, необходимых в нашей репликации, нам нужно выполнить довольно простой расчет:


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

Пример 01:

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

n = ( 200 * 5 ) + 5

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

Пример 02:

Для исследования мы хотим использовать 21-периодную скользящую среднюю, 50-периодную скользящую среднюю, RSI, установленный на 14 периодов, и внутридневной VWAP.  Мы будем выполнять исследование в 5 и 15-минутных таймфреймах. Однако время от времени мы будем проверять 30-минутный таймфрейм для подтверждения сделок на вход или выход. Какое минимальное количество баров нам нужно иметь перед началом воспроизведения?

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

n = ( 50 * 30 ) + 30

Это равно 1530, т.е. перед началом фактической репликации необходимо иметь не менее 1530 1-минутных баров.

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

Теперь у нас возникает другой вопрос. Обычно на B3 (Бразильская фондовая биржа) торговая сессия (период торговли) проходит в несколько этапов и с разными временными окнами для некоторых классов активов. Фьючерсные рынки обычно начинают торговать в 9:00 и заканчивают в 18:00, но в определенное время года этот период продлевается до 18:30, в то время как фондовый рынок работает с 10:00 до 18:00. Другие рынки и активы имеют свои особенные часы работы. Поэтому перед началом торгов необходимо проверить данные параметры. Постарайтесь следовать этой идее. Из-за того, что B3 имеет торговое окно, вам часто придется загружать данные за несколько дней, чтобы получить рассчитанное минимальное количество баров.

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

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


где N - количество дней, F - полный час закрытия торгового окна, K - полный час начала торгового окна и M - количество дробных минут в этом окне. Для иллюстрации рассмотрим пример выполнения расчета:

Фьючерсы на доллар начали торговаться в 9:00 и закончили в 18:30. Сколько 1-минутных баров было у нас в тот день?

n = (( 18 - 9 ) * 60 ) + 30

Это равно 570 1-минутным барам.

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

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


Реализация

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

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string    user00 = "WIN$N_M1_202108030900_202108051754";  //Arquivo de Barras ( Prev. )
input string    user01 = "WINQ21_202108060900_202108061759";    //Arquivo com ticks ( Replay )
//+------------------------------------------------------------------+
C_Replay        Replay;

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

То, что мы можем указать только один файл, означает, что если нам нужно использовать несколько дней, нам придется заставить MetaTrader 5 сгенерировать файл с несколькими встроенными днями. Это звучит сложно, но всё гораздо проще и легче, чем кажется.


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

Но почему мы должны использовать 1-минутные бары? Почему бы не использовать 5- или 15-минутные бары?

Причина в том, что при использовании 1-минутных баров, у нас будет возможность использовать любой таймфрейм. Платформа MetaTrader 5 сама создаст для нас различные бары таймфрейма, что очень удобно, поскольку нам не нужно беспокоиться о внесении каких-либо корректировок. Теперь, если мы используем базу данных, например, с 15-минутными временными барами, мы не сможем использовать другие таймфреймы простым способом. Это связано с тем, что мы не сможем рассчитывать на поддержку, которую предоставляет платформа MetaTrader 5 при использовании 1-минутных баров. По этой причине мы всегда, предпочтительно, должны использовать время бара в 1 минуту.

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

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

void OnStart()
{
        ulong t1;
        int delay = 3;
        long id;
        u_Interprocess Info;
        bool bTest = false;
        
        Replay.InitSymbolReplay();
        Replay.LoadPrevBars(user00);
        if (!Replay.LoadTicksReplay(user01)) return;
        Print("Aguardando permissão para iniciar replay ...");

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

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

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

        id = Replay.ViewReplay();
        while (!GlobalVariableCheck(def_GlobalVariableReplay)) Sleep(750);
        Print("Permissão concedida. Serviço de replay já pode ser utilizado...");
        t1 = GetTickCount64();

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

так как в этом случае создастся глобальная переменная терминала, обеспечивающая связь между сервисом терминала и индикатором управления. Этот индикатор был создан и может быть изучен в статьях "Разработка системы репликации - Моделирование рынка (Часть 03): Внесение корректировок (I)"и "Разработка системы репликации - Моделирование рынка (Часть 04): Внесение корректировок (II)".

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

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


Новый класс C_Replay

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

int      m_ReplayCount;
datetime m_dtPrevLoading;
long     m_IdReplay;
struct st00
{
        MqlTick Info[];
        int     nTicks;
}m_Ticks;

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

void InitSymbolReplay(void)
{
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        SymbolSelect(def_SymbolReplay, true);
}

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

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

Следующая функция, которая претерпела изменения - это загрузка торгуемых тиков.

#define macroRemoveSec(A) (A - (A % 60))
                bool LoadTicksReplay(const string szFileNameCSV)
                        {
                                int     file,
                                        old;
                                string  szInfo;
                                MqlTick tick;
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        Print("Carregando ticks de replay. Aguarde...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray);
                                        old = m_Ticks.nTicks = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        while ((!FileIsEnding(file)) && (m_Ticks.nTicks < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file) + " " + FileReadString(file);
                                                tick.time = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                tick.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                tick.bid = StringToDouble(FileReadString(file));
                                                tick.ask = StringToDouble(FileReadString(file));
                                                tick.last = StringToDouble(FileReadString(file));
                                                tick.volume_real = StringToDouble(FileReadString(file));
                                                tick.flags = (uchar)StringToInteger(FileReadString(file));
                                                if ((m_Ticks.Info[old].last == tick.last) && (m_Ticks.Info[old].time == tick.time) && (m_Ticks.Info[old].time_msc == tick.time_msc))
                                                        m_Ticks.Info[old].volume_real += tick.volume_real;
                                                else
                                                {
                                                        m_Ticks.Info[m_Ticks.nTicks] = tick;
                                                        m_Ticks.nTicks += (tick.volume_real > 0.0 ? 1 : 0);
                                                        old = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : old);
                                                }
                                        }
                                }else
                                {
                                        Print("Aquivo de ticks ", szFileNameCSV,".csv não encontrado...");
                                        return false;
                                }
                                return true;
                        };
#undef macroRemoveSec

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

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

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

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

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

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

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

bool LoadPrevBars(const string szFileNameCSV)
{
        int     file,
                iAdjust = 0;
        datetime dt = 0;
        MqlRates Rate[1];
                                
        if ((file = FileOpen("Market Replay\\Bars\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                for (int c0 = 0; c0 < 9; c0++) FileReadString(file);
                Print("Carregando barras previas para Replay. Aguarde ....");
                while (!FileIsEnding(file))
                {
                        Rate[0].time = StringToTime(FileReadString(file) + " " + FileReadString(file));
                        Rate[0].open = StringToDouble(FileReadString(file));
                        Rate[0].high = StringToDouble(FileReadString(file));
                        Rate[0].low = StringToDouble(FileReadString(file));
                        Rate[0].close = StringToDouble(FileReadString(file));
                        Rate[0].tick_volume = StringToInteger(FileReadString(file));
                        Rate[0].real_volume = StringToInteger(FileReadString(file));
                        Rate[0].spread = (int) StringToInteger(FileReadString(file));
                        iAdjust = ((dt != 0) && (iAdjust == 0) ? (int)(Rate[0].time - dt) : iAdjust);
                        dt = (dt == 0 ? Rate[0].time : dt);
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
                m_dtPrevLoading = Rate[0].time + iAdjust;
                FileClose(file);
        }else
        {
                Print("Falha no acesso ao arquivo de dados das barras previas.");
                m_dtPrevLoading = 0;                                    
                return false;
        }
        return true;
}

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

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

На этот раз мы ввели цикл, который будет заканчиваться только на последней строке файла. Размер или количество данных в этом файле не ограничено. В итоге все данные будут считаны. Считанные данные следуют моделированию OHCL и помещаются во временную структуру. Когда всё это сделано, нам нужно обратить внимание на такой момент: как узнать, где начинаются репликации и где заканчиваются предыдущие бары?

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

Давайте перейдем к следующей функции, которая очень важна при всей своей простоте.

long ViewReplay(void)
{
        m_IdReplay = ChartOpen(def_SymbolReplay, PERIOD_M1);                            
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        return m_IdReplay;
}

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

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

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

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

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

Следующая функция также претерпела некоторые изменения.

inline int Event_OnTime(void)
{
        bool    bNew;
        int     mili, iPos;
        u_Interprocess Info;
        static MqlRates Rate[1];
        static datetime _dt = 0;
                                
        if (m_ReplayCount >= m_Ticks.nTicks) return -1;
        if (bNew = (_dt != m_Ticks.Info[m_ReplayCount].time))
        {
                _dt = m_Ticks.Info[m_ReplayCount].time;
                Rate[0].real_volume = 0;
                Rate[0].tick_volume = 0;
        }
        mili = (int) m_Ticks.Info[m_ReplayCount].time_msc;
        do
        {
                while (mili == m_Ticks.Info[m_ReplayCount].time_msc)
                {
                        Rate[0].close = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].open = (bNew ? Rate[0].close : Rate[0].open);
                        Rate[0].high = (bNew || (Rate[0].close > Rate[0].high) ? Rate[0].close : Rate[0].high);
                        Rate[0].low = (bNew || (Rate[0].close < Rate[0].low) ? Rate[0].close : Rate[0].low);
                        Rate[0].real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                        bNew = false;
                        m_ReplayCount++;
                }
                mili++;
        }while (mili == m_Ticks.Info[m_ReplayCount].time_msc);
        Rate[0].time = m_Ticks.Info[m_ReplayCount].time;
        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
        iPos = (int)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
        GlobalVariableGet(def_GlobalVariableReplay, Info.Value);
        if (Info.s_Infos.iPosShift != iPos)
        {
                Info.s_Infos.iPosShift = iPos;
                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
        }
        return (int)(m_Ticks.Info[m_ReplayCount].time_msc < mili ? m_Ticks.Info[m_ReplayCount].time_msc + (1000 - mili) : m_Ticks.Info[m_ReplayCount].time_msc - mili);
}

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

Вы наверно заметили, что тиковый объем всегда равен нулю. А почему так? Чтобы понять это, нужно смотреть документ "". Если вы прочитали его и не поняли документ или не поняли, почему тиковый объем всегда равен нулю, вы игнорируете очень важный факт. Поэтому мы попробуем понять, почему это значение всегда равно нулю.

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

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

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

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

int AdjustPositionReplay()
{
        u_Interprocess Info;
        MqlRates Rate[1];
        int iPos = (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks);
                                
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        if (Info.s_Infos.iPosShift == iPos) return 0;
        iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / def_MaxPosSlider));
        if (iPos < m_ReplayCount)
        {
                CustomRatesDelete(def_SymbolReplay, m_dtPrevLoading, LONG_MAX);
                m_ReplayCount = 0;
                if (m_dtPrevLoading == 0)
                {
                        Rate[0].close = Rate[0].open = Rate[0].high = Rate[0].low = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].tick_volume = 0;
                        Rate[0].time = m_Ticks.Info[m_ReplayCount].time - 60;
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
        };
        for (iPos = (iPos > 0 ? iPos - 1 : 0); m_ReplayCount < iPos; m_ReplayCount++) Event_OnTime();
        return Event_OnTime();
}

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


Заключение

Всё остальное в функции уже было описано в статье Разработка системы репликации - Моделирование рынка (Часть 04): Внесение корректировок (II), поэтому мы здесь не будем подробно останавливаться на этом.

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




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

Прикрепленные файлы |
Market_Replay.zip (6102.2 KB)
Разработка системы репликации - Моделирование рынка (Часть 06): Первые улучшения (I) Разработка системы репликации - Моделирование рынка (Часть 06): Первые улучшения (I)
В этой статье мы приступим к стабилизации всей системы, иначе мы рискуем не выполнить следующие шаги.
Разработка системы репликации - Моделирование рынка (Часть 04): Внесение корректировок (II) Разработка системы репликации - Моделирование рынка (Часть 04): Внесение корректировок (II)
Сегодня мы продолжим разработку системы и управления. Без возможности управления сервисом сложно двигаться вперед и совершенствовать систему.
StringFormat(). Обзор, готовые примеры использования StringFormat(). Обзор, готовые примеры использования
Статья является продолжением обзора функции PrintFormat(). Рассмотрим вкратце форматирование строк при помощи StringFormat() и их дальнейшее использование в программе. Напишем шаблоны для вывода информации о символе в журнал терминала. Статья будет полезна как новичкам, так и уже опытным разработчикам.
Нейросети — это просто (Часть 49): Мягкий Актор-Критик (Soft Actor-Critic) Нейросети — это просто (Часть 49): Мягкий Актор-Критик (Soft Actor-Critic)
Мы продолжаем рассмотрение алгоритмов обучения с подкреплением в решении задач непрерывного пространства действий. И в данной статье предлагаю познакомиться с алгоритмом Soft Аctor-Critic (SAC). Основное преимущество SAC заключается в способности находить оптимальные политики, которые не только максимизируют ожидаемую награду, но и имеют максимальную энтропию (разнообразие) действий.