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

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

MetaTrader 5Тестер | 14 июля 2023, 17:23
734 0
Daniel Jose
Daniel Jose

Введение

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

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

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

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

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


Реализуем улучшения

Первое изменение, которое нам нужно реализовать - это добавить две новые строки в сервисный файл. Они показаны ниже:

#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence

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

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

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

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

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

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

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

Идем дальше. У нас теперь две новые строки:

input string            user00 = "Config.txt";  //Конфигурационный файл Репликации.
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Начальный таймфрейм.

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

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

Теперь нам надо понять ещё несколько моментов, которые можно увидеть в приведенном ниже коде:

void OnStart()
{
        ulong t1;
        int delay = 3;
        long id = 0;
        u_Interprocess Info;
        bool bTest = false;
        
        Replay.InitSymbolReplay();
        if (!Replay.SetSymbolReplay(user00))
        {
                Finish();
                return;
        }
        Print("Ожидаем разрешения индикатора [Market Replay] на запуск репликации ...");
        id = Replay.ViewReplay(user01);
        while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(id) != "")) Sleep(750);
        if ((_StopFlag) || (ChartSymbol(id) == ""))
        {
                Finish();
                return;
        }
        Print("Разрешение получено. Теперь можно использовать сервис репликации...");
        t1 = GetTickCount64();
        while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)) && (!_StopFlag))
        {
                if (!Info.s_Infos.isPlay)
                {
                        if (!bTest) bTest = true;
                }else
                {
                        if (bTest)
                        {
                                delay = ((delay = Replay.AdjustPositionReplay()) == 0 ? 3 : delay);
                                bTest = false;
                                t1 = GetTickCount64();
                        }else if ((GetTickCount64() - t1) >= (uint)(delay))
                        {
                                if ((delay = Replay.Event_OnTime()) < 0) break;
                                t1 = GetTickCount64();
                        }
                }
        }
        Finish();
}

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

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

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

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

void Finish(void)
{
        Replay.CloseReplay();
        Print("Сервис репликации завершен...");
}

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

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

Первое, что бросается в глаза - это строки, показанные ниже:

#define def_STR_FilesBar        "[BARS]"
#define def_STR_FilesTicks      "[TICKS]"
#define def_Header_Bar          "<DATE><TIME><OPEN><HIGH><LOW><CLOSE><TICKVOL><VOL><SPREAD>"
#define def_Header_Ticks        "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>"

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

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

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

[Bars]
WIN$N_M1_202108020900_202108021754
WIN$N_M1_202108030900_202108031754
WIN$N_M1_202108040900_202108041754

[Ticks]
WINQ21_202108050900_202108051759
WINQ21_202108060900_202108061759

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

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

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

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


Чтобы понять, о чём говорилось выше, давайте посмотрим на приведенные ниже изображения:

                   

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

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

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

[Ticks]
Mini_Dolar_Futuro\2020\06-Junho\WDO_16062020

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

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

bool SetSymbolReplay(const string szFileConfig)
{
        int     file;
        string  szInfo;
        bool    isBars = true;
                        
        if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
                MessageBox("Не удалось открыть конфигурационный файл.", "Market Replay", MB_OK);
                return false;
        }
        Print("Идет загрузка данных для репликации.\nПодождите....");
        while ((!FileIsEnding(file)) && (!_StopFlag))
        {
                szInfo = FileReadString(file);
                StringToUpper(szInfo);
                if (szInfo == def_STR_FilesBar) isBars = true; else
                if (szInfo == def_STR_FilesTicks) isBars = false; else
                if (szInfo != "") if (!(isBars ? LoadPrevBars(szInfo) : LoadTicksReplay(szInfo)))
                {
			if (!_StopFlag)
	                        MessageBox(StringFormat("Файл %s от %s\nневозможно загрузить.", szInfo, (isBars ? def_STR_FilesBar : def_STR_FilesTicks), "Market Replay", MB_OK));
                        FileClose(file);
                        return false;
                }
        }
        FileClose(file);
        return (!_StopFlag);
}

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

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

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

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

bool LoadPrevBars(const string szFileNameCSV)
{
        int     file,
                iAdjust = 0;
        datetime dt = 0;
        MqlRates Rate[1];
        string  szInfo = "";
                                
        if ((file = FileOpen("Market Replay\\Bars\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                for (int c0 = 0; c0 < 9; c0++) szInfo += FileReadString(file);
                if (szInfo != def_Header_Bar)
                {
                        Print("Файл ", szFileNameCSV, ".csv это не файл предыдущих баров.");
                        return false;
                }
                Print("Идет загрузка предыдущих баров для репликации. Подождите ....");
                while ((!FileIsEnding(file)) && (!_StopFlag))
                {
                        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("Не удалось получить доступ к файлу данных предыдущих баров.");
                m_dtPrevLoading = 0;                                    
                return false;
        }
        return (!_StopFlag);
}

На первый взгляд этот код не сильно отличается от кода, приведенного в предыдущей статье " Разработка системы репликации (Часть 05)". Но да, в нем есть различия, и они весьма значительны в общем и структурном плане.

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

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

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

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

#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)
                        {
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                old = m_Ticks.nTicks;
                                for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(file);
                                if (szInfo != def_Header_Ticks)
                                {
                                        Print("Файл ", szFileNameCSV, ".csv это не файл торгуемых тиков.");
                                        return false;
                                }
                                Print("Идет загрузка тиков репликации. Подождите...");
                                while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
                                {
                                        ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), 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);
                                        }
                                }
                                if ((!FileIsEnding(file))&& (!_StopFlag))
                                {
                                        Print("Слишком много данных в файле тиков.\nНевозможно продолжить....");
                                        return false;
                                }
                        }else
                        {
                                Print("Файл тиков ", szFileNameCSV,".csv не найден...");
                                return false;
                        }
                        return (!_StopFlag);
                };
#undef macroRemoveSec

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

#define def_MaxSizeArray        134217727 // 128 Мб позиций

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

Но подождите секундочку. Я сказал: МЕНЬШЕЕ? ДА. И если вы посмотрите на новое определение, то увидите следующий код:

#define def_MaxSizeArray        16777216 // 16 Мб позиций

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

Чтобы не потерять первые позиции, мы вычитаем 2, чтобы тест не провалился по какой-то причине. Можно добавить дополнительный внешний цикл, чтобы увеличить объем памяти, но лично я не вижу причин для этого. Если 2 гигабайта позиций будет недостаточно, то я не знаю, сколько тогда нужно. Давайте теперь разберемся, как уменьшение значения определения обеспечивает лучшую оптимизацию, используя для этого две строки. Рассмотрим более подробно фрагмент, ответственный за это.

// ... Предыдущий код ....

if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
{
        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
        old = m_Ticks.nTicks;
        for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(file);
        if (szInfo != def_Header_Ticks)
        {
                Print("Файл ", szFileNameCSV, ".csv это не файл торгуемых тиков.");
                return false;
        }
        Print("Идет загрузка тиков репликации. Подождите...");
        while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
        {
                ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
                szInfo = FileReadString(file) + " " + FileReadString(file);

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

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

Теперь обратите внимание: в этом втором выделении мы используем текущее значение уже считанного счетчика тиков плюс 1. Когда я тестировал систему, то заметил, что она выполнила этот вызов со значением, равным 0, что вызвало ошибку времени выполнения. Можете подумать, что это безумие, поскольку ранее память уже была выделена с большим значением. И вот в чем дело, документация самой функции ArrayResize сообщает нам, что мы собираемся ПЕРЕОПРЕДЕЛИТЬ размер массива.

Когда мы используем этот второй вызов, функция СБРОСИТ массив до значения нуля. Именно потому такое текущее значение переменной при первом вызове функции, и причина в том, что мы её не увеличили. Мы здесь не будем объяснять причину этого, но вы должны быть осторожны при работе с динамическим распределением в MQL5. Потому что может случиться так, что ваш код выглядел правильно, но система интерпретировала его не так, как вы себе могли представить.

Вот ещё одна маленькая деталь, на которую следует обратить внимание: почему я использую INT_MAX, а не UINT_MAX в тесте? На самом деле идеальным вариантом было бы использование UINT_MAX, что дало бы нам 4 гигабайта выделенного пространства, но функция ArrayResize работает с системой INT, то есть целым числом со знаком.

И даже если мы хотим выделить 4 гигабайта, что было бы возможно при использовании 32-битного типа long, мы всегда будем терять 1 бит в длине данных из-за знака. Таким образом, по сути, мы будем использовать 31 бит, что гарантирует нам 2 гигабайта возможного пространства для выделения с помощью функции ArrayResize. Каким-то образом мы могли бы обойти это ограничение, используя схему обмена, которая гарантировала бы нам выделение 4 гигабайт или даже больше, но я не вижу причин для использования этот ресурс. Нам достаточно будет двух гигабайт данных.

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

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

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

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

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

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


Заключение

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

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



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


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

Прикрепленные файлы |
Market_Replay.zip (13057.37 KB)
StringFormat(). Обзор, готовые примеры использования StringFormat(). Обзор, готовые примеры использования
Статья является продолжением обзора функции PrintFormat(). Рассмотрим вкратце форматирование строк при помощи StringFormat() и их дальнейшее использование в программе. Напишем шаблоны для вывода информации о символе в журнал терминала. Статья будет полезна как новичкам, так и уже опытным разработчикам.
Разработка системы репликации - Моделирование рынка (Часть 05): Предварительный просмотр Разработка системы репликации - Моделирование рынка (Часть 05): Предварительный просмотр
Нам удалось разработать способ осуществления репликации рынка достаточно реалистичным и доступным образом. Теперь давайте продолжим наш проект и добавим данные для улучшения поведения репликации.
Представления частотной области временных рядов: Спектральная функция Представления частотной области временных рядов: Спектральная функция
В этой статье мы рассмотрим методы, связанные с анализом временных рядов в частотной области. Также будет уделено внимание пользе изучения спектральных функций временных рядов при построении прогностических моделей. Кроме того, мы обсудим некоторые многообещающие перспективы анализа временных рядов в частотной области с использованием дискретного преобразования Фурье (ДПФ).
Разработка системы репликации - Моделирование рынка (Часть 04): Внесение корректировок (II) Разработка системы репликации - Моделирование рынка (Часть 04): Внесение корректировок (II)
Сегодня мы продолжим разработку системы и управления. Без возможности управления сервисом сложно двигаться вперед и совершенствовать систему.