Основы тестирования в MetaTrader 5
Зачем нужен тестер
Идея автоматической торговли привлекательна тем, что торговый робот может без устали работать 24 часа в сутки и семь дней в неделю. Робот не знает усталости, сомнений и страха, ему не ведомы психологические проблемы. Достаточно четко формализовать торговые правила и реализовать их в виде алгоритмов, и робот готов неустанно трудиться. Но прежде необходимо убедиться в соблюдении двух важных условий:
- эксперт совершает торговые операции в соответствии с правилами торговой системы;
- торговая стратегия, реализованная в эксперте, показывает прибыль на истории.
Для получения ответов на эти вопросы предназначен тестер стратегий, входящий в состав клиентского терминала MetaTrader 5.
Режимы генерации тиков
Эксперт на языке MQL5 представляет из себя программу, которая запускается каждый раз в ответ на некое внешнее воздействие - событие. Для каждого предопределенного события в эксперте есть соответствующая этому событию функция - обработчик события.
Основным событием для эксперта является изменение цены - NewTick, и поэтому для тестирования экспертов необходимо генерировать тиковые последовательности. В тестере клиентского терминала MetaTrader 5 реализовано 3 режима генерации тиков:
- Все тики
- Цены OHLC с минутных баров (1 Minute OHLC)
- Только цены открытия
Базовым и наиболее детальным режимом генерации является режим "Все тики", остальные два режима являются упрощением основного и будут описаны в сравнении с режимом "Все тики". Рассмотрим все три режима, чтобы понять в чем различие между ними.
Все тики
История котировок по финансовым инструментам передается от торгового сервера в клиентский терминал MetaTrader 5 в виде экономно упакованных блоков минутных баров. Подробную информацию о том, как происходит запрос и построение требуемых таймфреймов можно получить из раздела справки Организация доступа к данным.
Минимальным элементом ценовой истории является минутный бар, из которого можно получить информацию о четырех значениях цены:
- Open - цена, по которой открылся минутный бар;
- High - максимум, который достигался в течение этого минутного бара;
- Low - минимум, который достигался в течение этого минутного бара;
- Close - цена закрытия бара.
Новый минутный бар открывается не в тот момент, когда начинается новая минута (количество секунд становится равным 0), а когда приходит тик - изменение цены хотя бы на один пункт. На рисунке показан первый минутный бар новой торговой недели, который имеет время открытия 2011.01.10. 00:00. Ценовой разрыв между пятницей и понедельником, который мы видим на графике, является обычным явлением, так как даже в выходные дни валютные курсы изменяются в ответ на поступающие новости.
Для этого бара нам только известно, что данный минутный бар открыт 10 января 2011 года в 00 часов 00 минут, но ничего неизвестно о секундах. Это могло быть время 00:00:12 или 00:00:36 (12 или 36 секунд после начала нового дня) или любое другое время в пределах этой минуты. Но мы знаем точно, что в момент открытия нового минутного бара цена Open по EURUSD была на уровне 1.28940.
Точно также мы не знаем с точностью до секунды, когда пришел тик, соответствующий цене закрытия рассматриваемого минутного бара, известно только одно - это последняя цена на минутном баре, которая и была записана как цена Close. Для данной минуты это оказалась цена 1.28958. Время появления цен High и Low также неизвестно, но мы знаем, что максимальная и минимальная цена точно побывала на уровнях 1.28958 и 1.28940 соответственно.
Для тестирования торговой стратегии нам необходима тиковая последовательность, на которой будет эмулироваться работа эксперта. Таким образом, для каждого минутного бара нам известны 4 контрольные точки, о которых мы точно можем сказать, что цена там побывала. Если бар имеет только 4 тика, то для тестирования этой информации достаточно, но обычно тиковый объем больше 4. Значит, необходимо сгенерировать дополнительные контрольные точки для тиков, которые приходили между ценами Open, High, Low и Close. Принцип генерации тиков в режиме "Все тики" описан в статье Алгоритм генерации тиков в тестере стратегий терминала MetaTrader 5, рисунок из которой представлен ниже.
При тестировании в режиме "Все тики" функция OnTick() эксперта будет вызываться на каждой контрольной точке, каждая контрольная точка - это тик из сгенерированной последовательности. Эксперт будет получать время и цену смоделированного тика так же, как и при работе в онлайне.
Важно: режим тестирования "Все тики" является самым точным, но при этом и самым затратным по времени. Для первичной оценки большинства торговых стратегий обычно достаточно использовать один из двух других режимов тестирования.
1 minute OHLC
Тестирование в режиме "Все тики" является самым точным из трех режимов, но в то же время и самым медленным. Запуск обработчика OnTick() происходит на каждом тике, а тиковый объем может быть достаточно большим. Для стратегий, которым не важно, в какой тиковой последовательности развивалась цена в течение бара, существует более быстрый и более грубый режим моделирования - "1 minute OHLC".
В режиме "1 minute OHLC" тиковая последовательность строится только по OHLC ценам минутных баров, количество сгенерированных контрольных точек существенно уменьшается - следовательно, уменьшается время тестирования. Запуск функции OnTick() производится на всех контрольных точках, которые строятся по ценам OHLC минутных баров.
Отказ от генерации дополнительных промежуточных тиков между ценами Open, High, Low и Close приводит к появлению жесткой детерминированности в развитии цены с того момента, как определена цена Open. Это дает возможность для создания "Грааля тестирования", который показывает красивый восходящий график баланса при тестировании. Пример такого Грааля представлен в Code Base - Grr-al.
На рисунке представлен очень привлекательный график тестирования этого эксперта. Как он получен? Для минутного бара известно 4 цены, и для них точно известно, что первой идет цена Open, а последней идет цена Close. Между ними есть цены High и Low, последовательность их наступления неизвестна, но известно, что цена High больше или равна цене Open (цена Low меньше или равна цене Open).
Достаточно определить момент поступления цены Open и затем анализировать следующий тик, чтобы определить что перед нами - High или Low. Если цена ниже цены Open, значит, перед нами цена Low - покупаем на этом тике, следующий тик будет соответствовать цене High, на котором закрываем покупку и открываем продажу. Следующий тик последний, это цена Close, на нем закрываем продажу.
Если после цены пришел тик с ценой больше цены открытия, то последовательность сделок обратная. Отработаем в таком мошенническом режиме минутный бар и ждем следующий. При тестировании такого эксперта на истории все идет хорошо, но стоит запустить его в онлайне, и сказка рассыпается - линия баланса по-прежнему ровная, но идет вниз. Для быстрого разоблачения трюка достаточно прогнать такой советник в режиме "Все тики".
Важно: если результаты тестирования эксперта на грубых режимах тестирования ("1 minute OHLC" и "Только цены открытия") слишком хороши, обязательно протестируйте его в режиме "Все тики".
Только цены открытия
В данном режиме происходит генерация тиков по ценам OHLC таймфрейма, выбранного для тестирования. При этом функция эксперта OnTick() запускается только в начале бара по цене Open. Из-за этой особенности стоп-уровни и отложенные ордера могут срабатывать не по заявленной цене (особенно при тестировании на старших таймфреймах). В обмен за это мы получаем возможность быстро провести оценочное тестирование эксперта.
Исключением при генерации тиков в режиме "Только цены открытия" являются периоды W1 и MN1: для этих таймфреймов тики генерируются для цен OHLC каждого дня, а не для цен OHLC недели или месяца соответственно.
Например, производится тестирование советника на EURUSD H1 в режиме "Только цены открытия". В этом случае общее количество тиков (контрольных точек) будет не больше 4*количество часовых баров, попавших в тестируемый интервал. Но при этом вызов обработчика OnTick() производится только на открытии часового бара. На остальных ("скрытых" от эксперта) тиках происходят проверки, необходимые для корректного тестирования:
- вычисление маржевых требований;
- срабатывание Stop Loss и Take Profit;
- срабатывание отложенных ордеров;
- удаление отложенных ордеров с истекшим временем.
Если нет открытых позиций или отложенных ордеров, то необходимости в данных проверках на скрытых тиках нет и прирост скорости может оказаться существенным. Данный режим "Только цены открытия" хорошо подходит для тестирования стратегий, которые совершают сделки только на открытии бара и не используют отложенные ордера, а также не используют ордера StopLoss,. TakeProfit. Для класса таких стратегий сохраняется вся необходимая точность тестирования.
В качестве примера эксперта, которому не важно в каком режиме тестироваться, приведем советник Moving Average из стандартной поставки. Логика этого эксперта построена таким образом, что все решения принимаются на открытии бара и сделки проводятся сразу же без использования отложенных ордеров. Запустим тестирование эксперта на EURUSD H1 на интервале с 2010.01.09 по 2010.31.12 и сравним графики. На рисунке показаны графики баланса из отчета тестера для всех трех режимов.
Рис. 4. График тестирования советника Moving Average.mq5 из стандартной поставки не зависит от режима тестирования (нажмите на рисунок для увеличения)
Как видите, графики на разных режимах тестирования абсолютно одинаковы для советника Moving Average из стандартной поставки.
Существует ряд ограничений применения режима "Только цены открытия":
- Нельзя использовать режим торговли "Произвольная задержка";
- В тестируемом эксперте невозможно обратиться к данным более низкого таймфрейма, чем тот, что используется для тестирования/оптимизации. Например, если тестирование/оптимизация осуществляется на периоде H1, то вы можете обращаться к данным H2, H3, H4 и т.д., но не к данным M30, M20, M10 и т.д. Помимо этого, более старшие таймфреймы, к которым идет обращение, должны быть кратными таймфрейму тестирования. Например, при тестировании на периоде M20 нельзя обратиться к таймфрейму M30, но можно к H1. Эти ограничения обусловлены невозможностью получить данные более низких и не кратных таймфреймов из баров, генерируемых при тестировании/оптимизации.
- Ограничения по обращению к данным других таймфремов распространяются и на другие символы, чьи данные используются советником. Однако в этом случае ограничением для каждого символа служит первый таймфрейм, к которому произошло обращение во время тестирования/оптимизации. Например, тестирование осуществляется на символе и периоде EURUSD H1, советник в первый раз обратился к символу GBPUSD M20. В этой ситуации советник в дальнейшем может использовать данные EURUSD H1, H2, и т.д., а также GBPUSD M20, H1, H2 и т.д.
Важно: режим "Только цены открытия" является самым быстрым по времени тестирования, но подходит не для всех торговых стратегий. Выбирайте необходимый режим тестирования исходя из особенностей работы торговой системы.
В завершение раздела о режимах моделирования приведем визуальное сравнение разных режимов генерации тиков для EURUSD для двух баров M15 на интервале с 2011.01.11 21:00:00 - 2011.01.11 21:30:00. Запись тиков произведена с помощью эксперта WriteTicksFromTester.mq5 в разные файлы, окончание имен этих файлов задаются input-параметраx filenamEveryTick, filenameOHLC и filenameOpenPrice.
Рис. 5. В тестере для советника WriteTicksFromTester.mq5 можно указать начальную и конечную дату записываемых тиков (переменные start и end).
Чтобы получить три файла с тремя тиковыми последовательностями (для каждого из режимов "Все тики", "OHLC на минутных барах" и "Только цены открытия") советник был запущен трижды в соответствующих режимах на одиночных прогонах. Затем данные из этих трех файлов с помощью индикатора TicksFromTester.mq5 были выведены на график. Код индикатора прилагается к статье.
Рис. 6. Тиковая последовательность в тестере терминала MetaTrader 5 в трех различных режимах тестирования.
По умолчанию все файловые операции в языке MQL5 производятся в пределах "файловой песочницы" и при тестировании эксперту доступна только собственная "файловая песочница". Для того чтобы индикатор и эксперт при тестировании работали с файлами из одной папки, использовался флаг FILE_COMMON. Пример кода из эксперта:
//--- откроем файл file=FileOpen(filename,FILE_WRITE|FILE_CSV|FILE_COMMON,";"); //--- проверим успешность операции if(file==INVALID_HANDLE) { PrintFormat("Не удалось открыть на запись файл %s. Код ошибки=%d",filename,GetLastError()); return; } else { //--- сообщим о записи в общую папку всех клиентских терминалов и ее местоположение PrintFormat("Файл будет записан в папке %s",TerminalInfoString(TERMINAL_COMMONDATA_PATH)); }
В индикаторе для чтения данных также использовался флаг FILE_COMMON, это позволило избежать переноса необходимых файлов вручную из одной папки в другую.
//--- откроем файл int file=FileOpen(fname,FILE_READ|FILE_CSV|FILE_COMMON,";"); //--- проверим успешность операции if(file==INVALID_HANDLE) { PrintFormat("Не удалось открыть на чтение файл %s. Код ошибки=%d",fname,GetLastError()); return; } else { //--- сообщим местонахождение общей папки всех клиентских терминалов PrintFormat("Файл будет прочитан из папки %s",TerminalInfoString(TERMINAL_COMMONDATA_PATH)); }
Моделирование спреда
Разница между ценами Bid и Ask называется спредом. При тестировании спред не моделируется, а берется из исторических данных. Если в исторических данных спред меньше или равен нулю, то используется текущий спред на момент запроса информации со стороны тестерного агента.
В тестере спред всегда считается плавающим. То есть SymbolInfoInteger(symbol, SYMBOL_SPREAD_FLOAT) всегда возвращает true.
Кроме того, в исторических данных хранятся значения тиковых и торговых объемов. Для хранения и получения данных используется специальная структура MqlRates:
struct MqlRates { datetime time; // время открытия бара double open; // цена открытия Open double high; // наивысшая цена High double low; // наименьшая цена Low double close; // цена закрытия Close long tick_volume; // тиковый объем int spread; // спред long real_volume; // биржевой объем };
Глобальные переменные клиентского терминала
При тестировании глобальные переменные клиентского терминала также эмулируются, но они никак не связаны с настоящими глобальным переменным терминала, которые можно увидеть в терминале по кнопке F3. Это означает, что все операции с глобальными переменными терминала при тестировании производятся вне самого клиентского терминала (в агенте тестирования).
Расчет индикаторов при тестировании
В режиме реального времени значения индикаторов вычисляются на каждом тике. В тестере принята экономичная модель вычисления индикаторов - индикаторы пересчитываются только непосредственно перед тем, как запускается на исполнение эксперт. Это означает, что пересчет значений индикаторов производится перед вызовом функций OnTick(), OnTrade() и OnTimer().
Неважно, есть вызов индикатора в конкретном обработчике события или нет, все индикаторы, чьи хэндлы были созданы функцией iCustom() или IndicatorCreate(), будут принудительно пересчитаны перед вызовом функции-обработчика события.
Следовательно, при тестировании в режиме "Все тики" расчет индикаторов происходит перед каждым вызовом OnTick(). Если в эксперте с помощью функции EventSetTimer() включен таймер, то индикаторы будут пересчитаны перед каждым вызовом обработчика OnTimer(). Следовательно, время тестирования может многократно возрасти при использовании в эксперте индикатора, написанного неоптимальным образом.
Загрузка истории при тестировании
История по тестируемому инструменту синхронизируется и закачивается терминалом с торгового сервера перед запуском процесса тестирования. При этом в первый раз терминал скачивает с торгового сервера сразу всю доступную по тестируемому инструменту историю, чтобы впоследствии не обращаться за ней. В дальнейшем происходит лишь докачка новых данных.
Агент тестирования получает от клиентского терминала историю по тестируемому инструменту сразу же после запуска тестирования. Если в процессе тестирования используются данные по другим инструментам (например, это мультивалютный эксперт), то в этом случае агент тестирования запрашивает у клиентского терминала требуемую историю при первом же обращении. Если исторические данные имеются на терминале, они сразу передаются на агенты тестирования. Если данные отсутствуют, терминал запросит и скачает их с сервера, а затем передаст на агенты тестирования.
Обращение к дополнительным инструментам происходит и в том случае, когда вычисляется цена кросс-курса при торговых операциях. Например, при тестировании стратегии на EURCHF с валютой депозита в долларах США перед обработкой первой же торговой операцией агент тестирования запрашивает у клиентского терминала историю по EURUSD и USDCHF, хотя в стратегии нет прямого обращения к этим инструментам.
Перед началом тестирования мультивалютной стратегии рекомендуется предварительно скачать все необходимые исторические данные на клиентском терминале. Это позволит избежать задержек при тестировании/оптимизации, связанных с докачкой данных. Закачать историю можно, например, путем открытия соответствующих графиков и прокрутки их к началу истории. Пример принудительной загрузки истории в торговый терминал приведен в документации по MQL5 в разделе Организация доступа к данным.
Тестерные агенты в свою очередь получают историю от терминала и также в упакованном виде. При повторном тестировании загрузка тестером истории из терминала уже не происходит, потому что данные есть от предыдущего запуска тестера.
Мультивалютное тестирование
Тестер позволяет проводить проверку на истории стратегий, торгующих на нескольких инструментах. Такие эксперты условно называют мультивалютными, так как изначально в предыдущих платформах тестирование проводилось только для одного инструмента. В тестере же терминала MetaTrader 5 можно моделировать торговлю по всем доступным инструментам.
История по используемым инструментам закачивается тестером из клиентского терминала (не с торгового сервера!) автоматически при первом обращении к данному инструменту.
Агент тестирования закачивает только недостающую историю с небольшим запасом, чтобы обеспечить необходимые данные на истории для расчета индикаторов на момент начала тестирования. Минимальный объем истории при скачивании с торгового сервера для таймфреймов D1 и меньше составляет один год. Таким образом, если запускается тестирование на интервале 2010.11.01-2010.12.01 (тестирование на интервале в один месяц) с периодом M15 (каждый бар равен 15 минутам), то у терминала будет запрошена история по инструменту за весь 2010 год. Для таймфреймов Weekly будет запрошена история в 100 баров, что составляет примерно два года (в году 52 недели). Для тестирования на месячном таймфрейме Monthly агент запросит историю за 8 лет (12 месяцев * 8 лет = 96 месяцев).
Если не удается по каким-либо причинам обеспечить необходимый запас баров перед началом тестирования, то дата начала будет автоматически сдвинута от прошлого к настоящему для того, чтобы обеспечить такой запас.
При тестировании эмулируется также и "Обзор рынка", из которого можно получать информацию по инструментам. По умолчанию в начале тестирования в "Обзоре рынка" тестера есть только один символ - символ на котором запущено тестирование. Все необходимые символы подключаются к "Обзору рынка" тестера (не терминала!) автоматически при обращении к ним.
Перед началом тестирования мультивалютного эксперта необходимо выбрать требуемые для тестирования инструменты в "Обзоре рынка" терминала и подкачать данные на нужную глубину. При первом же обращении к "чужому" символу будет автоматически произведена синхронизация по этому символу между агентом тестирования и клиентским терминалом. "Чужой" символ - это символ, отличающийся от того, на котором запущено тестирование.
Обращение к данным чужого символа происходят в следующих случаях:
- использование функций технических индикаторов и IndicatorCreate() на паре символ/таймфрейм;
- запрос к "Обзору рынке" (Market Watch) по чужому символу:
- запрос к таймсерии по паре символ/период функциями:
В тот момент, когда происходит первое обращение к чужому символу, процесс тестирования останавливается и происходит подкачка истории по паре символ/период от терминала к агенту тестирования. Одновременно включается генерация тиковой последовательности для этого символа.
Для каждого инструмента генерируется собственная тиковая последовательность в соответствие с выбранным режимом генерации тиков. Кроме того, можно явно запросить историю для нужных символов с помощью вызова функции SymbolSelect() в обработчике OnInit() - загрузка истории будет произведена сразу же до начала тестирования советника.
Таким образом, для проведения мультивалютного тестирования в клиентском терминале MetaTrader 5 не требуется предпринимать никаких дополнительных усилий. Достаточно открыть графики соответствующих инструментов в клиентском терминале. История по нужным символам будет автоматически загружена с торгового сервера при условии, что эти данные есть на нем.
Моделирование времени в тестере
При тестировании локальное время TimeLocal() всегда равно серверному времени TimeTradeServer(). В свою очередь, cерверное время всегда равно времени, соответствующему времени GMT - TimeGMT(). Таким образом, все эти функции при тестировании выдают одно и то же время.
Отсутствие разницы между GMT, локальноым и серверным временами в тестере сделано сознательно по той самой причине, что связь с сервером может быть не всегда. А результаты тестирования должны быть одинаковыми, независимо от наличия связи. Информация о серверном времени не хранится локально, а берётся с сервера.
Функция OnTimer() в тестере
В MQL5 возможна обработка событий таймера. Вызов обработчика OnTimer() производится независимо от режима тестирования. Это означает, что если тестирование запущено в режиме "Только цены открытия" на периоде H4 и внутри эксперта установлен таймер с вызовом каждую секунду, то на открытии каждого H4 бара один раз будет вызван обработчик OnTick() и 14400 раз (3600 секунд * 4 часа) в течение бара будет вызван обработчик OnTimer(). Насколько при этом увеличится время тестирования эксперта, зависит от логики эксперта.
Для проверки зависимости времени тестирования от заданной периодичности таймера был написан простой эксперт без торговых операций.
//--- input parameters input int timer=1; // значение таймера, сек input bool timer_switch_on=true; // таймер включен //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- запустим таймер если timer_switch_on==true if(timer_switch_on) { EventSetTimer(timer); } //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- остановим таймер EventKillTimer(); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- // ничего не делаем, тело обработчика пустое } //+------------------------------------------------------------------+
Были сделаны замеры времени тестирования при различных значениях параметра timer (периодичность события Timer). На полученных данных построен график зависимости времени тестирования T от значения периодичности Period.
Хорошо видно, чем меньше параметр timer при инициализации таймера функцией EventSetTimer(timer), тем меньше период (Period) между вызовами обработчика OnTimer(), и тем больше время тестирования T при одних и тех же остальных условиях.
Функция Sleep() в тестере
Функция Sleep() позволяет в эксперте или скрипте приостановить выполнение mql5-программы на некоторое время при работе на графике. Это может понадобиться при запросе каких-либо данных, которые в момент запроса еще не готовы и необходимо дождаться момента их готовности. Подробный пример использования функции Sleep() можно посмотреть в разделе Организация доступа к данным.
В тестере же вызовы Sleep() не задерживают процесс тестирования. При вызове Sleep() "проигрываются" сгенерированные тики в пределах указанной задержки, в результате чего могут сработать отложенные ордера, стопы и т.д. После вызова Sleep() cмоделированное в тестере время увеличивается на интервал, указанный в параметре функции Sleep.
Если в результате выполнения функции Sleep() текущее время в тестере вышло за конец периода тестирования, то будет получена ошибка "бесконечный цикл в Sleep". При получение такой ошибки результаты тестирования не отбрасываются, все вычисления производятся в полном объеме (количество сделок, просадка и т.д.) и результаты данного тестирования передаются терминалу.
Функция Sleep() не будет работать в OnDeinit(), так как после ее вызова тестерное время гарантированно окажется за пределами интервала тестирования.
Рис. 7. Схема использования функции Sleep() в тестере терминала MetaTrader 5.
Использование тестера для задач оптимизации в математических вычислениях
Тестер в терминале MetaTrader 5 можно использовать не только для проверки торговых стратегий, но и для математических расчётов. Для этого необходимо выбрать соответствующий режим в настройках:
При выборе режима "Математические вычисления" будет произведен "пустой" прогон агента тестирования. Пустой прогон означает, что не будет производиться генерация тиков и загрузки истории. При таком прогоне будут вызваны только три функции: OnInit(), OnTester(), OnDeinit().
Если дата окончания тестирования меньше или равна дате начала тестирования, то это также будет означать тестирования в режиме "Математические вычисления".
При использовании тестера для решения математических задач закачка истории и генерация тиков не происходят.
Типичная математическая задача для решения в тестере MetaTrader 5 - поиск экстремума от функции многих переменных. Для ее решения необходимо:
- Разместить блок вычислений значения функции от многих переменных в OnTester() и вернуть вычисленное значение через return(значение_функции);
- Вынести параметры функции в глобальную область программы в виде input-переменных;
Компилируем советник, открываем окно "Тестер". На вкладке "Входные параметры" отмечаем требуемые входные переменные и задаем для них задаем границы в пространстве значений и шаг для перебора.
Выбираем тип оптимизации - "Медленный (полный перебор параметров) или "Быстрая (генетический аглоритм)". Для простого поиска экстремума функции лучше выбрать быструю оптимизацию, но если нужно вычислить значения на всем пространстве переменных, то подойдет медленная оптимизация.
Выбираем режим "Математические вычисления" и запускаем кнопкой "Старт" процедуру оптимизации. Необходимо помнить, что при оптимизации всегда ищется локальный максимум значения функции OnTester. Для поиска локального минимума можно из функции OnTester возвращать значение, обратное вычисленному значению функции:
return(1/значение_функции);
При этом необходимо самостоятельно реализовать проверку, чтобы значение_функции не было равно нулю, так как в противном случае можно получить критическую ошибку деления на ноль. Есть и другой вариант, более подходящий и не искажающий результаты оптимизации, он предложен читателями статьи:
return(-значение_функции);
В этом варианте не требуется проверять значение_функции на равенство нулю и сама поверхность результатов оптимизации в 3D-представлении имеет ту же форму, только зеркально отраженную от исходной.
В качестве примера приведем функцию sink():
Код советника для поиска экстремума этой функции поместим в OnTester():
//+------------------------------------------------------------------+ //| Sink.mq5 | //| Copyright 2011, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" //--- input parameters input double x=-3.0; // start=-3, step=0.05, stop=3 input double y=-3.0; // start=-3, step=0.05, stop=3 //+------------------------------------------------------------------+ //| Tester function | //+------------------------------------------------------------------+ double OnTester() { //--- double sink=MathSin(x*x+y*y); //--- return(sink); } //+------------------------------------------------------------------+Проведем оптимизацию и представим результаты оптимизации в виде 2D графика.
Рис. 8. Результаты полной оптимизации функции sink(x*x+y*y) в виде 2D-графика.
Чем лучше значение для заданной пары параметров (x,y), тем более насыщенный цвет. Как и ожидалось из вида формулы функции sink(), ее значения образуют концентрические круги с центром в точке (0,0). Для функции sink() не существует абсолютного экстремума. Это хорошо видно при просмотре результатов оптимизации в режиме 3D:
Синхронизация баров при тестировании в режиме "Только цены открытия"
Тестер в клиентском терминале MetaTrader 5 позволяет проверять и, так называемые, "мультивалютные" советники. Мультивалютный советник - это советник, который торгует на двух или более символах.
Тестирование стратегий, торгующих на нескольких инструментах, налагает на тестер несколько дополнительных технических требований:
- генерации тиков для этих инструментов;
- расчет значений индикаторов для этих инструментов;
- расчет маржевых требований по этим инструментам;
- синхронизация сгенерированных тиковых последовательностей по всем торгуемым инструментам.
Тестер генерирует и проигрывает для каждого инструмента тиковую последовательность в соответствии с выбранным режимом торговли. При этом новый бар на каждом инструменте открывается независимо от того, как открылся бар на другом инструменте. Это означает, что при тестировании мультивалютного эксперта возможна ситуация (и чаще всего так и бывает), когда на одном инструменте новый бар уже открылся, а на другом еще нет. Таким образом, при тестировании все происходит как в жизни.
Такое достоверное моделирование развития истории в тестере не вызывает вопросов до тех пор, пока используются режимы тестирования "Все тики" и "1 minute OHLC". При этих режимах в пределах одной свечи генерируется достаточное количество тиков, чтобы дождаться момента синхронизации баров с разных символов. Но как тестировать мультивалютные стратегии в режиме "Только цены открытия", если требуется обязательная синхронизация баров на торгуемых инструментах? Ведь в этом режиме эксперт вызывается только на одном тике, который соответствует времени открытия бара.
Поясним на примере: если мы тестируем эксперта на символе EURUSD, и на EURUSD открылась новая часовая свеча, то мы легко узнаем этот факт - при тестировании в режиме "Только цены открытия" событие NewTick соответствует моменту открытия бара на тестируемом периоде. Но при этом нет никакой гарантии, что новая свеча открылась по символу GBPUSD, который используется в эксперте.
В обычных условиях достаточно завершить работу функции OnTick() и проверить появление нового бара на GBPUSD на следующем тике. Но при тестировании в режиме "Только цены открытия" другого тика не будет, и может показаться, что этот режим не годится для тестирования мультивалютных экспертов. Но это не так - не забывайте, что тестер в MetaTrader 5 ведет себя также, как и в жизни. Можно дождаться момента, когда на другом символе откроется новый бар с помощью функции Sleep()!
Код советника Synchronize_Bars_Use_Sleep.mq5, который демонстрирует пример синхронизации баров при тестировании в режиме "Только цены открытия":
//+------------------------------------------------------------------+ //| Synchronize_Bars_Use_Sleep.mq5 | //| Copyright 2011, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" //--- input parameters input string other_symbol="USDJPY"; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- сверим текущий символ if(_Symbol==other_symbol) { PrintFormat("Необходимо указать другой символ или запустить тестирование на другом символе!"); //--- принудительно прекращаем тестирование эксперта return(-1); } //--- return(0); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- статическая переменная для хранения времени открытия последнего бара static datetime last_bar_time=0; //--- признак того, что время открытия последнего бара на разных символах синхронизировано static bool synchonized=false; //--- если статическая переменная еще неинициализирована if(last_bar_time==0) { //--- это первый вызов, запишем время открытия и выйдем last_bar_time=(datetime)SeriesInfoInteger(_Symbol,Period(),SERIES_LASTBAR_DATE); PrintFormat("Инициализировали переменную last_bar_time значением %s",TimeToString(last_bar_time)); } //--- получим время открытия последнего бара по своему символу datetime curr_time=(datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE); //--- если время открытия текущего бара не совпадает с тем, что хранится в last_bar_time if(curr_time!=last_bar_time) { //--- запомним время открытия нового бара в статической переменной last_bar_time=curr_time; //--- синхронизация нарушена, сбросим флаг в false synchonized=false; //--- выведем сообщение об этом событии PrintFormat("На символе %s открылся новый бар в %s",_Symbol,TimeToString(TimeCurrent())); } //--- сюда будем сохранять время открытия бара на чужом символе datetime other_time; //--- цикл, пока время открытия последнего бара по другому символу не сравняется с curr_time while(!(curr_time==(other_time=(datetime)SeriesInfoInteger(other_symbol,Period(),SERIES_LASTBAR_DATE)) && !synchonized)) { PrintFormat("Подождем 5 секунд.."); //--- подождем 5 секунд и опять запросим SeriesInfoInteger(other_symbol,Period(),SERIES_LASTBAR_DATE) Sleep(5000); } //--- время открытия бара теперь одинаково для обоих символов synchonized=true; PrintFormat("Время открытия последнего бара на своем символе %s: %s",_Symbol,TimeToString(last_bar_time)); PrintFormat("Время открытия последнего бара на символе %s: %s",other_symbol,TimeToString(other_time)); //--- TimeCurrent() не подойдет, используем TimeTradeServer() для Print("Бары синхронизировались в ",TimeToString(TimeTradeServer(),TIME_DATE|TIME_SECONDS)); } //+------------------------------------------------------------------+
Обратите внимание на последнюю строчку в советнике, которая выводит текущее время, когда был установлен факт синхронизации:
Print("Бары синхронизировались в ",TimeToString(TimeTradeServer(),TIME_SECONDS));
Для вывода текущего времени мы использовали функцию TimeTradeServer(), а не TimeCurrent(). Дело в том, что функция TimeCurrent() возвращает время последнего тика, которое никак не изменилось после использования Sleep(). Запустите советник в режиме "Только цены открытия" и увидите сообщения о синхронизации баров.
Используйте функцию TimeTradeServer() вместо TimeCurrent(), если требуется получить текущее серверное время, а не время поступления последнего тика.
Есть и другой способ синхронизации баров - с помощью таймера. Пример такого эксперта Synchronize_Bars_Use_OnTimer.mq5 приложен к статье.
Функция IndicatorRelease() в тестере
После окончания одиночного тестирования автоматически открывается график инструмента, на котором отображаются совершенные сделки и индикаторы, которые использовались в эксперте. Это помогает визуально проверить моменты входа и выхода, а также сопоставить их со значениями индикаторов.
Важно: индикаторы, отображаемые на автоматически открытом после завершения тестирования графике, рассчитываются заново уже после окончания тестирования. Даже если эти индикаторы использовались в тестируемом эксперте.
Но в некоторых случаях программисту может понадобиться скрыть информацию о том, какие индикаторы задействованы в торговом алгоритме. Например, код эксперта сдается в аренду или продается в виде исполняемого файла без предоставления исходного кода. Для этих целей подойдет функция IndicatorRelease().
Если в терминале задан шаблон с названием tester.tpl в каталоге /profiles/templates клиентского терминала, то именно он будет применен к открываемому графику. При его отсутствии применяется шаблон по умолчанию (default.tpl).
Функция IndicatorRelease() изначально предназначена для освобождения расчетной части индикатора, если он больше не нужен. Это позволяет экономить как память, так и ресурсы процессора, потому что каждый тик вызывает расчет индикатора. Второе ее предназначение - запретить показ индикатора на графике тестирования после окончания одиночного прогона.
Чтобы не показывать индикатор на графике по окончании тестирования, сделайте вызов IndicatorRelease() с хэндлом индикатора в обработчике OnDeinit(). Функция OnDeinit() всегда вызывается после завершения и перед показом графика тестирования.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- bool hidden=IndicatorRelease(handle_ind); if(hidden) Print("IndicatorRelease() выполнена успешно"); else Print("IndicatorRelease() вернула false. Код ошибки ",GetLastError()); }
Обработка событий в тестере
Наличие обработчика OnTick() в эксперте не является обязательным для того, чтобы его можно было подвергнуть проверке на исторических данных в тестере терминала MetaTrader 5. Достаточно того, чтобы в советнике была хотя бы одна функция-обработчик из перечисленных:
- OnTick() - обработчик события прихода нового тика;
- OnTrade() - обработчик торгового события;
- OnTimer() - обработчик события прихода сигнала от таймера;
- OnChartEvent() - обработчик пользовательских событий.
При тестировании в эксперте можно обрабатывать пользовательские события с помощью функции OnChartEvent(), но в индикаторах эта функция в тестере не вызывается. Даже если индикатор имеет обработчик OnChartEvent() и этот индикатор используется в тестируемом эксперте, то сам индикатор не будет получать никаких пользовательских событий.
Индикатор при тестировании может генерировать пользовательские события с помощью функции EventChartCustom(), а советник может обрабатывать это событие в OnChartEvent().
Агенты тестирования
Тестирование в клиентском терминале MetaTrader 5 осуществляется с помощью агентов тестирования. Локальные агенты создаются и подключаются автоматически. Количество локальных объектов по умолчанию соответствует количеству ядер на компьютере.
У каждого агента тестирования своя копия глобальных переменных, которая никак не связана с клиентским терминалом. Сам терминал является диспетчером, который раздает задачи локальным и удаленным агентам. После выполнения очередного задания по тестированию советника с заданными параметрами агент возвращает терминалу результаты. При одиночном тестировании используется только один агент.
Агент хранит полученную от терминала историю в отдельных папках по имени инструмента, то есть история для EURUSD хранится в папке с именем EURUSD. Кроме того история инструментов разделяется по источникам. Структура для хранения истории выглядит таким образом:
Например, история по EURUSD с сервера MetaQuotes-Demo может храниться в папке каталог_тестера\Agent-127.0.0.1-3000\bases\MetaQuotes-Demo\EURUSD.
Локальный агент после окончания тестирования находится в режиме ожидания следующей задачи в течение 5 минут, чтобы не терять время на запуск при следующих вызовах. Только по истечении ожидания локальный агент прекращает свою работу и выгружается из памяти компьютера.
При досрочном завершении тестирования со стороны пользователя (кнопка "Отмена"), а также при закрытии клиентского терминала все локальные агенты тут же прекращают свою работу и выгружаются из памяти.
Обмен данными между терминалом и агентом
При запуске тестирования терминал готовит для отправки агенту несколько блоков параметров:- Входные параметры тестирования (режим моделирования, интервал тестирования, инструмент, критерий оптимизации и т.д.)
- Список выбранных в "Обзоре рынка" инструментов
- Спецификация тестируемого инструмента (размер контракта, допустимые отступы от рынка для установки StopLoss и Takeprofit, и т.д)
- Тестируемый эксперт и значения его входных параметров
- Информация о дополнительных файлах (библиотеки, индикаторы, файлы данных - #property tester_...)
tester_indicator
Имя пользовательского индикатора в формате "имя_индикатора.ex5". Необходимые для тестирования индикаторы определяются автоматически из вызова функций iCustom(), если соответствующий параметр задан константной строкой. Для остальных случаев (использование функции IndicatorCreate() или использование неконстантной строки в параметре, задающем имя индикатора) необходимо данное свойство
tester_file
Имя файла для тестера с указанием расширения, заключенное в двойные кавычки (как константная строка). Указанный файл будет передан тестеру в работу. Входные файлы для тестирования, если необходимы, должны указываться всегда
tester_library
Имя библиотеки с расширением, заключенное в двойные кавычки. Библиотека может быть как с расширением dll, так и с расширением ex5. Необходимые для тестирования библиотеки определяются автоматически. Однако, если какая-либо библиотека используется пользовательским индикатором, то необходимо использовать данное свойство
Для каждого блока параметров создается цифровой отпечаток в виде MD5-хэша, который и посылается агенту. MD5-хэш является уникальным для каждого набора, его объем во много раз меньше объема информации, на основе которой он вычислен.
Агент получает хэши блоков и с сравнивает с теми, что он уже хранит у себя. Если отпечаток данного блока параметров отсутствует у агента, или присланный хэш отличается от имеющегося, то агент запрашивает сам блок параметров. Таким образом уменьшается трафик между терминалом и агентом.
После проведения тестирования агент возвращает терминалу все результаты прогона, показываемые во вкладках "Результаты тестирования" и "Результаты оптимизации": полученная прибыль, количество сделок, коэффициент Шарпа, результат функции OnTester() и т.д.
При оптимизации терминал раздает агентам задачи на проведение тестирования небольшими пакетами, в каждом пакете находится несколько заданий (каждое задание означает одиночное тестирование с набором входных параметров). Это уменьшает время обмена между терминалом и агентом.
Агенты никогда не записывают на жесткий диск полученные от терминала EX5-файлы (эксперт, индикаторы, библиотеки и т.д.) из соображений безопасности, чтобы на компьютере с установленным агентом нельзя было воспользоваться присланными данным. Все остальные файлы, в том числе DLL, записываются в "песочницу". В удалённых агентах нельзя тестировать экспертов с использованием DLL.
Результаты тестирования складываются терминалом в специальный кэш результатов (результирующий кэш) для последующего быстрого доступа к ним при необходимости. Для каждого набора параметров терминал ищет в результирующем кэше уже готовые результаты от предыдущих запусков для исключения повторных запусков. Если результат с таким набором параметров не найден, агенту отдается задание на проведение тестирования.
Весь трафик между терминалом и агентами шифруется.
Использование общей папки всех клиентских терминалов
Все тестерные агенты изолированы друг от друга и от клиентского терминала: у каждого агента есть собственная папка, в которую записываются логи агента. Кроме того, все файловые операции при тестирования агента происходят в папке имя_агента/MQL5/Files. Однако можно реализовать взаимодействие между локальными агентами и клиентским терминалом через общую папку всех клиентских терминалов, если при открытии файла указать флаг FILE_COMMON:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- общая папка всех клиентских терминалов common_folder=TerminalInfoString(TERMINAL_COMMONDATA_PATH); //--- выведем имя этой папки PrintFormat("Откроем файл в общей папке клиентских терминалов %s", common_folder); //--- откроем файл в общей папке (указан флаг FILE_COMMON) handle=FileOpen(filename,FILE_WRITE|FILE_READ|FILE_COMMON); ... дальнейшие действия //--- return(0); }
Использование DLL
Для ускорения оптимизации можно использовать не только локальные, но и удаленные агенты. При этом есть некоторые ограничения для удаленных агентов. Во-первых, удаленные агенты не выводят в свои логи результаты выполнения функции Print(), сообщения об открытии/закрытии позиций. Выводится в лог минимум информации чтобы неправильно написанные эксперты не забили сообщениями жесткий диск компьютера, на котором работает удаленный агент.
Второе ограничение - запрет на использование DLL при тестировании экспертов. Вызовы DLL безусловно запрещены на удалённых агентах из соображений безопасности. На локальных агентах вызовы dll в тестируемых экспертах разрешены только в том случае, если установлено соответствующее разрешение "Разрешить импорт DLL".
Рис. 9. Опция "Разрешить импорт DLL" в mql5-программах.
Важно: при использовании полученных со стороны советников (скриптов, индикаторов), которые требуют разрешить вызовы DLL, вы должны осознавать весь риск, который принимаете на себя в случае разрешения этой опции в настройках терминала. Независимо от того, как будет использован советник - для тестирования или для запуска на графике.
Заключение
В статье рассмотрены базовые основы, знание которых поможет быстро освоить тестирование экспертов в клиентском терминале MetaTrader 5:
- три режима генерации тиков;
- расчет значений индикаторов при тестировании;
- мультивалютное тестирование;
- моделирование времени при тестировании;
- работа функций OnTimer(), OnSleep() и IndicatorRelease() в тестере;
- работа агентов тестирования при вызовах DLL;
- использование общей папки всех клиентских терминалов;
- синхронизация баров в режиме "Только цены открытия";
- обработка событий.
Основная задача тестера в клиентском терминале - обеспечение требуемой достоверности данных при минимуме усилий со стороны программиста на MQL5. Разработчики сделали многое, чтобы вам не приходилось переписывать свой код только для того, чтобы проверить работу торговой стратегии на исторических данных. Достаточно знать основы тестирования и правильно написанный советник будет одинаково работать как в тестере, так и в режиме онлайн на графике.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
***
На SSD нужно чтобы стояла вся операционка или можно как-то на SSD поселить только агентов?
На SSD нужно чтобы стояла вся операционка или можно как-то на SSD поселить только агентов?
Все на SSD, они же сейчас стоят копейки.
Почему бы не включить это в стандартную конфигурацию Оптимизатора?
У многих бы компы не зависали, меньше жалоб.Все просто:
Спасибо