
Разрабатываем мультивалютный советник (Часть 28): Добавляем менеджер закрытия позиций
Введение
В Части 12 мы добавили в мультивалютного советника модуль риск-менеджера для ограничения дневной и общей просадки. Он не увеличивает прибыль, но критически важен для защиты капитала в неблагоприятных условиях. Основан на правилах проп-трейдингов, с возможностью гибкой настройки: просадка в валюте, в процентах от баланса или от начала дня.
Модуль реализован как класс CVirtualRiskManager с методами отслеживания баланса, прибыли и проверки ограничений. Также предусмотрена функция фиксации прибыли — по достижении цели все позиции закрываются и торговля останавливается.
Для обычных счетов хотелось бы, чтобы после достижения прибыли торговля автоматически перезапускалась. Сейчас это требует ручного вмешательства. Пора автоматизировать и этот момент.
Для перезапуска торговых стратегий при достижении заданной прибыли рассматривались два варианта:
- расширить текущий риск-менеджер,
- создать отдельный модуль.
Мы выбрали второй путь, поскольку текущий риск-менеджер работает независимо от стратегий: он закрывает только реальные позиции, не затрагивая виртуальные. Изменение этой логики усложнило бы архитектуру и нарушило модульную независимость.
Также риск-менеджер создаёт дополнительную нагрузку при тестировании, поэтому новый функционал лучше вынести в отдельный модуль — его можно будет использовать даже без запущенного риск-менеджера.
Новая цель — модуль, способный перезапускать все стратегии при выполнении заданных условий (прибыль, убыток, время и т.д.), не полагаясь на историю и без ручного вмешательства. Будем называть новый модуль менеджером закрытия, так как это отдельный модуль, наличие которого не является обязательным, но его добавление может улучшить результаты, и он управляет процессом полного закрытия всех позиций, как реальных, так и виртуальных.
Начальные требования
Сформулируем более чётко, что будет входить в обязанности менеджера закрытия и какие параметры для этого ему понадобятся.
Менеджер закрытия должен:
- Фиксировать прибыль, то есть закрывать все виртуальные позиции при достижении заданной прибыли. Реальные позиции при этом будут тоже автоматически закрыты. Для управления этим процессом введём три параметра:
- Базовый баланс. Размер средств на торговом счёте, от которого будет вестись расчёт прибыли или убытка.
- Способ расчёта прибыли. Может принимать одно из нескольких возможных значений, например, в процентах от базового баланса или фиксированную величину в валюте депозита.
- Значение прибыли. Число, используемое для расчёта прибыли выбранным способом.
- Фиксировать убыток, то есть закрывать все виртуальные позиции при достижении заданного убытка. Для этого процесса тоже пригодятся три параметра, один или два из которых можно сделать общими с параметрами фиксирования прибыли:
- Базовый баланс. Размер средств на торговом счёте, от которого будет вестись расчёт прибыли или убытка.
- Способ расчёта убытка. Тоже может принимать одно из нескольких возможных значений, как и для способа расчёта прибыли.
- Значение убытка. Число, используемое для расчёта убытка выбранным способом.
- Включать трейлинг прибыли — по достижении заданной прибыли виртуальные позиции не закрываются, а запоминается некоторый меньший уровень прибыли, на котором произойдет непосредственное закрытие позиций. Если прибыль растёт, то этот уровень тоже должен увеличиваться. Увеличение может происходить как непрерывно, так и ступенчато с некоторым шагом. Для этого процесса можно добавить такие параметры:
- Включение трейлинга (Да / Нет).
- Способ задания уровня. В этом параметре мы можем выбрать предпочтительный способ задания уровня включения трейлинга. Например, уровень может задаваться как доля от фиксированной прибыли или в виде абсолютного значения в валюте торгового счёта.
- Уровень начала трейлинга. Число, используемое для расчёта уровня начала трейлинга выбранным способом.
- Величина шага. Число, используемое для расчёта величины шага, по достижении которого переставляется уровень трейлинга. Для расчёта можно использовать тот же способ, что и для уровня начала трейлинга.
- Включать уровень безубытка — по достижении прибылью этого значения, запоминается некоторый небольшой положительный уровень прибыли, на котором произойдёт закрытие позиций. При дальнейшем увеличении прибыли этот уровень, в отличие от трейлинга, не будет увеличиваться. Параметры, управляющие этим процессом могут быть такие:
- Включение безубытка (Да / Нет).
- Способ задания уровня. Этот параметр аналогичен одноимённому параметру для трейлинга, то есть также может быть либо относительным, либо абсолютным.
- Уровень включения безубытка. Число, используемое для расчёта уровня безубытка выбранным способом.
Репозиторий проекта
В Части 25 мы добавили новую стратегию и рассмотрели, как можно создать проект по автоматической оптимизации выбранной стратегии и создании итогового советника, включающего много экземпляров торговых стратегий с разными параметрами. Весь код был разделён на две части — библиотечную и проектную. Для библиотечной части мы уже создали в части 26 общедоступный репозиторий кода Adwizard в хранилище MQL5 Algo Forge. Однако, для проектной части этого ещё не было сделано.
Давайте это исправим и создадим новый репозиторий SimpleCandles. В этом репозитории будет располагаться проектная часть для создания итогового советника, использующего стратегии с одноимённым названием. Помимо основной ветки main, тоже сделаем ветку для разработки с названием develop. Если этому проекту будет посвящено несколько статей, то правки, относящиеся к разным статьям, будут разнесены по разным веткам, порождённым от ветки develop. По мере готовности они будут вливаться обратно в ветки develop и main.
Создадим локальную папку для размещения папки проекта, например, MQL5/Experts/Articles/17608. Клонируем этот репозиторий в выбранную папку и создадим в ней папку Include. В этой папке мы разместим репозиторий библиотечной части, от которой зависит данный проект. Клонируем в папку Include репозиторий библиотеки Adwizard.
После данных операций мы получим примерно такую структуру папок в папке терминала:
Рис. 1. Структура папок в репозитории проекта после клонирования проектной и библиотечной частей
В клонированной папке репозитория Adwizard переключимся на ветку develop. Она будет являться общей для всех статей. Однако в процессе работы над этим проектом мы будем вносить изменения в библиотеку Adwizard, поэтому в этом репозитории создадим новую ветку, порождённую от ветки develop.
После этого, в репозитории проекта SimpleCandles также создадим отдельную ветку для работы над данной статьёй и приступим к разработке в ней.
Подготовка библиотечного кода
Перед началом реализации непосредственно менеджера закрытия, подготовим почву для его внедрения. Прежде всего, отметим, что в последних билдах MetaTrader был добавлен более строгий контроль типов переменных, из-за чего ранее компилировавшийся код стал выдавать ошибки такого типа:
parameter convertion type 'short[260]' to 'ushort[] &' is not allowed MTTester.mqh int user32::GetClassNameW(long,ushort&[],int) winuser.mqh
К счастью, в используемом коде это встретилось в единственном месте и было исправлено изменением типа массива:
static string GetClassName( const HANDLE Handle ) { string Str = NULL; ushort Buffer[MAX_PATH] = {0}; if (user32::GetClassNameW(Handle, Buffer, ::ArraySize(Buffer))) Str = ::ShortArrayToString(Buffer); return(Str); }
Однако затем, после очередных обновлений терминала, этот файл был полностью заменён на последнюю версию из библиотеки MultiTester, чтобы исправить некорректное поведение, имеющее какую-то другую причину.
Следующее изменение связано с необходимостью менеджеру закрытия инициировать закрытие всех позиций. Добавим отдельный метод закрытия всех позиций в класс советника CVirtualAdvisor, чтобы менеджер закрытия смог вызывать его в случае необходимости.
Для реализации этого метода у нас уже есть всё необходимое: каждая стратегия, унаследованная от CVirtualStrategy имеет метод закрытия всех своих виртуальных позиций. Поэтому в классе советника нам достаточно вызвать этот метод для каждой стратегии:
//+------------------------------------------------------------------+ //| Закрытие позиций всех стратегий | //+------------------------------------------------------------------+ void CVirtualAdvisor::Close(void) { // Для всех стратегий вызываем метод закрытия виртуальныхпозиций FOREACH(m_strategies) ((CVirtualStrategy *)m_strategies[i]).Close(); }
В части 27 мы создали компонент для вывода многострочного текста в окно, развернутое на весь график, к которому прикреплён советник. Он был сделан в рамках другого проекта, однако пригодится нам и здесь. Поэтому перенесём его в библиотеку Adwizard, расположив файл с классом CConsoleDialog в папке Adwizard/Utils. Для его использования добавим создание объекта этого класса в итоговом советнике в файле Adwizard/Experts/Expert.mqh:
CConsoleDialog *dialog; // Диалог для вывода текста с результатами //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // ... // Создаём и запускаем диалог для вывода результатов dialog = new CConsoleDialog(); dialog.Create(__NAME__ + ":" + (string) magic_); dialog.Run(); // Успешная инициализация return(INIT_SUCCEEDED); }
В функции обработки нового тика в этом же файле добавим установку нового текста этому объекту. Сам текст мы будем получать от объекта класса CVirtualAdvisor, вызывая его метод Text(), который напишем далее:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { expert.Tick(); // ... // Выводим текст с информацией о работе советника if (IsNewBar(Symbol(), PERIOD_M1)) { dialog.Text(expert.Text()); } }
Для того чтобы на фоне текста не рисовались линии открытия виртуальных позиций, мы временно запретим их визуализацию, сделав метод CVirtualChartOrder::Show() пустым:
//+------------------------------------------------------------------+ //| Показ виртуальной позиции (ордера) | //+------------------------------------------------------------------+ void CVirtualChartOrder::Show() { return; // ... }
Свойство IsActive для всех потомков CFactorable
Когда оптимизация выполняется на торговых инструментах, включающих криптовалюты, а запуск производится у брокера, где криптовалют нет, может возникать ошибка при запуске итогового советника. Она связана с попыткой получить торговую историю и свойства символа, отсутствующего в обзоре рынка. В этом случае, при наличии в итоговом советнике большого количества экземпляров торговых стратегий, работающих на имеющихся символах, можно просто отключать стратегии по тем инструментам, которых нет в обзоре рынка.
Сейчас все торговые стратегии являются потомками класса CFactorable, делающего возможным создание объектов этих стратегий из строки инициализации. В этом классе предусмотрена возможность того, что строка инициализации окажется не совсем правильной. Тогда этот объект и все предыдущие объекты из общей строки инициализации будут признаны неправильными. Советник в этой ситуации не сможет выполнить инициализацию и продолжить работу.
А нам бы хотелось, чтобы определённый вид "ошибок" в строке инициализации позволял бы просто проигнорировать часть строки инициализации, создав в итоге объект эксперта из полной строки инициализации. Для этого добавим к классу CFactorable новое свойство, которое назовём m_isActive и метод для чтения его значения IsActive():
//+------------------------------------------------------------------+ //| Базовый класс объектов, создаваемых из строки | //+------------------------------------------------------------------+ class CFactorable { private: // ... protected: // ... bool m_isActive; // Объект активен? // ... public: // ... bool IsActive(); // Объект активный? // ... };
У некоторых классов, например у класса риск-менеджера CVirtualRiskManager, такое свойство уже было, поэтому в этих классах мы его объявление уберём, так как оно будет сделано в базовом классе. Это касается и будущего класса менеджера закрытия, в котором тоже будет использоваться это свойство для проверки активен ли он.
Попутно мы сделали необязательным указание риск-менеджера и менеджера закрытия в строке инициализации, добавив проверку их наличия при запуске советника в конструкторе класса CVirtualAdvisor:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CVirtualAdvisor::CVirtualAdvisor(string p_params) { // Запоминаем строку инициализации m_params = p_params; // Читаем строку инициализации объекта группы стратегий string groupParams = ReadObject(p_params); // Читаем строку инициализации объекта риск-менеджера string riskManagerParams = NULL; if(IsObjectOf(p_params, "CVirtualRiskManager")) { riskManagerParams = ReadObject(p_params); } // Читаем строку инициализации объекта менеджера закрытия string closeManagerParams = NULL; if(IsObjectOf(p_params, "CVirtualCloseManager")) { closeManagerParams = ReadObject(p_params); } // Читаем магический номер ulong p_magic = ReadLong(p_params); // Читаем название эксперта string p_name = ReadString(p_params); // Читаем признак работы на только на открытии бара m_useOnlyNewBar = (bool) ReadLong(p_params); // Если нет ошибок чтения, то if(IsValid()) { // Создаём группу стратегий CREATE(CVirtualStrategyGroup, p_group, groupParams); // Инициализируем монитор символов статическим монитором символов m_symbols = CSymbolsMonitor::Instance(); // Инициализируем получателя статическим получателем m_receiver = CVirtualReceiver::Instance(p_magic); // Инициализируем интерфейс статическим интерфейсом m_interface = CVirtualInterface::Instance(p_magic); // Формируем из имени эксперта и параметров имя файла базы данных эксперта для сохранения состояния m_fileName = FileName(p_name, p_magic); // Запоминаем время начала работы (тестирования) m_fromDate = TimeCurrent(); // Сбрасываем время последнего сохранения m_lastSaveTime = 0; // Добавляем к эксперту содержимое группы Add(p_group); // Удаляем объект группы delete p_group; // Создаём объект риск-менеджера if(riskManagerParams != NULL) { m_riskManager = NEW(riskManagerParams); } // Создаём объект менеджера закрытия if(closeManagerParams != NULL) { m_closeManager = NEW(closeManagerParams); m_closeManager.Expert(&this); } } }
После внесённых изменений приступим к основной части — созданию менеджера закрытия.
Создаём менеджер закрытия
Для начала выделим несколько возможных состояний, в которых может находиться менеджер закрытия. В нормальном состоянии ещё не достигнута ни запланированная прибыль, ни максимальный убыток. Находясь в этом состоянии, менеджер закрытия должен только ожидать перехода в одно из нескольких следующих состояний. По достижении заданной прибыли или убытка, будет осуществляться переход в два соответствующих состояния. В этих состояниях менеджер закрытия должен закрыть все позиции, запомнить новые уровни заданной прибыли и убытка и перейти в нормальное состояние.
Если будет включён трейлинг, то при достижении уровня заданной прибыли менеджер закрытия будет переходить в другое состояние. Обратный переход из него к нормальному состоянию будет требовать уже более сложных действий, поэтому пока не будем их детально описывать.
Все состояния мы реализуем в виде перечислимого типа ENUM_CM_STATE.
Для задания способов расчёта запланированной прибыли и убытка тоже сделаем два отдельных перечислимых типа: ENUM_CM_CALC_LOSS и ENUM_CM_CALC_PROFIT. Предусмотрим два варианта: фиксированная величина в денежном выражении и относительная величина в процентах от некоторого базового баланса.
// Возможные состояния менеджера закрытия enum ENUM_CM_STATE { CM_STATE_OK, // Лимиты не превышены CM_STATE_LOSS, // Превышен общий лимит CM_STATE_PROFIT, // Достигнута общая прибыль CM_STATE_TRAIL_PROFIT // Трейлинг прибыли }; // Возможные способы расчёта общего убытка enum ENUM_CM_CALC_LOSS { CM_CALC_LOSS_MONEY_BB, // [$] Fixed Money CM_CALC_LOSS_PERCENT_BB, // [%] of Base Balance }; // Возможные способы расчёта общей прибыли enum ENUM_CM_CALC_PROFIT { CM_CALC_PROFIT_MONEY_BB, // [$] Fixed Money CM_CALC_PROFIT_PERCENT_BB, // [%] of Base Balance };
Сам класс менеджера закрытия унаследуем от базового класса CFactorable для обеспечения возможности создавать объект менеджера закрытия из строки инициализации. Заодно у него сразу будет присутствовать наследуемое свойство активности для простого включения или отключения менеджера закрытия.
Чтобы обеспечить возможность выполнять свою задачу, менеджеру закрытия понадобится помнить уровень базового баланса, от которого будет отсчитываться полученная прибыль или убыток. При фиксации прибыли или убытка этот уровень тоже должен изменяться на значение текущего баланса счёта, достигнутое после закрытия всех позиций. В этом состоит отличие этого параметра от одноимённого параметра в риск-менеджере. Там уровень базового баланса всегда остаётся неизменным.
Следующая группа свойств будет использована для выбора способа расчёта и самого расчёта запланированной прибыли и убытка. Они будут использоваться в методах расчёта LossMoney() и ProfitMoney(), возвращающих значение в денежном выражении.
Для закрытия позиций, менеджер закрытия должен иметь возможность обратиться к объекту эксперта с просьбой о закрытии. Поэтому добавим в список свойств менеджера закрытия указатель на объект эксперта и метод для его установки.
Ещё одно свойство добавим для хранения текущего состояния объекта менеджера закрытия.
Порождение от CFactorable требует разместить конструктор в непубличной области и добавить два специальных макроса, как это описано в части 24.
В итоге получим примерно такое описание класса менеджера закрытия:
//+------------------------------------------------------------------+ //| Класс менеджера закрытия (фиксации прибыли и убытков) | //+------------------------------------------------------------------+ class CVirtualCloseManager : public CFactorable { protected: // Основные параметры конструктора double m_baseBalance; // Базовый баланс ENUM_CM_CALC_LOSS m_calcLossLimit; // Способ расчёта максимального общего убытка double m_maxLossLimit; // Параметр расчёта максимального общего убытка ENUM_CM_CALC_PROFIT m_calcProfitLimit; // Способ расчёта максимальной общей прибыли double m_maxProfitLimit; // Параметр расчёта максимальной общей прибыли CVirtualAdvisor* m_expert; // Указатель на объект эксперта // Текущее состояние ENUM_CM_STATE m_state; // Состояние // Обновляемые значения double m_balance; // Текущий баланс double m_equity; // Текущие средства double m_profit; // Текущая плавающая прибыль double m_overallProfit; // Текущая общая прибыль относительно базового баланса // Защищённые методы double LossMoney(); // Максимальный общий убыток double ProfitMoney(); // Максимальная прибыль void UpdateProfit(); // Обновление текущих значений прибыли void CheckLimits(); // Проверка достижения допустимых уровней прибыли/убытка CVirtualCloseManager(string p_params); // Закрытый конструктор public: STATIC_CONSTRUCTOR(CVirtualCloseManager); // Статический метод создания объекта virtual void Tick(); // Обработка тика в менеджере закрытия virtual string Text(); // Информация о текущем состоянии // Привязка эксперта к менеджеру закрытия void Expert(CVirtualAdvisor* p_expert); virtual bool Save(); // Сохранение состояния virtual bool Load(); // Загрузка состояния virtual string operator~() override; // Преобразование объекта в строку }; REGISTER_FACTORABLE_CLASS(CVirtualCloseManager); // Регистрация нового потомка CFactorable
Посмотрим на два основных метода этого класса: конструктор и метод обработки тика.
В конструкторе мы, как обычно, читаем последовательно значения параметров из строки инициализации и присваиваем их соответствующим свойствам, устанавливаем текущее состояние в нормальное, обновляем текущие значения прибыли и запоминаем значение текущего баланса в качестве базового, если он не был задан напрямую:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CVirtualCloseManager::CVirtualCloseManager(string p_params) { // Запоминаем строку инициализации m_params = p_params; // Читаем строку инициализации и устанавливаем значения свойств m_isActive = (bool) ReadLong(p_params); m_baseBalance = ReadDouble(p_params); m_calcLossLimit = (ENUM_CM_CALC_LOSS) ReadLong(p_params); m_maxLossLimit = ReadDouble(p_params); m_calcProfitLimit = (ENUM_CM_CALC_PROFIT) ReadLong(p_params); m_maxProfitLimit = ReadDouble(p_params); // Устанавливаем состояние: Лимиты не превышены m_state = CM_STATE_OK; // Обновляем текущие значения прибыли UpdateProfit(); // Корректируем базовый баланс, если он не задан if(m_baseBalance == 0) { m_baseBalance = m_balance; } }
В основном методе обработки тика мы анализируем текущее состояние и, в зависимости от него, либо проверяем достижение уровней заданной прибыли или убытка, если менеджер находился в нормальном состоянии, либо инициируем закрытие всех позиций с последующим переходом в нормальное состояние:
//+------------------------------------------------------------------+ //| Обработка тика в риск-менеджере | //+------------------------------------------------------------------+ void CVirtualCloseManager::Tick() { // Если риск-менеджер неактивен, то выходим if(!m_isActive) { return; } // Обновляем текущие значения прибыли UpdateProfit(); // Если менеджер в состоянии трейлинга, то if(m_state == CM_STATE_TRAIL_PROFIT) { // Пока просто будем сразу фиксировать прибыль, // переводя менеджер в соответсвующее состояние if(true) { m_state = CM_STATE_PROFIT; } } // Если менеджер в нормальном состоянии, то if(m_state == CM_STATE_OK) { // Проверяем превышение пределов убытка и прибыли CheckLimits(); } // Если менеджер в состоянии достигнутого убытка или прибыли, то if(m_state == CM_STATE_LOSS || m_state == CM_STATE_PROFIT) { // Закрываем все позиции m_expert.Close(); // Если все позиции закрыты, то if(PositionsTotal() == 0) { // Переходим в нормальное состояние m_state = CM_STATE_OK; // Обновляем значение базового баланса m_baseBalance = m_balance; } else { // Ждём закрытия всех позиций } // Сохраняем состояние эксперта m_expert.Save(); } }
Для начала мы решили ограничиться только такой функциональностью менеджера закрытия, поэтому состояние трейлинга прибыли пока что не используется.
Передача входных параметров
После создания класса менеджера закрытия, нам надо подключить его к советнику. Для этого необходимо добавить входные параметры, через которые мы могли бы управлять созданием строки инициализации для менеджера закрытия. Сделать это нужно в файле Adwizard/Experts/Expert.mqh:
// ... //+------------------------------------------------------------------+ //| Входные параметры | //+------------------------------------------------------------------+ 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; // - Масштабирующий множитель для группы input group "::: Менеджер закрытия" input bool cmIsActive_ = true; // - Активен? input double cmStartBaseBalance_ = 0; // - Базовый баланс input ENUM_CM_CALC_LOSS cmCalcLossLimit_ = CM_CALC_LOSS_MONEY_BB; // - Способ расчёта убытка input double cmLossLimit_ = 100; // - Значение убытка для фиксации input ENUM_CM_CALC_PROFIT cmCalcProfitLimit_ = CM_CALC_PROFIT_MONEY_BB; // - Способ расчёта общей прибыли input double cmProfitLimit_ = 1000000; // - Значение общей прибыли для фиксации // ...
Этот файл подключается при компиляции итогового советника, поэтому добавленные входные параметры станут в нём доступны. По умолчанию мы задали, что значения фиксируемой прибыли и убытка будут задаваться в деньгах, в валюте депозита. Сами значения нам ещё предстоит подбирать, поэтому то, что указано в значениях по умолчанию, пока не важно.
Первичное тестирование
Посмотрим, что у нас получилось. Для начала проверим корректность работы самого механизма закрытия позиций без оглядки на получаемую прибыль. Если всё будет работать корректно, то тогда на следующем этапе можно уже заняться и оптимизацией получаемой прибыли.
Воспользуемся базой данных итогового советника, полученной в части 25, для запуска итогового советника в тестере. Мы тогда провели ускоренную оптимизацию на нескольких интервалах, длительностью в 1 год и получили двенадцать групп стратегий, сохранённых в таблице strategy_groups:
Файл базы данных назывался SimpleCandles-27183.test.db.sqlite. Для того, чтобы итоговый советник смог воспользоваться этой базой данных, этот файл должен располагаться в общей папке данных терминалов MeteTrader 5 в подпапке Files. Помимо этого, имя итогового советника должно быть SimpleCandles.ex5 и значение магического номера во входных параметрах необходимо оставить равным 27183.
Запустим сначала советник без использования менеджера закрытия с самой первой группой стратегий с id_group=20. Для этого установим такие значения входных параметров:
В качестве интервала тестирования возьмём интервал, на котором проводилась автоматическая оптимизация, то есть весь 2022 год. Получим следующие результаты:
Рис. 2. Результаты итогового советника с id_group=20 без менеджера закрытия за 2022 год
Как видно, оптимизация нашла довольно неплохие сочетания параметров для разных экземпляров простых торговых стратегий, чтобы на заданном интервале обеспечить существенную прибыли, оставаясь в рамках заданной просадки в 10%.
Теперь включим менеджер закрытия и поставим в качестве ожидаемой прибыли для фиксации небольшое значение, например 10 USD:
Запустим советник в режиме визуального тестирования. Поскольку мы добавили к итоговому советнику отображение информации о работе, то в этом режиме мы можем наблюдать, какие символы и сколько всего стратегий используется в группе с идентификатором 20 из базы данных советника (три символа GBPUSD, EURUSD, EURGBP и 48 стратегий), каково значение базового баланса менеджера закрытия, целевой уровень прибыли и убытка для закрытия.
Рис. 3. Начало визуального тестирования советника с включенным менеджером закрытия
На рисунке 3 видно, что базовый баланс менеджера закрытия уже составил $10009.89, то есть один раз произошло закрытие всех позиций по достижению заданной прибыли $10.
Мы видим в журнале следующую строку:
2022.01.03 02:31:00 CVirtualCloseManager::CheckLimits | CLOSE PROFIT Profit = 12.94 | OverallProfit = 10.54 (10.00)
Менеджер закрытия сработал, когда общая прибыль (OverallProfit = 10.54) по отношению к начальному базовому балансу $10000 превысила $10. Из-за режима тестирования только в начале каждого минутного бара (1 minute OHLC) процесс закрытия всех открытых позиций растянулся на две соседние минуты, поэтому зафиксированный новый базовый уровень оказался немного меньше $10010. При включении режима всех тиков таких расхождений уже не наблюдается.
Протестируем теперь работу менеджера закрытия по ограничению убытков. Поставим небольшое значение для фиксации убытка, например $20, а значение для фиксации прибыли наоборот сделаем большим, чтобы с высокой вероятностью убыток был бы достигнут, а прибыль нет.
В прочих параметрах запретим работать только на открытии бара, чтобы при включении режима моделирования всех тиков (Every tick) советник выполнял все требуемые действия на каждом тике, а не только в начале минутного бара:
Тестирование запустим на коротеньком интервале в один день (2022.01.03). Отфильтровав сообщения журнала, выделим только те строки, которые выводятся при достижении указанного убытка $20:
2022.01.03 17:11:33 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -33.13 | OverallProfit = -20.06 (-20.00)
2022.01.03 17:30:39 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -20.51 | OverallProfit = -20.51 (-20.00)
2022.01.03 19:13:31 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -21.20 | OverallProfit = -20.11 (-20.00)
Видим, что это случилось три раза за день тестирования и в режиме всех тиков общая прибыль (OverallProfit), при которой происходит срабатывание закрытия позиций по достижению заданного убытка, гораздо ближе к указанной в параметрах величине.
Обратите внимание, что в первой записи журнала из приведённых выше есть следующая часть:
Profit = -33.13
Это значение текущей прибыли по открытым позициям (отрицательная прибыль - это как раз и есть убыток). В данном случае она отличается от значения -$20 потому, что вначале было закрыто несколько позиций с прибылью около $13. Поэтому убыток в $20 относительно начального базового баланса счёта был достигнут именно при таком значении прибыли открытых позиций.
Итак, первичное тестирование показало, что разработанный менеджер закрытия уже может выполнять базовую часть своей работы.
Заключение
На этом мы сделаем небольшую паузу и продолжим дальнейшую разработку менеджера закрытия в одной из следующих частей. В планах дальнейшего развития его функциональности остаётся прежде всего добавление трейлинга прибыли открытых позиций и возможность установки уровня безубытка.
Этим доработки не ограничиваются. Например, сейчас менеджер закрытия проверяет завершение закрытия всех позиций просто ожидая, пока количество открытых позиций станет равным нулю. Но это может быть и при наличии открытых виртуальных позиций, поэтому посмотрим, не надо ли здесь применить какой-то более надежный способ проверки. Также нам возможно придется организовать взаимодействие риск-менеджера и менеджера закрытия: при закрытии надо обновлять состояние риск-менеджера и наоборот.
Тем не менее первая версия сделана и следующий шаг будет сделан уже не с нуля.
Спасибо за внимание, до встречи!
Важное предупреждение
Все результаты, изложенные в этой статье и всех предшествующих статьях цикла, основываются только на данных тестирования на истории и не являются гарантией получения хоть какой-то прибыли в будущем. Работа в рамках данного проекта носит исследовательский характер. Все опубликованные результаты могут быть использованы всеми желающими на свой страх и риск.
Содержание архива
# | Имя | Версия | Описание | Последние изменения |
---|---|---|---|---|
SimpleCandles | Рабочая папка проекта (должна быть внутри MQL5/Experts) | |||
1 | SimpleCandles.mq5 | 1.01 | Итоговый советник для параллельной работы нескольких групп модельных стратегий. Параметры будут браться из встроенной библиотеки групп. | Часть 25 |
└ Optimization | Папка советников оптимизации проекта | |||
2 | CreateProject.mq5 | 1.02 | Советник-скрипт создания проекта с этапами, работами и задачами оптимизации. | Часть 25 |
3 | Optimization.mq5 | 1.00 | Советник для автоматической оптимизации проектов | |
4 | Stage1.mq5 | 1.02 | Советник оптимизации одиночного экземпляра торговой стратегии (Этап 1) | Часть 25 |
5 | Stage2.mq5 | 1.01 | Советник оптимизации группы экземпляров торговых стратегий (Этап 2) | Часть 25 |
6 | Stage3.mq5 | 1.01 | Советник, сохраняющий сформированную нормированную группу стратегий в базу данных эксперта с заданным именем. | Часть 25 |
└ Strategies | Папка стратегий проекта | Часть 25 | ||
7 | SimpleCandlesStrategy.mqh | 1.01 | Класс торговой стратегии SimpleCandles | Часть 25 |
└ Include/Adwizard | Папка библиотеки Adwizard | |||
└ Base | Базовые классы, от которых наследуются другие классы проекта | |||
8 | Advisor.mqh | 1.04 | Базовый класс эксперта | Часть 10 |
9 | Factorable.mqh | 1.06 | Базовый класс объектов, создаваемых из строки | Часть 28 |
10 | FactorableCreator.mqh | 1.00 | Класс создателей, связывающих названия и статические конструкторы классов-наследников CFactorable | Часть 24 |
11 | Interface.mqh | 1.01 | Базовый класс визуализации различных объектов | Часть 4 |
12 | Receiver.mqh | 1.04 | Базовый класс перевода открытых объемов в рыночные позиции | Часть 12 |
13 | Strategy.mqh | 1.04 | Базовый класс торговой стратегии | Часть 10 |
└ Database | Файлы для работы со всеми типами баз данных, используемых советниками проекта | |||
14 | Database.mqh | 1.12 | Класс для работы с базой данных | Часть 25 |
15 | db.adv.schema.sql | 1.00 | Схема базы данных итогового советника | Часть 22 |
16 | db.cut.schema.sql | 1.00 | Схема урезанной базы данных оптимизации | Часть 22 |
17 | db.opt.schema.sql | 1.05 | Схема базы данных оптимизации | Часть 22 |
18 | Storage.mqh | 1.01 | Класс работы с хранилищем Key-Value для итогового советника в базе данных эксперта | Часть 23 |
└ Experts | Файлы с общими частями используемых советников разного типа | |||
19 | Expert.mqh | 1.24 | Библиотечный файл для итогового советника. Параметры групп могут браться базы данных эксперта | Часть 28 |
20 | Optimization.mqh | 1.04 | Библиотечный файл для советника, управляющего запуском задач оптимизации | Часть 23 |
21 | Stage1.mqh | 1.19 | Библиотечный файл для советника оптимизации одиночного экземпляра торговой стратегии (Этап 1) | Часть 23 |
22 | Stage2.mqh | 1.04 | Библиотечный файл для советника оптимизации группы экземпляров торговых стратегий (Этап 2) | Часть 23/td> |
23 | Stage3.mqh | 1.04 | Библиотечный файл для советника, сохраняющего сформированную нормированную группу стратегий в базу данных эксперта с заданным именем. | Часть 23 |
└ Optimization | Классы, отвечающие за работу автоматической оптимизации | |||
24 | OptimizationJob.mqh | 1.00 | Класс для работы этапа проекта оптимизации | Часть 25 |
25 | OptimizationProject.mqh | 1.00 | Класс для проекта оптимизации | Часть 25 |
26 | OptimizationStage.mqh | 1.00 | Класс для этапа проекта оптимизации | Часть 25 |
27 | OptimizationTask.mqh | 1.00 | Класс для задачи оптимизации (для создания) | Часть 25 |
28 | Optimizer.mqh | 1.03 | Класс для менеджера автоматической оптимизации проектов | Часть 22 |
29 | OptimizerTask.mqh | 1.03 | Класс для задачи оптимизации (для конвейера) | Часть 22 |
└ Strategies | Примеры торговых стратегий, используемые для демонстрации работы проекта | |||
24 | HistoryStrategy.mqh | 1.00 | Класс торговой стратегии воспроизведения истории сделок | Часть 16 |
25 | SimpleVolumesStrategy.mqh | 1.11 | Класс торговой стратегии с использованием тиковых объемов | Часть 22 |
└ Utils | Вспомогательные утилиты, макросы для сокращения кода | |||
26 | ConsoleDialog.mqh | 1.01 | Класс для вывода текстовой информации на график | Часть 28 |
26 | ExpertHistory.mqh | 1.00 | Класс для экспорта истории сделок в файл | Часть 16 |
27 | Macros.mqh | 1.07 | Полезные макросы для операций с массивами | Часть 26 |
28 | MTTester.mqh | — | Файл для работы с тестером стратегий из библиотеки MultiTester | Часть 28 |
29 | NewBarEvent.mqh | 1.00 | Класс определения нового бара для конкретного символа | Часть 8 |
30 | SymbolsMonitor.mqh | 1.01 | Класс получения информации о торговых инструментах (символах) | Часть 28 |
└ Virtual | Классы для создания различных объектов, объединённых использованием системы виртуальных торговых ордеров и позиций | |||
31 | Money.mqh | 1.01 | Базовый класс управления капиталом | Часть 12 |
32 | TesterHandler.mqh | 1.07 | Класс для обработки событий оптимизации | Часть 23 |
33 | VirtualAdvisor.mqh | 1.12 | Класс эксперта, работающего с виртуальными позициями (ордерами) | Часть 28 |
34 | VirtualChartOrder.mqh | 1.02 | Класс графической виртуальной позиции | Часть 28 |
35 | VirtualCloseManager.mqh | 1.00 | Класс менеджера закрытия | Часть 28 |
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.06 | Класс управления риском (риск-менеждер) | Часть 28 |
41 | VirtualStrategy.mqh | 1.09 | Класс торговой стратегии с виртуальными позициями | Часть 23 |
42 | VirtualStrategyGroup.mqh | 1.04 | Класс группы торговых стратегий или групп торговых стратегий | Часть 28 |
43 | VirtualSymbolReceiver.mqh | 1.00 | Класс символьного получателя | Часть 3 |
Common/Files | Общая папка данных терминалов MetaTrader 5 | |||
44 | SimpleCandles-27183.test.db.sqlite | — | База данных итогового советника | Часть 25 |
Также исходный код доступен в публичных репозиториях SimpleCandles и Adwizard
Как воспользоваться открытыми репозиториями
В связи с постепенным переходом на новое хранилище Algo Forge, мы всё ещё не до конца определились, как лучше и удобнее будет им пользоваться. Ограничение редактора MetaEditor на использование единственного репозитория соответствующего корневой папке MQL5 является не особо удобным. Другие же репозитории будут доступны только как подпапки папки Shared Projects, что несколько скрашивает картину, но не настолько, чтобы сразу начать использовать такой подход.
К тому же, в процессе перехода единственный основной репозиторий создавался от имени суперадмина дважды, причём второй раз его создание по какой-то причине уничтожило созданные ранее другие дополнительные репозитории пользователя. К счастью, имея локальную копию репозитория её можно заново выгрузить на сервер, однако такого рода вынужденные действия не особо желанны. Поэтому будем пока что ждать дальнейшего развития функциональности редактора MetaEditor по работе с репозиториями без их непосредственного использования.
В этом нет ничего сложного: продолжая работать в MetaEditor, мы просто перенесём пока все операции, связанные с хранилищем, во внешние приложения.
Например, получить на локальном компьютере копию всех файлов с кодом данной статьи можно выполнив такой скрипт в консоли, предварительно сделав текущей папкой какую-то папку внутри папки MQL5 нужного терминала MetaTrader:
# Создаём папку для проекта
mkdir SimpleCandles
# Заходим в папку проекта
cd SimpleCandles
# Клонируем репозиторий проекта в текущую папку
git clone https://forge.mql5.io/antekov/SimpleCandles.git .
# Переключаем репозиторий на нужную ветку (для этой статьи - "article-17608-close-manager")
git checkout article-17608-close-manager
# Проверим, что мы переключились на ветку
git status
# Создаём папку для библиотечной части
mkdir Include
# Переходим в неё
cd Include
# Клонируем репозиторий Adwizard в библиотечную папку
git clone https://forge.mql5.io/antekov/Adwizard.git
# Переходим в созданную папку
cd Adwizard
# Переключаем репозиторий на нужную ветку (для этой статьи - "article-17608-close-manager")
git checkout article-17608-close-manager
# Проверим, что мы переключились на ветку
git status
# Поднимемся в исходную папку проекта на два уровня вверх
cd ./../..
Единственное, что отсутствует в этих репозиториях — это файл с базой данных итогового советника, поскольку код в репозитории позволяет его получить, проведя автоматическую оптимизацию. Но при необходимости этот файл можно взять из архива к статье и поместить в общую папку терминалов в папке Files.
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





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