
Разрабатываем мультивалютный советник (Часть 23): Приводим в порядок конвейер этапов автоматической оптимизации проектов (II)
Введение
Одна из предыдущих частей серии уже была посвящена этому вопросу. Она позволила выбрать более правильный вектор дальнейшего развития проекта. Вместо ручного формирования в базе данных оптимизации всех задач, выполняемых в рамках одного проекта, у нас теперь есть более удобный инструмент — скрипт создания проекта оптимизации. Точнее, это скорее шаблон, который может быть легко адаптирован под создание проектов оптимизации разных торговых стратегий.
В этой части мы получили вполне работоспособное решение, позволяющее запускать созданные проекты оптимизации с экспортом подобранных новых групп торговых стратегий напрямую в новую базу данных. Эта база данных была названа базой данных эксперта, чтобы её можно было отличать от базы данных оптимизации, которая использовалась ранее (в полном и урезанном вариантах). Базу данных эксперта может использовать какой-то работающий на торговом счёте итоговый советник, обновляя настройки используемых торговых систем без перекомпиляции. Корректность этого механизма нам предстоит проверить. Однако, уже сейчас можно сказать, что такой подход упрощает работу конвейера в целом. Ведь до этого мы планировали к трём имеющимся этапам конвейера добавлять ещё несколько:
- Экспорт библиотеки для получения ExportedGroupsLibrary.mqh в папке данных (Stage4).
- Копирование файла в рабочую папку (Stage5, Python или DLL) или доработка предыдущего этапа для экспорта сразу в рабочую папку.
- Компилирование итогового советника (Stage6, Python).
- Запуск терминала с новой версией итогового советника.
Теперь эти этапы оказались не нужны. Заодно, мы избавились ещё и от большого недостатка: проверять в тестере стратегий корректность работы такого механизма автоматического обновления настроек было бы нельзя. Наличие этапа перекомпиляции несовместимо с тем, что в процессе одного прохода тестера скомпилированный код советника меняться не может.
Но самое главное — мы попробуем сделать важный шаг в сторону упрощения использования всего написанного кода для оптимизации произвольных стратегий и попробуем описать пошаговый алгоритм действий для этого.
Намечаем путь
Начнём с реализации давно назревших изменений в структуре файлов проекта. Сейчас они находятся в одной папке, что, с одной стороны, упрощает перенос и использование всего кода в новом проекте, но с другой стороны, в процессе непрерывной разработки у нас образуется несколько почти идентичных рабочих папок проектов для разных торговых стратегий, каждую из которых надо обновлять отдельно. Поэтому проведём разделение всего кода на библиотечную часть, которая будет одинаковой для всех проектов, и проектную часть, которая будет содержать код, специфичный для разных проектов.
Далее мы реализуем проверку, что если в процессе работы итогового советника появляются новые группы стратегий, то он сможет корректно загрузить обновлённые параметры и продолжить работу. Начнём, как обычно, с моделирования желаемого поведения в советнике, запущенном в тестере стратегий. Если там результаты окажутся удовлетворительными, то тогда можно будет переходить к его использованию в итоговых советниках, работающих уже не в тестере.
Что нам для этого понадобится? В прошлой части мы не реализовали сохранение в базу данных эксперта информацию о датах окончания интервала оптимизации и завершения выполнения конвейера оптимизации. Теперь нам эта информация понадобится, иначе при проходе в тестере, итоговому советнику нельзя будет понять, эта группа стратегий уже сформирована в определённую моделируемую дату, или ещё нет.
Также понадобится доработать итоговый советник, чтобы он мог проводить собственную повторную инициализацию при появлении новых групп стратегий в своей базе данных эксперта. Сейчас такой функциональности в нём просто нет. Здесь уже оказался бы нелишним хоть какой-то вывод информации о текущей группе торговых стратегий, чтобы можно было наглядно убедиться в успешной смене одной группы на другую. Удобнее было бы видеть эту информацию непосредственно на графике, на котором запущен советник, но можно, конечно, для этого использовать и обычный вывод в лог терминала.
И напоследок, приведём описание общего алгоритма работы с разработанными на данный момент инструментами.
Приступим!
Переход на другую структуру файлов
Во всех предыдущих частях разработка велась в одной рабочей папке проекта. Существующие файлы в ней модифицировались, время от времени добавлялись новые. Иногда удалялись или "забывались" файлы, потерявшие свою актуальность для проекта. Для работы с одной возможной торговой стратегией (например, используемой в качестве примера в статьях стратегии с названием SimpleVolumes) такой подход вполне себя оправдывал. Но при распространении механизма автоматической оптимизации на другие торговые стратегии, приходилось создавать полную копию рабочей папки проекта и, далее, менять в ней только небольшую часть файлов.
С ростом количества подключаемых таким образом торговых стратегий (и с ростом количества разных рабочих папок), поддержание кода в них во всех в актуальном состоянии становилось всё более и более трудоёмким. Поэтому вынесем ту часть кода, которая должна быть одинаковой для всех рабочих папок, в отдельную библиотечную папку, расположенную в папке MQL5/Include. Название общей папки для библиотечных файлов было выбрано Advisor, а чтобы оно не конфликтовало с возможно существующей папкой с таким именем, к нему была добавлена уникальная составляющая. Теперь библиотечные файлы будут находиться в папке MQL5/Include/antekov/Advisor/.
Перенеся в неё все файлы, мы приступили к дальнейшей систематизации. Было решено все файлы разнести по подпапкам, отражающим какое-то общее предназначение для расположенных внутри них файлов. Это потребовало поработать с директивами подключения одних файлов в состав других, так как их относительное местоположение поменялось. Но в итоге, удалось добиться успешной компиляции как советников, так и всех библиотечных файлов по отдельности.
Вот как стала выглядеть структура файлов после модификации:
Рис. 1. Структура файлов библиотеки Advisor
Как видно, мы выделили несколько групп файлов, разнеся их в следующие подпапки:
- Base. Базовые классы, от которых наследуются другие классы проекта.
- Database. Файлы для работы со всеми типами баз данных, используемых советниками проекта.
- Experts. Файлы с общими частями используемых советников разного типа.
- Optimization. Классы, отвечающие за работу автоматической оптимизации.
- Strategies. Примеры торговых стратегий, используемые для демонстрации работы проекта.
- Utils. Вспомогательные утилиты, макросы для сокращения кода.
- Virtual. Классы для создания различных объектов, объединённых использованием системы виртуальных торговых ордеров и позиций.
Из всех вышеперечисленных подпапок выделяется только одна, про которую стоит сказать особо. Это папка Experts. Если сравнить состав файлов на рис. 1 c составом файлов из предыдущей части, то можно заметить, что только в этой папке появились файлы, которых ранее не было. Сначала можно подумать, что мы просто частично переименовали файлы используемых советников и перенесли их сюда, но обратите внимание, что расширение у них не *.mq5, а *.mqh. Но прежде, чем посмотреть на них детальнее, посмотрим на то, что останется в папке отдельного проекта для некоторой торговой стратегии.
Мы сделаем отдельную папку внутри MQL5/Experts/, которую можно будет назвать как угодно. В ней будут находиться файлы всех используемых советников:
Рис. 2. Структура файлов проекта, использующего библиотеку Advisor
Назначение этих файлов следующее:
- CreateProject.mq5— советник создания проекта автоматической оптимизации в базе данных оптимизации. Каждый проект в базе данных представлен в виде трёх этапов, состоящих из одной или нескольких работ. Каждая работа состоит из одной или нескольких задач оптимизации, выполняемых советниками этапов.
- HistoryReceiverExpert.mq5— советник воспроизведения сохранённой ранее истории сделок. Мы его уже давно не использовали, поскольку он был сделан только с целью проверки повторяемости результатов при смене брокера. Для работы автоматической оптимизации он не нужен, так что его можно спокойно удалить при желании.
- Optimization.mq5— советник, запускающий задачи из проекта автоматической оптимизации. Сам процесс последовательного выполнения таких задач мы называли конвейером автоматической оптимизации.
- SimpleVolumes.mq5— итоговый советник, объединяющий много одиночных экземпляров торговых стратегий типа SimpleVolumes. Информацию о составе этих экземпляров он будет брать из базы данных эксперта. А в базу данных эксперта эту информацию будет помещать советник третьего этапа конвейера автоматической оптимизации.
- Stage1.mq5— советник первого этапа конвейера автоматической оптимизации. Проводит оптимизацию одиночного экземпляра торговой стратегии.
- Stage2.mq5— советник второго этапа конвейера автоматической оптимизации. В процессе оптимизации подбирает из многих хороших одиночных экземпляров, полученных на первом этапе, группу из небольшого количества экземпляров (как правило, 8 или 16), которая при совместной работе показывает наилучшие результаты по нормированной прибыли.
- Stage3.mq5— советник третьего этапа конвейера автоматической оптимизации. Объединяет все группы, полученные на втором этапе, нормирует размеры позиций и сохраняет итоговую группы с масштабирующим множителем в заданную в настройках базу данных эксперта.
Среди перечисленных файлов советников только CreateProject.mq5 сохранил своё содержимое. Все остальные советники содержат в основном только команду включения соответствующего mqh-файла из библиотеки Advisor, которые расположены в папке MQL5/Include/antekov/Advisor/Experts. Причём SimpleVolumes.mq5 и HistoryReceiverExpert.mq5 используют один и тот же включаемый файл Expert.mqh. Как показала практика, нам нет необходимости писать для разных торговых стратегий разный код для советников второго и третьего этапов. А для советника первого этапа всё различие сводится только к разным входным параметрам и составлению нужной строки инициализации из их значений. Всё остальное тоже будет одинаковым.
Так что, только CreateProject.mq5 потребует пока более существенной модификации при переходе на проект с другой торговой стратегией. В будущем, мы попробуем и из него выделить общую часть.
Посмотрим теперь, какие изменения надо внести в файлы библиотеки для реализации автоматического обновления итогового советника.
Даты окончания
Напомним, что в прошлой части мы провели запуск четырёх почти одинаковых проектов оптимизации. Различие между ними было в дате окончания интервала оптимизации одиночных экземпляров торговых стратегий. Состав торговых инструментов, таймфреймов и прочие параметры не отличались. В результате, в базе данных эксперта в таблице strategy_groups появились следующие записи:
Так как мы добавили в название группы дату окончания интервала оптимизации, то по этой информации нам можно понять, какая группа какой дате окончания соответствует. Но надо, чтобы это мог понять советник. Мы специально сделали в этой таблице два поля для сохранения этих дат, которые нужно заполнять при создании записей, и даже подготовили место в коде, где это нужно сделать:
//+------------------------------------------------------------------+ //| Экспорт массива стратегий в заданную базу данных эксперта | //| как новой группы стратегий | //+------------------------------------------------------------------+ void CTesterHandler::Export(CStrategy* &p_strategies[], string p_groupName, string p_advFileName) { // Подключаемся к нужной базе данных эксперта if(DB::Connect(p_advFileName, DB_TYPE_ADV)) { string fromDate = ""; // Дата начала интервала оптимизации string toDate = ""; // Дата конца интервала оптимизации // Создаём запись для новой группы стратегий string query = StringFormat("INSERT INTO strategy_groups VALUES(NULL, '%s', '%s', '%s', NULL)" " RETURNING rowid;", p_groupName, fromDate, toDate); ulong groupId = DB::Insert(query); // ... } }
Даты начала и окончания интервала оптимизации хранятся в базе данных в таблице этапов stages. Поэтому можно получить их оттуда, выполнив в данном месте кода соответствующий SQL-запрос. Но такой подход оказался не самым оптимальным, потому что мы уже писали код, выполняющий SQL-запрос для получения, в том числе, и информации об этих датах. Это происходило в советнике автоматической оптимизации. Он должен был получать информацию из базы данных об очередной задаче оптимизации. В состав этой информации обязательно должны включаться нужные нам даты. Давайте этим воспользуемся.
Нам понадобится создать объект класса COptimizerTask, передав его конструктору имя базы данных оптимизации. Оно у нас есть в статическом поле класса CTesterHandler::s_fileName. А в другом статическом поле CTesterHandler::s_idTask есть идентификатор текущей задачи оптимизации. Его мы передадим методу загрузки данных задачи оптимизации. После этого, нужные даты можно получить из соответствующих полей структуры m_params объекта задачи.
//+------------------------------------------------------------------+ //| Экспорт массива стратегий в заданную базу данных эксперта | //| как новой группы стратегий | //+------------------------------------------------------------------+ void CTesterHandler::Export(CStrategy* &p_strategies[], string p_groupName, string p_advFileName) { // Создаём объект задачи оптимизации COptimizerTask task(s_fileName); // Загружаем в него данные текущей задачи оптимизации task.Load(CTesterHandler::s_idTask); // Подключаемся к нужной базе данных эксперта if(DB::Connect(p_advFileName, DB_TYPE_ADV)) { string fromDate = task.m_params.from_date; // Дата начала интервала оптимизации string toDate = task.m_params.to_date; // Дата конца интервала оптимизации // Создаём запись для новой группы стратегий string query = StringFormat("INSERT INTO strategy_groups VALUES(NULL, '%s', '%s', '%s', NULL)" " RETURNING rowid;", p_groupName, fromDate, toDate); ulong groupId = DB::Insert(query); // ... } }
Сохраним сделанные изменения в файле TesterHandler.mqh в подпапке Virtual библиотеки.
Создадим заново несколько проектов с помощью советника CreateProject.ex5. Для ускорения процесса, сделаем интервал оптимизации небольшим (4 месяца). Дату начала и окончания интервала оптимизации для каждого следующего проекта будем сдвигать на один месяц вперёд. В результате, получим следующее:
Как видно, теперь каждая группа в базе данных эксперта содержит дату окончания интервала оптимизации. Отметим, что эта дата берётся из интервала для задачи третьего этапа. Чтобы всё было корректно, даты интервалов всех трёх этапов должны быть одинаковыми. Это обеспечивается в советнике создания проектов.
Модификация итогового советника
Прежде чем приступить к реализации автоматического обновления используемой группы стратегий в итоговом советнике, посмотрим на изменения, обусловленные переходом к новой структуре файлов проекта. Как уже было отмечено, итоговый советник теперь представлен в виде двух файлов. Основной файл находится в папке проекта и называется SimpleVolumes.mq5. Приведём его полный код:
//+------------------------------------------------------------------+ //| SimpleVolumes.mq5 | //| Copyright 2024-2025, Yuriy Bykov | //| https://www.mql5.com/ru/users/antekov | //+------------------------------------------------------------------+ #property copyright "Copyright 2024-2025, Yuriy Bykov" #property link "https://www.mql5.com/ru/articles/16913" #property description "Итоговый советник, объединяющий много экземпляров торговых стратегий:" #property description " " #property description "Стратегии открывают рыночный или отложенный ордер в тот момент," #property description "когда тиковый объем свечи превышает средний объем в направлении текущей свечи." #property description "Если ордера еще не превратились в позиции, то они удаляются по времени истечения." #property description "Открытые позиции закрываются только по SL или TP." #property version "1.22" #include <antekov/Advisor/Experts/Expert.mqh> //+------------------------------------------------------------------+
В этом коде, по сути, есть только одна команда импорта библиотечного файла итогового советника. Это как раз тот случай, когда более важно не то, что есть, а то, чего нет. Сравним его с кодом второго советника HistoryReceiverExpert.mq5:
//+------------------------------------------------------------------+ //| HistoryReceiverExpert.mq5 | //| Copyright 2024-2025, Yuriy Bykov | //| https://www.mql5.com/ru/users/antekov | //+------------------------------------------------------------------+ #property copyright "Copyright 2024-2025, Yuriy Bykov" #property link "https://www.mql5.com/ru/articles/16913" #property description "Советник открывает рыночный или отложенный ордер в тот момент," #property description "когда тиковый объем свечи превышает средний объем в направлении текущей свечи." #property description "Если ордера еще не превратились в позиции, то они удаляются по времени истечения." #property description "Открытые позиции закрываются только по SL или TP." #property version "1.01" //+------------------------------------------------------------------+ //| Объявление имени итогового советника. | //| При компиляции будет использоваться функция формирования | //| строки инициализации из текущего файла | //+------------------------------------------------------------------+ #define __NAME__ MQLInfoString(MQL_PROGRAM_NAME) //+------------------------------------------------------------------+ //| Входные параметры | //+------------------------------------------------------------------+ input group "::: Тестирование истории сделок" input string historyFileName_ = "SimpleVolumesExpert.1.19 [2021.01.01 - 2022.12.30]" " [10000, 34518, 1294, 3.75].history.csv"; // Файл с историей //+------------------------------------------------------------------+ //| Функция формирования строки инициализации стратегии | //| из входных параметров | //+------------------------------------------------------------------+ string GetStrategyParams() { return StringFormat("class CHistoryStrategy(\"%s\")\n", historyFileName_); } #include <antekov/Advisor/Experts/Expert.mqh> //+------------------------------------------------------------------+
Цветом выделены три блока, которых нет в файле SimpleVolumes.mq5. Их наличие учитывается в библиотечном файле итогового советника Experts/Expert.mqh следующим образом: если не задана константа с именем итогового советника, то объявляется функция формирования строки инициализации, которая будет получать её из базы данных эксперта. Если же имя задано, то такая функция должна быть объявлена в вышестоящем файле, в который подключается данный библиотечный файл.
// Если не задана константа с именем итогового советника, то #ifndef __NAME__ // Задаём её равной названию файла советника #define __NAME__ MQLInfoString(MQL_PROGRAM_NAME) //+------------------------------------------------------------------+ //| Функция формирования строки инициализации стратегии | //| из входных параметров по умолчанию (если не было задано имя). | //| Импортирует строку инициализации из базы данных советника | //| по идентификатору группы стратегий | //+------------------------------------------------------------------+ string GetStrategyParams() { // Берём строку инициализации из новой библиотеки для выбранной группы // (из базы данных эксперта) string strategiesParams = CVirtualAdvisor::Import( CVirtualAdvisor::FileName(__NAME__, magic_), groupId_ ); // Если группа стратегий из библиотеки не задана, то прерываем работу if(strategiesParams == NULL && useAutoUpdate_) { strategiesParams = ""; } return strategiesParams; } #endif
Далее, в библиотечном файле Experts/Expert.mqh, в функции инициализации советника, используется один из возможных вариантов функции формирования строки инициализации:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // ... // Строка инициализации с наборами параметров стратегий string strategiesParams = NULL; // Берём строку инициализации из новой библиотеки для выбранной группы // (из базы данных эксперта) strategiesParams = GetStrategyParams(); // Если группа стратегий из библиотеки не задана, то прерываем работу if(strategiesParams == NULL) { return INIT_FAILED; } // ... // Успешная инициализация return(INIT_SUCCEEDED); }
Таким образом, мы можем при желании создать итоговый советник, который не будет использовать загрузку строки инициализации из базы данных эксперта. Для этого достаточно в файле *.mq5 объявить константу __NAME__ и функцию с сигнатурой.
string GetStrategyParams()
Ну а теперь, можно приступить и к автоматическому обновлению.
Автоматическое обновление
Первый вариант реализации автоматического обновления, возможно, не является самым красивым, но для начала вполне подойдёт. Главное, что он работает. Необходимые изменения в библиотечном файле итогового советника состояли из двух частей.
Во-первых, мы немного изменили состав входных параметров, убрав перечисление с номером группы из старой библиотеки, заменив его на идентификатор группы из базы эксперта и добавили логический параметр, включающий автообновление:
//+------------------------------------------------------------------+ //| Входные параметры | //+------------------------------------------------------------------+ input group "::: Использовать группу стратегий" sinput int groupId_ = 0; // - ID группы из новой библиотеки (0 - последняя) sinput bool useAutoUpdate_ = true; // - Использовать автообновление? input group "::: Управление капиталом" sinput double expectedDrawdown_ = 10; // - Максимальный риск (%) sinput double fixedBalance_ = 10000; // - Используемый депозит (0 - использовать весь) в валюте счета input double scale_ = 1.00; // - Масштабирующий множитель для группы // ...
Во-вторых, добавили следующий код к функции обработки нового тика, после выделенной цветом строки:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { expert.Tick(); // Если одновременно выполнено: if(groupId_ == 0 // - не задан конкретный идентификатор группы && useAutoUpdate_ // - разрешено автообновление && IsNewBar(Symbol(), PERIOD_D1) // - наступил новый день && expert.CheckUpdate() // - обнаружена новая группа стратегий ) { // Сохраняем текущее состояние эксперта expert.Save(); // Удаляем объект эксперта delete expert; // Вызываем функцию инициализации советника для загрузки новой группы стратегий OnInit(); } }
Таким образом, автоматическое обновление будет работать только если входные параметры groupId_=0 и useAutoUpdate_=true. Если мы укажем какой-то ненулевой идентификатор группы, то будет использована именно она на всём протяжении интервала тестирования. В этом случае нет ограничения на то, когда итоговый советник может совершать сделки.
При включенном автоматическом обновлении, итоговый советник будет совершать сделки только после даты окончания интервала оптимизации самой ранней из существующих в базе данных эксперта групп. Такой механизм будет реализован в новом методе класса CVirtualAdvisor::CheckUpdate():
//+------------------------------------------------------------------+ //| Проверка наличия новой группы стратегий в базе данных эксперта | //+------------------------------------------------------------------+ bool CVirtualAdvisor::CheckUpdate() { // Запрос на получение стратегий заданной группы либо последней группы string query = StringFormat("SELECT MAX(id_group) FROM strategy_groups" " WHERE to_date <= '%s'", TimeToString(TimeCurrent(), TIME_DATE)); // Открываем базу данных эксперта if(DB::Connect(m_fileName, DB_TYPE_ADV)) { // Выполняем запрос int request = DatabasePrepare(DB::Id(), query); // Если нет ошибки if(request != INVALID_HANDLE) { // Структура данных для чтения одной строки результата запроса struct Row { int groupId; } row; // Читаем данные из первой строки результата while(DatabaseReadBind(request, row)) { // Запоминаем идентификатор группы стратегий // в статическом свойстве класса эксперта return s_groupId < row.groupId; } } else { // Сообщаем об ошибке при необходимости PrintFormat(__FUNCTION__" | ERROR: request \n%s\nfailed with code %d", query, GetLastError()); } // Закрываем базу данных эксперта DB::Close(); } return false; }
В этом методе мы получаем из базы данных эксперта самый большой идентификатор группы, для которого дата окончания интервала оптимизации не более текущей даты. Таким образом, даже если в базе данных уже физически присутствует запись новой группы, но время её появления (>= время окончания интервала оптимизации) расположено в будущем относительно текущего моделируемого времени тестера стратегий, то она не будет получена в результате используемого SQL-запроса.
При инициализации, эксперт запоминает идентификатор загруженной группы стратегий в статическом поле класса CVirtualAdvisor::s_groupId. Поэтому мы можем определить появление новой группы, сравнив идентификатор, только что полученный из базы данных эксперта, с идентификатором загруженной ранее группы. Если первый больше, то появилась новая группа.
В методе получения строки инициализации из базы данных эксперта, который уже непосредственно взаимодействует с базой данных, мы используем такое же условие на дату окончания интервала тестирования группы при включенном автоматическом обновлении:
//+------------------------------------------------------------------+ //| Получение строки инициализации группы стратегий | //| из базы данных эксперта с заданным идентификатором | //+------------------------------------------------------------------+ string CVirtualAdvisor::Import(string p_fileName, int p_groupId = 0) { string params[]; // Массив для строк инициализации стратегий // Запрос на получение стратегий заданной группы либо последней группы string query = StringFormat("SELECT id_group, params " " FROM strategies" " WHERE id_group = %s;", (p_groupId > 0 ? (string) p_groupId : "(SELECT MAX(id_group) FROM strategy_groups WHERE to_date <= '" + TimeToString(TimeCurrent(), TIME_DATE) + "')")); // Открываем базу данных эксперта if(DB::Connect(p_fileName, DB_TYPE_ADV)) { // ... } // Строка инициализации группы стратегий string groupParams = NULL; // Общее количество стратегий в группе int totalStrategies = ArraySize(params); // Если стратегии есть, то if(totalStrategies > 0) { // Соединяем их строки инициализации через запятую JOIN(params, groupParams, ","); // Создаём строку инициализации группы стратегий groupParams = StringFormat("class CVirtualStrategyGroup([%s], %.5f)", groupParams, totalStrategies); } // Возвращаем строку инициализации группы стратегий return groupParams; }
Последнее, про что стоит тут упомянуть — это дополнение метода загрузки состояния эксперта после перехода на новую группу стратегий. Дело в том, что новые стратегии из новой группы не найдут в базе данных эксперта своих настроек, так как для них ещё не был вызван метод Save() и сообщат об ошибке загрузки. Но эту ошибку надо проигнорировать.
И ещё одно дополнение связано с необходимостью закрытия виртуальных позиций старых стратегий сразу после загрузки новых. Для этого необходимо и достаточно для всех символов, используемых старыми стратегиями, создать объекты символьных получателей. Эти объекты на следующем тике проведут коррекцию объёмов открытых позиций. Если этого не сделать, то коррекция объёмов будет происходить только по мере открытия виртуальных позиций новыми стратегиями. А если в новых стратегиях перестал использоваться какой-то из ранее используемых символов, то позиции по нему так и останутся висеть открытыми.
//+------------------------------------------------------------------+ //| Загрузка состояния | //+------------------------------------------------------------------+ bool CVirtualAdvisor::Load() { bool res = true; ulong groupId = 0; // Загружаем состояние, если: if(true // файл существует && FileIsExist(m_fileName, FILE_COMMON) // и сейчас не оптимизация && !MQLInfoInteger(MQL_OPTIMIZATION) // и сейчас не тестирование либо сейчас визуальное тестирование && (!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE)) ) { // Если подключение к базе данных эксперта установлено if(CStorage::Connect(m_fileName)) { // Если время последних изменений загружено и меньше текущего времени if(CStorage::Get("CVirtualReceiver::s_lastChangeTime", m_lastSaveTime) && m_lastSaveTime <= TimeCurrent()) { PrintFormat(__FUNCTION__" | LAST SAVE at %s", TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS)); // Если идентификатор сохранённой группы стратегий загружен if(CStorage::Get("CVirtualAdvisor::s_groupId", groupId)) { // Загружаем все стратегии, игнорируя возможные ошибки FOREACH(m_strategies, { res &= ((CVirtualStrategy*) m_strategies[i]).Load(); }); if(groupId != s_groupId) { // Действия при запуске эксперта с новой группой стратегий. PrintFormat(__FUNCTION__" | UPDATE Group ID: %I64u -> %I64u", s_groupId, groupId); // Сбрасываем возможный признак ошибки при загрузке стратегий res = true; string symbols[]; // Массив для названий символоа // Получаем список всех используемых предыдущей группой символов CStorage::GetSymbols(symbols); // Для всех символов создаём символьный получатель. // Это нужно для корректного закрытия виртуальных позиций // старой группы стратегий сразу после загрузки новой FOREACH(symbols, m_receiver[symbols[i]]); } // ... } } else { // Если время последних изменений не найдено или находится в будущем, // то начинаем работу с чистого листа PrintFormat(__FUNCTION__" | NO LAST SAVE [%s] - Clear Storage", TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS)); CStorage::Clear(); m_lastSaveTime = 0; } // Закрываем соединение CStorage::Close(); } } return res; }
После этого, можно уже приступить к проверке работоспособности автоматической загрузки новых групп. К сожалению, добиться успеха удалось не сразу, так как понадобилось исправить появившиеся ошибки. Например, выяснилось, что советник впадает в бесконечный цикл, если база данных эксперта вдруг оказалась пустой. Или тестирование не запускается, если дату начала тестирования поставить хоть на один день раньше даты появления первой группы стратегий. Но в итоге, все обнаруженные ошибки были устранены.
Давайте теперь посмотрим на алгоритм использования создаваемой библиотеки в целом и на результаты проверки автоматического обновления.
Алгоритм использования библиотеки Advisor
В этот раз мы опишем алгоритм использования библиотеки Advisor для автоматической оптимизации модельной стратегии SimpleVolumes, которая входит в состав библиотеки и запуска в тестере итогового советника.
- Устанавливаем библиотеку в Include (рис. 1).
- Создаём папку проекта и переносим в неё файлы советников (рис. 2).
- Вносим изменения в файлы советника первого этапа и файл советника создания проекта. При использовании модельной стратегии внесение изменений не требуется, так как они находятся в актуальном состоянии. Компилируем все советники в папке проекта.
- Запускаем советник создания проекта, устанавливая желаемые значения параметров (можно оставить по умолчанию).
На выходе, в общей папке данных терминала, должна появиться база данных оптимизации, наполненная задачами для проекта. В описании проекта можно указать что угодно, например даты начала и окончания интервала оптимизации. Это пока только создание задания на запуск конвейера. Запуск будет выполнять другой советник. - При желании, можно повторить предыдущий пункт произвольное количество раз, меняя параметры. Так можно, например, создать сразу несколько проектов для автоматической оптимизации на разных временных интервалах.
- Запускаем советник оптимизации и ждём. Время, требуемое для завершения всех добавленных в базу данных оптимизации проектов, зависит от их количества, а также от количества символов и таймфреймов в проектах, от продолжительности временного интервала тестирования/оптимизации, от сложности реализованной торговой стратегии. Также это время зависит от количества задействованных в оптимизации агентов тестирования.
На выходе получается файл с базой данных эксперта в общей папке. Его имя берётся из настроек.
В базе данных эксперта будут находиться сохранённые группы стратегий. - Запускаем итоговый советник. Важно, чтобы его имя и магический номер совпадало с указанными при оптимизации. Иначе он создаст пустую базу данных эксперта и будет ждать, пока в ней что-то появится. Если же итоговый советник находит свою базу, то он пытается загрузить группу стратегий с указанным идентификатором или последнюю добавленную группу, если идентификатор равен 0. Если установлен параметр автоматического обновления, то раз в сутки советник будет проверять, не появилась ли в базе данных эксперта новая доступная по дате группа стратегий. Если появилась, то она заменяет собой используемую ранее группу.
Тестирование автоматического обновления
Итак, после того как завершится выполнение оптимизации всех добавленных в базу данных проектов с разными датами окончания, у нас образуется база данных эксперта с несколькими группами стратегий разного состава. Также они отличаются датой окончания интервала оптимизации. И у нас есть итоговый советник, который может по мере тестирования брать из базы новую группу стратегий, когда моделируемое текущее время превысит время окончания интервала оптимизации для этой новой группы.
Следует учесть, что сохранение и загрузка параметров эксперта работает только при запуске советника на графике или в режиме визуального тестирования, поэтому, для проверки автоматического обновления в тестере, надо обязательно использовать визуальный режим.
Запустим итоговый советник с указанием конкретного идентификатора группы groupId_=1. В этом случае, независимо от значения параметра useAutoUpdate_, будет использоваться только эта группа. Она была оптимизирована на интервале 2022.09.01 — 2023.01.01, поэтому запустим тестер начиная с даты 2022.09.01 (основной период), а с даты 2023.01.01 начнём форвард-период до 2024.01.01.
Основной период 2022.09.01 — 2023.01.01
Форвард-период 2023.01.01 — 2024.01.01
Рис. 3. Результаты итогового советника с параметрами groupId_=1 на интервале 2022.09.01 — 2024.01.01
Как видно, на основном периоде, совпадающем с интервалом оптимизации, советник показывает неплохие результаты, но на форвард-периоде картина совершенно другая. Наблюдается гораздо большая просадка, и отсутствует выраженный рост кривой средств. Что ж, хотелось бы, конечно видеть что-то более красивое, но такой результат не является чем-то неожиданным. Ведь мы использовали очень маленький интервал при оптимизации, мало символов и мало таймфреймов. Поэтому получили, что на известном участке параметры подобрались слишком хорошо именно для этого короткого участка. На неизвестном участке советник так показать себя не смог.
Ради интереса, посмотрим, будет ли наблюдаться похожая картина для другой группы торговых стратегий. Запустим итоговый советник с указанием идентификатора группы groupId_=3. Эта группа была оптимизирована на интервале 2022.11.01 — 2023.03.01, поэтому запустим тестер начиная с даты 2022.11.01 (основной период), а с даты 2023.03.01 так же начнём форвард-период до 2024.01.01.
Основной период 2022.11.01 — 2023.03.01
Форвард-период 2023.03.01 — 2024.01.01
Рис. 4. Результаты итогового советника с параметрами groupId_=3 на интервале 2022.11.01 — 2024.01.01
Да, результаты получились такие же, как и для первой группы. Для обеих групп в мае-июне наблюдается большая просадка. Можно подумать, что это неудачный для стратегии период. Но если взять группу, которая оптимизировалась на этом диапазоне, то мы увидим, что и тут были успешно подобраны такие параметры стратегий из группы. На нём виден такой же плавный и красивый рост графика.
Если запустим итоговый советник, начиная с даты 2023.01.01, с параметрами groupId_=0, useAutoUpdate=false, то получим тот же результат, что на форвард-периоде для первой группы, так как в этом случае будет загружена первая группа (она уже "существует" на дату начала прохода). Однако, из-за выключенного автоматического обновления она не будет заменяться на группы с более поздним временем появления.
И наконец, запустим итоговый советник на интервале 2023.01.01 — 2024.01.01 с автоматическим обновлением, указав параметры groupId_=0, useAutoUpdate=true.
Рис. 5. Результаты итогового советника с параметрами groupId_=0, useAutoUpdate=true на интервале 2023.01.01 — 2024.01.01
Сами по себе торговые результаты интереса не представляют, так как для сокращения времени проведения автоматической оптимизации, использовался очень короткий период для оптимизации (всего 4 месяца). Сейчас мы хотели лишь продемонстрировать работоспособность механизма автоматического обновления используемых групп стратегий. А это, судя по записям в логе и автоматическому закрытию позиций в начале каждого месяца, работает как и было задумано:
SimpleVolumes (GBPUSD,H1) 2023.02.01 00:00:00 CVirtualReceiver::Get | OK, Strategy orders: 3 from 144 total SimpleVolumes (GBPUSD,H1) 2023.02.01 00:00:00 CVirtualStrategyGroup::CVirtualStrategyGroup | Scale = 2.44, total strategies = 1 SimpleVolumes (GBPUSD,H1) 2023.02.01 00:00:00 CVirtualStrategyGroup::CVirtualStrategyGroup | Scale = 48.00, total groups = 48 SimpleVolumes (GBPUSD,H1) 2023.02.01 00:00:00 CVirtualStrategyGroup::CVirtualStrategyGroup | Scale = 1.00, total groups = 1 SimpleVolumes (GBPUSD,H1) 2023.02.01 00:00:00 CVirtualRiskManager::UpdateBaseLevels | DAILY UPDATE: Balance = 0.00 | Equity = 0.00 | Level = 0.00 | depoPart = 0.10 = 0.10 * 1.00 * 1.00 SimpleVolumes (GBPUSD,H1) 2023.02.01 00:00:00 CVirtualAdvisor::Load | LAST SAVE at 2023.01.31 20:32:00 SimpleVolumes (GBPUSD,H1) 2023.02.01 00:00:00 CVirtualAdvisor::Load | UPDATE Group ID: 1 -> 2 SimpleVolumes (GBPUSD,H1) 2023.02.01 00:00:59 CSymbolNewBarEvent::IsNewBar | Register new event handler for GBPUSD PERIOD_D1 SimpleVolumes (GBPUSD,H1) 2023.02.01 00:01:00 CSymbolNewBarEvent::IsNewBar | Register new event handler for EURUSD PERIOD_D1 SimpleVolumes (GBPUSD,H1) 2023.02.01 00:01:00 CSymbolNewBarEvent::IsNewBar | Register new event handler for EURGBP PERIOD_D1
Заключение
Подведём некоторые итоги. Мы наконец-то расположили весь повторно используемый код в папке Include в виде библиотеки Advisor. Теперь можно будет подключать его к проектам, работающим с разными торговыми стратегиями. А последующие обновления библиотеки будут автоматически распространяться на все проекты, где она используется.
С каждым разом становится всё проще создавать и запускать проект автоматической оптимизации. Теперь мы упростили и механизм внедрения результатов оптимизации в итоговый советник. Достаточно указать нужное имя базы данных эксперта в настройках третьего этапа оптимизации и результаты попадут туда, откуда итоговый советник сможет их забрать.
Однако есть еще довольно много моментов, требующих внимания. Одним из них является отработка алгоритма добавления нового типа торговой стратегии и включения в итоговый советник групп, содержащих разные типы торговых стратегий. Но об этом уже в следующий раз.
Спасибо за внимание, до встречи!
Важное предупреждение
Все результаты, изложенные в этой статье и всех предшествующих статьях цикла, основываются только на данных тестирования на истории и не являются гарантией получения хоть какой-то прибыли в будущем. Работа в рамках данного проекта носит исследовательский характер. Все опубликованные результаты могут быть использованы всеми желающими на свой страх и риск.
Содержание архива
# | Имя | Версия | Описание | Последние изменения |
---|---|---|---|---|
MQL5/Experts/Article.16913 | Рабочая папка проекта | |||
1 | CreateProject.mq5 | 1.01 | Советник-скрипт создания проекта с этапами, работами и задачами оптимизации. | Часть 23 |
2 | HistoryReceiverExpert.mq5 | 1.01 | Советник воспроизведения истории сделок с риск-менеджером | Часть 23 |
3 | Optimization.mq5 | 1.00 | Советник для автоматической оптимизации проектов | Часть 23 |
4 | SimpleVolumesExpert.mq5 | 1.22 | Итоговый советник для параллельной работы нескольких групп модельных стратегий. Параметры будут браться из встроенной библиотеки групп. | Часть 23 |
5 | Stage1.mq5 | 1.22 | Советник оптимизации одиночного экземпляра торговой стратегии (Этап 1) | Часть 23 |
6 | Stage2.mq5 | 1.00 | Советник оптимизации группы экземпляров торговых стратегий (Этап 2) | Часть 23 |
7 | Stage3.mq5 | 1.00 | Советник, сохраняющий сформированную нормированную группу стратегий в базу данных эксперта с заданным именем. | Часть 23 |
MQL5/Include/antekov/Advisor/Base | Базовые классы, от которых наследуются другие классы проекта | |||
8 | Advisor.mqh | 1.04 | Базовый класс эксперта | Часть 10 |
9 | Factorable.mqh | 1.05 | Базовый класс объектов, создаваемых из строки | Часть 22 |
10 | Interface.mqh | 1.01 | Базовый класс визуализации различных объектов | Часть 4 |
11 | Receiver.mqh | 1.04 | Базовый класс перевода открытых объемов в рыночные позиции | Часть 12 |
12 | Strategy.mqh | 1.04 | Базовый класс торговой стратегии | Часть 10 |
MQL5/Include/antekov/Advisor/Database | Файлы для работы со всеми типами баз данных, используемых советниками проекта | |||
13 | Database.mqh | 1.10 | Класс для работы с базой данных | Часть 22 |
14 | db.adv.schema.sql | 1.00 | Схема базы данных итогового советника | Часть 22 |
15 | db.cut.schema.sql | 1.00 | Схема урезанной базы данных оптимизации | Часть 22 |
16 | db.opt.schema.sql | 1.05 | Схема базы данных оптимизации | Часть 22 |
17 | Storage.mqh | 1.01 | Класс работы с хранилищем Key-Value для итогового советника в базе данных эксперта | Часть 23 |
MQL5/Include/antekov/Advisor/Experts | Файлы с общими частями используемых советников разного типа | |||
18 | Expert.mqh | 1.22 | Библиотечный файл для итогового советника. Параметры групп могут браться базы данных эксперта | Часть 23 |
19 | Optimization.mqh | 1.04 | Библиотечный файл для советника, управляющего запуском задач оптимизации | Часть 23 |
20 | Stage1.mqh | 1.19 | Библиотечный файл для советника оптимизации одиночного экземпляра торговой стратегии (Этап 1) | Часть 23 |
21 | Stage2.mqh | 1.04 | Библиотечный файл для советника оптимизации группы экземпляров торговых стратегий (Этап 2) | Часть 23 |
22 | Stage3.mqh | 1.04 | Библиотечный файл для советника, сохраняющего сформированную нормированную группу стратегий в базу данных эксперта с заданным именем. | Часть 23 |
MQL5/Include/antekov/Advisor/Optimization | Классы, отвечающие за работу автоматической оптимизации | |||
23 | Optimizer.mqh | 1.03 | Класс для менеджера автоматической оптимизации проектов | Часть 22 |
24 | OptimizerTask.mqh | 1.03 | Класс для задачи оптимизации | Часть 22 |
MQL5/Include/antekov/Advisor/Strategies | Примеры торговых стратегий, используемые для демонстрации работы проекта | |||
25 | HistoryStrategy.mqh | 1.00 | Класс торговой стратегии воспроизведения истории сделок | Часть 16 |
26 | SimpleVolumesStrategy.mqh | 1.11 | Класс торговой стратегии с использованием тиковых объемов | Часть 22 |
MQL5/Include/antekov/Advisor/Utils | Вспомогательные утилиты, макросы для сокращения кода | |||
27 | ExpertHistory.mqh | 1.00 | Класс для экспорта истории сделок в файл | Часть 16 |
28 | Macros.mqh | 1.05 | Полезные макросы для операций с массивами | Часть 22 |
29 | NewBarEvent.mqh | 1.00 | Класс определения нового бара для конкретного символа | Часть 8 |
30 | SymbolsMonitor.mqh | 1.00 | Класс получения информации о торговых инструментах (символах) | Часть 21 |
MQL5/Include/antekov/Advisor/Virtual | Классы для создания различных объектов, объединённых использованием системы виртуальных торговых ордеров и позиций | |||
31 | Money.mqh | 1.01 | Базовый класс управления капиталом | Часть 12 |
32 | TesterHandler.mqh | 1.07 | Класс для обработки событий оптимизации | Часть 23 |
33 | VirtualAdvisor.mqh | 1.10 | Класс эксперта, работающего с виртуальными позициями (ордерами) | Часть 23 |
34 | VirtualChartOrder.mqh | 1.01 | Класс графической виртуальной позиции | Часть 18 |
35 | VirtualFactory.mqh | 1.04 | Класс фабрики объектов | Часть 16 |
36 | VirtualHistoryAdvisor.mqh | 1.00 | Класс эксперта воспроизведения истории сделок | Часть 16 |
37 | VirtualInterface.mqh | 1.00 | Класс графического интерфейса советника | Часть 4 |
38 | VirtualOrder.mqh | 1.09 | Класс виртуальных ордеров и позиций | Часть 22 |
39 | VirtualReceiver.mqh | 1.04 | Класс перевода открытых объемов в рыночные позиции (получатель) | Часть 23 |
40 | VirtualRiskManager.mqh | 1.04 | Класс управления риском (риск-менеждер) | Часть 23 |
41 | VirtualStrategy.mqh | 1.09 | Класс торговой стратегии с виртуальными позициями | Часть 23 |
42 | VirtualStrategyGroup.mqh | 1.02 | Класс группы торговых стратегий или групп торговых стратегий | Часть 23 |
43 | VirtualSymbolReceiver.mqh | 1.00 | Класс символьного получателя | Часть 3 |
MQL5/Common/Files | Общая папка терминалов | |||
44 | SimpleVolumes-27183.test.db.sqlite | — | База данных эксперта с добавленными группами стратегий |





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования