English 中文 Español Deutsch 日本語 Português
preview
Как построить советник, работающий автоматически (Часть 10): Автоматизация (II)

Как построить советник, работающий автоматически (Часть 10): Автоматизация (II)

MetaTrader 5Трейдинг | 26 апреля 2023, 11:13
815 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Как построить советник, работающий автоматически (Часть 09): Автоматизация (I), мы посмотрели, как создать систему безубытка и трейлинг-стопа, которая использует два разных режима. Один из них использует стоп-линию на позициях типа OCO, в то время как другой - отложенный ордер в качестве точки остановки. Как я уже объяснял ранее, у обоих этих методов есть плюсы и минусы.

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

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

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

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

Надо подчеркнуть, что мы всегда должны перепроверять любые дополнения или удаления в советнике, чтобы он мог работать в рамках данной методологии. Однако мы никогда не должны позволять советнику работать без присмотра. Этот момент нужно всегда иметь в виду: НИКОГДА НЕ ПОЗВОЛЯЙТЕ СОВЕТНИКУ РАБОТАТЬ БЕЗ ПРИСМОТРА.

Но как реализовать контроль за расписанием на практике?

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


Планирование

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

Однако здесь, в MQL5, нам не хватает способа объектно-ориентированного программирования, который есть в C++, так называемого множественного наследования. При неправильном применении этой методологии возможны серьезные проблемы при использовании множественного наследования, так как при программировании необходимо обеспечить его правильное использование. Даже без этой особенности C++ можно генерировать некоторые виды кода, сохраняя вещи в рамках системы наследования и применяя скрытие кода.

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

Рисунок 01

Рисунок 01 - Моделирование с помощью множественного наследования

Рисунок 02

Рисунок 02 - Моделирование без множественного наследования

Обратите внимание, что разница между рисунком 01 и рисунком 02 заключается в том, что на первом рисунке класс C_Manager получает методы, реализованные в классах C_Orders и C_ControlOfTime, с помощью наследования, что заставляет класс C_Manager быстро увеличиваться. Поскольку в MQL5 мы не можем этого сделать, нам нужно использовать другой подход, который показан на рисунке 02. Там класс C_ControlOfTime наследуется от класса C_Orders.

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

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


Давайте рассмотрим последние важные моменты перед внедрением

После того, как идея использования моделирования класса определена, что показано на рисунке 02, мы переходим ко второму этапу планирования - созданию класса управления слотом.

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

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

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

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

Рисунок 03

Рисунок 03 - Область настройки советника

На рисунке 03 показана система настройки советника. После настройки эту конфигурацию можно сохранить с помощью кнопки < SAVE >. Когда мы захотим загрузить сохраненную конфигурацию, можно будет использовать кнопку < ОТКРЫТЬ >. Таким образом, мы сможем получить систему, в которой необходимо реализовать гораздо меньше кода, при том, он будет очень надежным. Часть работы будет выполнена самой платформой MetaTrader 5, что позволит нам сэкономить на тестировании, чтобы убедиться, что всё будет работать правильно.

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


Рождение класса C_ControlOfTime

Первое, что нужно сделать внутри заголовочного файла C_ControlOfTime.mqh, это создать код, который видим ниже:

#include "C_Orders.mqh"
//+------------------------------------------------------------------+
class C_ControlOfTime : protected C_Orders
{
        private :
                struct st_00
                {
                        datetime Init,
                                 End;
                }m_InfoCtrl[SATURDAY + 1];
//+------------------------------------------------------------------+
        public  :

//... Процедуры класса ...

};

Обратите внимание, что мы добавляем заголовочный файл C_Orders.mqh для доступа к классу C_Orders. Таким образом, класс C_ControlOfTime будет наследоваться от класса C_Orders с помощью метода protected. Я уже объяснял последствия использования такого вида наследования в другой статье этой серии - "Как построить советник, работающий автоматически (часть 05): Ручные триггеры (II)".

Теперь внутри приватной части кода класса контроля мы добавляем структуру, которая будет использоваться как массив с 7 элементами. Но почему бы не определить 7 вместо того, чтобы использовать сложное объявление? Дело в том, что значение SATURDAY определено внутри языка MQL5 как перечисление, ENUM_DAY_OF_WEEK, что делает более понятным использование дней недели для доступа к массиву.

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

Когда массив определен, то мы можем перейти к нашей первой части кода в классе - конструктору класса, который показан ниже:

                C_ControlOfTime(const ulong magic)
                        :C_Orders(magic)
                        {
                                ResetLastError();
                                for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) ZeroMemory(m_InfoCtrl[c0]);
                        }

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

"Легче понять код, в котором используется язык, напоминающий естественный язык, чем код, в котором используются числовые значения.

Таким образом, цикл в этом конструкторе почти объясняет, что он делает, т.е. именно определяет, является ли код высокоуровневым или нет.

Думаю, вам не составит труда разобраться в этом конструкторе, так как в предыдущих моментах мы уже разобрали, как работает код в конструкторе. Если у вас есть какие-то сомнения, вам следует прочитать предыдущие статьи из этой серии. Единственное, что здесь отличается - это цикл, где мы определяем переменную, которая будет начинаться со значения «воскресенье» "SUNDAY" и заканчиваться на «суббота» "SATURDAY", но в остальном здесь нет ничего сложного.

Дело в том, что этот цикл работает правильно только потому, что перечисление устанавливает "воскресенье" как первый день недели и "субботу" как последний день недели. Однако если бы в качестве первого дня был установлен "понедельник" (MONDAY), то при выполнении кода на платформе MetaTrader 5, этот цикл не сработал бы и выдал ошибку. Поэтому важно быть осторожным при использовании высокоуровневого кода, так как при неправильной настройке, код может сгенерировать несколько ошибок исполнения (RUN-TIME).

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

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
                        {
                                string szRes[];
                                bool bLocal;
                                
                                if (_LastError != ERR_SUCCESS) return;
                                if ((index > SATURDAY) || (index < SUNDAY)) return;
                                if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
                                {
                                        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
                                        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
                                        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
                                        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
                                }
                                if ((_LastError != ERR_SUCCESS) || (!bLocal))
                                {
                                        Print("Error in the declaration of the time of day: ", EnumToString(index));
                                        ExpertRemove();
                                }
                        }

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

if (_LastError != ERR_SUCCESS) return;
if ((index > SATURDAY) || (index < SUNDAY)) return;

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

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

Как только этот первый шаг исполнится и код пройдет эти первые тесты, мы переведем содержимое, переданное автором вызова:

if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
{
        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
}

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

12:34 - - 18:34 <-- Это неправильно
12:32  -  18:34 <-- Правильная информация
12:32     18:34 <-- Это неправильно
       -  18:34 <-- Это неправильно
12:34  -        <-- Это неправильно

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

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

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

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

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

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

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

После преобразования значений мы получим следующий фрагмент кода:

if ((_LastError != ERR_SUCCESS) || (!bLocal))
{
        Print("Error in the declaration of the time of day: ", EnumToString(index));
        ExpertRemove();
}

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

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

virtual const bool CtrlTimeIsPassed(void) final
                        {
                                datetime dt;
                                MqlDateTime mdt;
                                
                                TimeToStruct(TimeLocal(), mdt);
                                dt = (mdt.hour * 3600) + (mdt.min * 60);
                                return ((m_InfoCtrl[mdt.day_of_week].Init <= dt) && (m_InfoCtrl[mdt.day_of_week].End >= dt));
                        }

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

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

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

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

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

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

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

Например, предположим, что мы сообщаем советнику, что он может работать в понедельник с 04:15 до 22:50 и во вторник с 3:15 до 20:45. Мы можем просто позволить ему работать с понедельника по вторник. Как только день сменится с понедельника на вторник, он автоматически начнет проверять, какой временной интервал разрешен для работы во вторник. Поэтому мы решили использовать недельный режим вместо настройки, основанной на текущем дне.

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

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
//+------------------------------------------------------------------+
virtual const bool CtrlTimeIsPassed(void) final
//+------------------------------------------------------------------+

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

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

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

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

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


Создание класса C_ControlOfTime в классе C_Manager и его использование в советнике

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Orders.mqh"
#include "C_ControlOfTime.mqh"
//+------------------------------------------------------------------+
#define def_MAX_LEVERAGE                10
#define def_ORDER_FINISH                false
//+------------------------------------------------------------------+
class C_Manager : private C_Orders
class C_Manager : public C_ControlOfTime

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

Таким образом, мы не изменяем уровень надежности класса C_Manager, который будет продолжать работать максимально безопасно, стабильно и надежно, как обычно. Если класс C_ControlOfTime начинает вызывать нестабильность в классе C_Manager, просто удалим класс C_ControlOfTime, и класс C_Manager снова станет стабильным.

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

Теперь, когда на конструкторы нужно каким-то образом ссылаться, давайте посмотрим, как будет выглядеть новый конструктор класса:

//+------------------------------------------------------------------+
                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_ControlOfTime(magic),
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;

// ... Остальной внутренний код конструктора ....

                        }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                tmp = C_Orders::ToMarket(type, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

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

В коде эксперта единственные действительно необходимые изменения можно увидеть ниже:

#include <Generic Auto Trader\C_Manager.mqh>
#include <Generic Auto Trader\C_Mouse.mqh>
//+------------------------------------------------------------------+
C_Manager *manager;
C_Mouse  *mouse;
//+------------------------------------------------------------------+
input int       user01   = 1;                   //Leverage Factor
input double    user02   = 100;                 //Take Profit ( FINANCE )
input double    user03   = 75;                  //Stop Loss ( FINANCE )
input bool      user04   = true;                //Day Trade ?
input color     user05  = clrBlack;             //Price Line Color
input color     user06  = clrForestGreen;       //Take Line Color 
input color     user07  = clrFireBrick;         //Stop Line Color
input double    user08  = 35;                   //BreakEven ( FINANCE )
//+------------------------------------------------------------------+
input string    user90  = "00:00 - 00:00";      //Sunday
input string    user91  = "09:05 - 17:35";      //Monday
input string    user92  = "10:05 - 16:50";      //Tuesday
input string    user93  = "09:45 - 13:38";      //Wednesday
input string    user94  = "11:07 - 15:00";      //Thursday
input string    user95  = "12:55 - 16:25";      //Friday
input string    user96  = "00:00 - 00:00";      //Saturday
//+------------------------------------------------------------------+
#define def_MAGIC_NUMBER 987654321
//+------------------------------------------------------------------+
int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)
        {
                switch (c0)
                {
                        case SUNDAY     : szInfo = user90; break;
                        case MONDAY     : szInfo = user91; break;
                        case TUESDAY    : szInfo = user92; break;
                        case WEDNESDAY  : szInfo = user93; break;
                        case THURSDAY   : szInfo = user94; break;
                        case FRIDAY     : szInfo = user95; break;
                        case SATURDAY   : szInfo = user96; break;
                }
                (*manager).SetInfoCtrl(c0, szInfo);
        }
        (*manager).CheckToleranceLevel();
        EventSetMillisecondTimer(100);

        return INIT_SUCCEEDED;
}

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


Заключение

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

Если в системе взаимодействия мы вводим время, превышающее 24 часа, оно будет скорректировано с точностью до 24 часов. Например, если мы хотим, чтобы советник торговал до 22:59 (если мы работаем на рынке Forex), мы должны ввести именно это значение. Если вы введете 25:59, система преобразования изменит его на 23:59. Хотя эта типографская ошибка встречается нечасто, такое может произойти.

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

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
                        {
                                string szRes[], sz1[];
                                bool bLocal;
                                
                                if (_LastError != ERR_SUCCESS) return;
                                if ((index > SATURDAY) || (index < SUNDAY)) return;
                                if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
                                {
                                        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
                                        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
                                        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
                                        for (char c0 = 0; (c0 <= 1) && (bLocal); c0++)
                                                if (bLocal = (StringSplit(szRes[0], ':', sz1) == 2))
                                                        bLocal = (StringToInteger(sz1[0]) <= 23) && (StringToInteger(sz1[1]) <= 59);
                                        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
                                }
                                if ((_LastError != ERR_SUCCESS) || (!bLocal))
                                {
                                        Print("Error in the declaration of the time of day: ", EnumToString(index));
                                        ExpertRemove();
                                }
                        }

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


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

Прикрепленные файлы |
Как построить советник, работающий автоматически (Часть 11): Автоматизация (III) Как построить советник, работающий автоматически (Часть 11): Автоматизация (III)
Автоматизированная система без соответствующей безопасности не будет успешной. Однако безопасность не будет обеспечена без хорошего понимания некоторых вещей. В этой статье мы разберемся с тем, почему достижение максимальной безопасности в автоматизированных системах является такой сложной задачей.
Эксперименты с нейросетями (Часть 6): Перцептрон как самодостаточное средство предсказания цены Эксперименты с нейросетями (Часть 6): Перцептрон как самодостаточное средство предсказания цены
Пример использования перцептрона как самодостаточного средства предсказания цены. В статье даются общие понятия, представлен простейший готовый советник и результаты его оптимизации.
Как построить советник, работающий автоматически (Часть 12): Автоматизация (IV) Как построить советник, работающий автоматически (Часть 12): Автоматизация (IV)
Если вы думаете, что автоматизированные системы просты, то наверно вы еще не до конца поняли, что нужно для их создания. В данном материале мы поговорим о проблеме, с которой сталкиваются многие советники: неизбирательное исполнение ордеров, и возможное решение этой проблемы.
Как построить советник, работающий автоматически (Часть 09): Автоматизация (I) Как построить советник, работающий автоматически (Часть 09): Автоматизация (I)
Хотя создание автоматического советника не является очень сложной задачей, однако без необходимых знаний может быть допущено много ошибок. В этой статье мы рассмотрим, как построить первый уровень автоматизации: он заключается в создании триггера для активации безубытка и трейлинг-стопа.