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

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

MetaTrader 5Тестер | 17 ноября 2023, 16:36
739 0
Daniel Jose
Daniel Jose

Введение

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

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

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

Здесь мы подготовим почву для того, чтобы при необходимости добавления новых функций в код это происходило плавно и легко. Текущий код пока не может охватывать или обрабатывать некоторые моменты, которые будут необходимы для значимого прогресса. Нам нужно, чтобы всё было построено так, чтобы усилия по реализации некоторых вещей были минимальными. Если всё сделать правильно, у нас будет возможность создать по-настоящему универсальную систему, способную очень легко адаптироваться к любой ситуации, которую необходимо решить. Один из этих аспектов станет темой следующей статьи. К счастью, благодаря двум последним статьям, в которых показано, как добавлять тики в окно Обзора рынка, всё в целом идет по плану. Если вы случайно не заметили эти статьи, вы сможете получить к ним доступ по следующим ссылкам:  Разработка системы репликации - Моделирование рынка (Часть 17): Тики и еще больше тиков (I) и Разработка системы репликации - Моделирование рынка (Часть 18): Тики и еще больше тиков (II). Эти две статьи содержат ценную информацию о том, что мы будем делать в следующих статьях.

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


Реализация системы каталогов

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

Рисунок 01

Рисунок 01 – Способ доступа к каталогам в текущей системе.


Рисунок 02

Рисунок 02 – Альтернативный способ доступа к каталогам.


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

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

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

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

private :
    enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
    string m_szPath;

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

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

inline bool Configs(const string szInfo)
    {
        const string szList[] = {
                                "POINTSPERTICK",
                                "PATH"
                                };
        string  szRet[];
        char    cWho;
                
        if (StringSplit(szInfo, '=', szRet) == 2)
        {
            StringTrimRight(szRet[0]);
            StringTrimLeft(szRet[1]);
            for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
            switch (cWho)
            {
                case 0:
                    m_PointsPerTick = StringToDouble(szRet[1]);
                    return true;
                case 1:
                    m_szPath = szRet[1];
                    return true;                                    
            }
            Print("Переменная >>", szRet[0], "<< не определена.");
        }else
            Print("Определение настроек >>", szInfo, "<< недействительно.");
                                
        return false;
    }

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

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

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

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

C_ConfigService()
	:m_szPath(NULL)
	{
	}

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

Давайте посмотрим, как это реализовать:

bool SetSymbolReplay(const string szFileConfig)
    {
        #define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo)
        int        file,
                iLine;
        char    cError,
                cStage;
        string  szInfo;
        bool    bBarsPrev;
        C_FileBars *pFileBars;
        
        if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
            Print("Не удалось открыть файл конфигурации [", szFileConfig, "]. Закрывем сервис...");
            return false;
        }
        Print("Загрузка тиков для репликации. Подождите....");
        ArrayResize(m_Ticks.Rate, def_BarsDiary);
        m_Ticks.nRate = -1;
        m_Ticks.Rate[0].time = 0;
        iLine = 1;
        cError = cStage = 0;
        bBarsPrev = false;
        while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0))
        {
            switch (GetDefinition(FileReadString(file), szInfo))
            {
                case Transcription_DEFINE:
                    cError = (WhatDefine(szInfo, cStage) ? 0 : 1);
                    break;
                case Transcription_INFO:
                    if (szInfo != "") switch (cStage)
                    {
                        case 0:
                            cError = 2;
                            break;
                        case 1:
                            pFileBars = new C_FileBars(macroFileName);
                            if ((m_dtPrevLoading = (*pFileBars).LoadPreView()) == 0) cError = 3; else bBarsPrev = true;
                            delete pFileBars;
                            break;
                        case 2:
                            if (LoadTicks(macroFileName) == 0) cError = 4;
                            break;
                        case 3:
                            if ((m_dtPrevLoading = LoadTicks(macroFileName, false)) == 0) cError = 5; else bBarsPrev = true;
                            break;
                        case 4:
                            if (!BarsToTicks(macroFileName)) cError = 6;
                            break;
                        case 5:
                            if (!Configs(szInfo)) cError = 7;
                            break;
                    }
                break;
            };
            iLine += (cError > 0 ? 0 : 1);
        }
        FileClose(file);
        switch(cError)
        {
            case 0:
                if (m_Ticks.nTicks <= 0)
                {
                    Print("Нет тиков для использования. Закрываем сервис...");
                    cError = -1;
                }else if (!bBarsPrev) FirstBarNULL();
                break;
            case 1  : Print("Команда в строке ", iLine, " не распознается системой...");    break;
            case 2  : Print("Содержимое строки неожиданно для системы", iLine);                  break;
            default : Print("Ошибка на строке ", iLine);
        }
                                                        
        return (cError == 0 ? !_StopFlag : false);
#undef macroFileName
    }

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

#define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo)

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

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

[Config]
Path = < NEW PATH >

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

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


Корректируем данные пользовательских активов

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

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

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

C_Replay(const string szFileConfig)
    {
        m_ReplayCount = 0;
        m_dtPrevLoading = 0;
        m_Ticks.nTicks = 0;
        Print("************** Serviço Market Replay **************");
        srand(GetTickCount());
        GlobalVariableDel(def_GlobalVariableReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

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

class C_FileTicks
{
    protected:
        struct st00
        {
            MqlTick  Info[];
            MqlRates Rate[];
            int      nTicks,
                     nRate;
            bool     bTickReal;
        }m_Ticks;
        double       m_PointsPerTick;
    private :
        int          m_File;

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

inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode)
    {
        double vStep, vNext, price, vHigh, vLow, PpT;
        char i0 = 0;

        PpT = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
        vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / PpT);
        vHigh = rate.high;
        vLow = rate.low;
        for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++)
        {
            price = tick[c0 - 1].last + (PpT * ((rand() & 1) == 1 ? -1 : 1));
            price = tick[c0].last = (price > vHigh ? price - PpT : (price < vLow ? price + PpT : price));
            switch (iMode)
            {
                case 0:
                    if (price == rate.close)
                        return c0;
                        break;
                case 1:
                    i0 |= (price == rate.high ? 0x01 : 0);
                        i0 |= (price == rate.low ? 0x02 : 0);
                        vHigh = (i0 == 3 ? rate.high : vHigh);
                        vLow = (i0 ==3 ? rate.low : vLow);
                        break;
                case 2:
                    break;
            }
            if ((int)floor(vNext) < c1)
            {
                if ((++c2) <= 3) continue;
                vNext += vStep;
                if (iMode == 2)
                {
                    if ((c2 & 1) == 1)
                    {
                        if (rate.close > vLow) vLow += PpT; else vHigh -= PpT;
                    }else
                    {
                        if (rate.close < vHigh) vHigh -= PpT; else vLow += PpT;
                    }
                } else
                {
                    if (rate.close > vLow) vLow = (i0 == 3 ? vLow : vLow + PpT); else vHigh = (i0 == 3 ? vHigh : vHigh - PpT);
                }
            }
        }

        return pOut;
    }

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

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

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

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

inline void CreateBarInReplay(const bool bViewMetrics, const bool bViewTicks)
        {
#define def_Rate m_MountBar.Rate[0]
        
                bool bNew;
        MqlTick tick[1];
        static double PointsPerTick = 0.0;
        
        if (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time))
        {
                        PointsPerTick = (PointsPerTick == 0.0 ? SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) : PointsPerTick);                    
                        if (bViewMetrics) Metrics();
                        m_MountBar.memDT = (datetime) macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                        def_Rate.real_volume = 0;
                        def_Rate.tick_volume = 0;
        }
        bNew = (def_Rate.tick_volume == 0);
        def_Rate.close = (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close);
        def_Rate.open = (bNew ? def_Rate.close : def_Rate.open);
        def_Rate.high = (bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
        def_Rate.low = (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;
        CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate);
        if (bViewTicks)
        {
                        tick = m_Ticks.Info[m_ReplayCount];
                        if (!m_Ticks.bTickReal)
                        {
                                static double BID, ASK;
                                double  dSpread;
                                int     iRand = rand();
                        
                                dSpread = PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? PointsPerTick : 0 ) : 0 );
                                if (tick[0].last > ASK)
                                {
                                        ASK = tick[0].ask = tick[0].last;
                                        BID = tick[0].bid = tick[0].last - dSpread;
                                }
                                if (tick[0].last < BID)
                                {
                                        ASK = tick[0].ask = tick[0].last + dSpread;
                                        BID = tick[0].bid = tick[0].last;
                                }
                        }
                        CustomTicksAdd(def_SymbolReplay, tick); 
        }
                m_ReplayCount++;

#undef def_Rate
        }

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

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


Последние штрихи для создания

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

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

C_Replay(const string szFileConfig)
    {
        m_ReplayCount = 0;
        m_dtPrevLoading = 0;
        m_Ticks.nTicks = 0;
        Print("************** Serviço Market Replay **************");
        srand(GetTickCount());
        GlobalVariableDel(def_GlobalVariableReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

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

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

bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
#define macroError(A) { Print(A); return false; }
   u_Interprocess info;
   
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o tamanho do ticket.");
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o valor do ticket.");
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o volume mínimo.");
   if (m_IdReplay == -1) return false;
   if ((m_IdReplay = ChartFirst()) > 0) do
   {
        if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
        {
            ChartClose(m_IdReplay);
            ChartRedraw();
        }
   }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
   Print("Aguardando permissão do indicador [Market Replay] para iniciar replay ...");
   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) != ""));
#undef macroError
  }

Чтобы избежать лишних повторений, мы будем использовать макрос. Он отобразит сообщение об ошибке и завершится сообщением о сбое системы. Здесь мы проверяем одно за другим значения, которые должны быть объявлены и инициализированы в конфигурационном файле. Если какой-либо из них не был инициализирован, пользователь будет оповещен о необходимости правильно настроить пользовательский актив. Без этого сервис репликации/моделирования не продолжит работать. С этого момента он будет считаться функциональным и способным предоставлять системе ордеров, в данном случае создаваемому советнику, необходимые данные для надлежащего моделирования или репликации рынка. Это позволит имитировать отправку ордеров.

Это хорошо, но чтобы инициализировать эти значения, нам нужно внести в систему некоторые дополнения, которые показаны ниже:

inline bool Configs(const string szInfo)
    {
        const string szList[] = {
                                "PATH",
                                "POINTSPERTICK",
                                "VALUEPERPOINTS",
                                "VOLUMEMINIMAL"
                                };
        string  szRet[];
        char    cWho;
        
        if (StringSplit(szInfo, '=', szRet) == 2)
        {
            StringTrimRight(szRet[0]);
            StringTrimLeft(szRet[1]);
            for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
            switch (cWho)
            {
                case 0:
                    m_szPath = szRet[1];
                    return true;
                case 1:
                    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
                    return true;
                case 2:
                    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
                    return true;
                case 3:
                    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
                    return true;                                    
            }
            Print("Переменная >>", szRet[0], "<< не определена.");
        }else
            Print("Определение настроек >>", szInfo, "<< недействительно.");

        return false;
    }

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


Конечные выводы

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

Так или иначе, проверьте, как обстоят дела в прикрепленных пользовательских активах.

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

В следующей статье мы начнем рассматривать эту тему: ФОРЕКС.


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

Прикрепленные файлы |
Разработка системы репликации - Моделирование рынка (Часть 20): ФОРЕКС (I) Разработка системы репликации - Моделирование рынка (Часть 20): ФОРЕКС (I)
Первоначальная цель данной статьи заключается не в охвате всех возможностей ФОРЕКС, а скорее в адаптации системы таким образом, чтобы вы могли совершить хотя бы одну репликацию рынка. Моделирование оставим для другого момента. Однако, если у нас нет тиков, а есть только бары, приложив немного усилий, мы можем смоделировать возможные сделки, которые могли произойти на рынке ФОРЕКС. Так будет до тех пор, пока мы не рассмотрим, как адаптировать тестер. Попытка работать с данными ФОРЕКС внутри системы без их модификации приводит к ошибкам диапазона.
Нейросети — это просто (Часть 64): Метод Консервативного Весового Поведенческого Клонирования (CWBC) Нейросети — это просто (Часть 64): Метод Консервативного Весового Поведенческого Клонирования (CWBC)
В результате тестов, проведенных в предыдущих статьях, мы пришли к выводу, что оптимальность обученной стратегии во многом зависит от используемой обучаемой выборки. В данной статье я предлагаю вам познакомиться с довольно простым и эффективном методе выбора траекторий для обучения моделей.
Торговая техника RSI Deep Three Move Торговая техника RSI Deep Three Move
В статье представлена техника торговли RSI Deep Three Move в MetaTrader 5. Статья основана на новой серии исследований, демонстрирующих несколько торговых методов, основанных на RSI - техническом индикаторе для измерения силы и импульса ценных бумаг, включая акции, валюты и товары.
Стоп-лосс и тейк-профит, дружелюбные к трейдеру Стоп-лосс и тейк-профит, дружелюбные к трейдеру
Стоп-лосс и тейк-профит могут оказать значительное влияние на результаты трейдинга. В этой статье мы рассмотрим несколько способов поиска оптимальных значений стоп-приказов.