Видеоуроки по программированию на MQL5 - страница 7

 

MQL5 Первый робот. Версия для тестера стратегий


MQL5 Первый робот. Версия для тестера стратегий

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

В принципе, базовая версия вполне себе торгует на реальном счете и оптимизируется в тестере стратегий терминала MetaTrader 5. Но и для тестера, и для торговли программу можно дополнить полезным функционалом. Однако функционал, полезный при тестировании, не будет работать в торговле, и наоборот.

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

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

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

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

Теперь подготовимся к работе с балансом. Зафиксируем сумму стартового депозита, сохраняя значение в отдельную глобальную переменную. Для этого используем функцию AccountInfoDouble, которая вернет нам значение депозита на старте тестирования.

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

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

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

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

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

if (currentBalance > startBalance * 1.05) {
    // снимаем разницу между текущим балансом и стартовым
}
Кстати, при работе со скользящими средними можно задать Start, Step и Stop одинаковыми для всех машек, чтобы оптимизация была более эффективной.

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

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

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

Если вы хотите, можете поискать примеры реализации самых разных сценариев оптимизации в кодовой базе сайта mql5.com. А я перехожу к функции OnTester.

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

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

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

Затем прописываем два варианта в перечислении MyOnTesterEnum: OnTest и OnTest2.

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

И наконец, в функции OnTester прописываем условное ветвление при помощи оператора if. Если в будущем вам понадобится добавить еще какой-то максимум пользовательского критерия, просто добавьте его в перечисление MyOnTesterEnum и в функцию перед условием.

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

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

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

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


Содержание, они же тайм-коды:

00:00:00 - Приветствие, тема видео и новый файл

00:01:57 - Нюансы Alert и Print в тестировании

00:02:22 - Проверка режима работы программы

00:03:43 - Работа с балансом в тестере стратегий

00:08:13 - Функция OnInit() и сценарии оптимизации

00:10:50 - Функция OnTester() и пользовательские критерии оптимизации

00:14:43 - Заключение и план на следующее видео

MQL5 Первый робот. Версия для тестера стратегий
MQL5 Первый робот. Версия для тестера стратегий
  • 2022.12.30
  • www.youtube.com
Продолжаем изучать язык программирования MQL5 на примере написания торгового эксперта.В этом видео-уроке мы, на основе базовой версии робота, создадим его ва...
 

MQL5 Структуры. Тип данных struct


MQL5 Структуры. Тип данных struct

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

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

Несмотря на то, что структуры уже повсеместно стали членами концепции ООП, до сих пор можно наткнуться на спор о том, являются ли структуры частью ООП или нет. С одной стороны, концепция структуры появилась до концепции ООП, и лишь со временем стали её частью, и то не совсем равноправным. С другой стороны, при создании самой концепции объектно-ориентированного программирования её авторы опирались именно на концепцию структуры, будучи сторонниками позиции "не нашим не вашим". Я исключительно для себя определяю структуру как переходную концепцию от процедурного программирования, на основе которого построен базовый курс, к объектно-ориентированному, к которому я планирую в будущем вас привести. Кстати, если вы со мной не согласны, комментарии для вас открыты.

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

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

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

Далее идут фигурные скобки, в которых будет описано содержимое структуры.

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

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

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

От чего так происходит? Давайте вернемся к нашей переменной и еще раз внимательно посмотрим на её инициализацию. 1) Ключевое слово "double" говорит компилятору о том, какого типа объект в памяти должен быть создан. Соответственно, будет выделен блок памяти, и этому блоку будет присвоен адрес в оперативной памяти. 2) Имя переменной - это удобный идентификатор для только что выделенного адреса в памяти. Каждый раз, когда компилятор натыкается в коде на этот идентификатор, он понимает, что должен обратиться по соответствующему адресу в памяти.

И последнее - операции присвоения. Мы помещаем в память, по указанному адресу, значение 0. В свою очередь, адрес памяти известен через имя переменной, то есть сначала выделяется блок памяти нужного размера, затем этот блок памяти связывается с программой посредством идентификатора, и только после этого в него заносятся некие значения, к которым теперь можно обращаться, изменять их или просто считывать. Но в первую очередь - выделение памяти, что логично. Иначе, откуда программа узнает, где ей брать требуемое значение и куда класть новое? Но откуда программа знает, сколько памяти потребуется для переменной типа double? Все просто. Я уже говорил ранее, что описание базовых типов данных встроено в синтаксис MQL5 и в числе прочего там описано, сколько памяти требуется данным этого типа. Так что, когда компилятор встречает в коде ключевое слово "double", он сразу знает, что от него требуется.

Однако структура это пользовательский тип данных. Если как-то изменить описание базовых типов мы не можем, то пользовательские типы мы описываем самостоятельно. Когда компилятор встречает в коде ключевое слово "struct", он понимает, что далее последует описание нового типа данных. После ключевого слова следует идентификатор нового типа данных, и в фигурных скобках мы уже описываем конкретные свойства нашего нового типа. Точка с запятой говорит компилятору, что описание нового типа данных окончено. Теперь идентификатор "teststruct" зарезервирован в программе за нашим новым типом данных, а конкретно за структурой, состав которой входят три переменные типа int, double и string.

И вот теперь, встретив в коде программы идентификатор "teststruct", компилятор уже будет знать, сколько памяти под него потребуется. Остается только объявить переменную этого типа, пусть будет просто "a".

Теперь нужно сделать небольшое отступление, а именно проговорить терминологию. Беда в том, что слово "объект" в программировании самое перегруженное. Это намек на перегрузку функций. Так вот, описание структуры при помощи ключевого слова "struct" можно уточнить как "объект структуры". Хотя можно встретить формулировки "создание объекта" или "описание объекта-структуры" или "абстрактное описание объекта/структуры". Речь всегда идет об одном - в коде объявлен новый объект типа структура. А когда мы создаем конкретный экземпляр нашей структуры, это называется "объявлением экземпляра объекта" или "создание экземпляра" или, в нашем случае, "объявление экземпляра структуры teststruct".

Короче, сама структура это объект, и в программе создаются экземпляры этого объекта. Если снова посмотреть на переменные, то аналогия видна сразу. Наша переменная "value" типа "double" является экземпляром объекта "double", который в свою очередь объявлен в синтаксисе MQL5.

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

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

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

Более того, если мы имеем дело с разными экземплярами одного типа, то есть одной структуры, мы можем копировать все содержимое одного экземпляра в другой простейшей операции присвоения. Давайте результаты этой операции тоже выведем в журнал.

А что если нам нужен массив, состоящий из экземпляров структуры? Нет ничего проще. Объявляется массив экземпляров объекта точно так же, как и массив переменных. Этот массив может быть как статическим, так и динамическим. Пока что сделаем статический массив на два элемента и скопируем в него экземпляры "A" и "B". Доступ к элементам массива экземпляров осуществляется примерно так же, как и к массивам переменных, через оператор индексации - квадратные скобки. Порядок записи такой: имя массива экземпляров, затем индекс нужного элемента в квадратных скобках, и затем, после закрывающей квадратные скобки, ставим точку - оператор доступа к членам и указываем нужное поле по его имени. Наглядно продемонстрируем это выводом в журнал.

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

Пока что прошу вас лайкнуть это видео, написать комментарий для продвижения канала в алгоритмах YouTube и ударить в колокол на всякий случай. До новых встреч! Всем пока!


Содержание, они же тайм-коды:

00:00:00 - Приветствие и причины появления видео

00:01:45 - О пользовательских и базовых типах данных

00:04:17 - Определение Структуры

00:05:38 - Объявление Структуры и ключевое слово struct

00:07:01 - Экземпляр Структуры и разница между объявлением Структуры и её экземпляров

00:10:26 - Терминология

00:12:14 - Доступ к переменным-членам экземпляра Структуры

00:13:09 - Копирование экземпляров Структуры через присвоение

00:13:29 - Массив экземпляров Структуры и взаимодействие с его элементами

00:14:27 - Область видимости экземпляров Структуры и эпилог

MQL5 Структуры. Тип данных struct
MQL5 Структуры. Тип данных struct
  • 2023.04.11
  • www.youtube.com
Продолжаем изучать язык программирования MQL5. В этом видео придется отвлечься на такую штуку, как Структура. Она же тип данных struct. Но только на самые ба...
 

MQL5 Первый робот. Мультивалютная версия. Часть 1


MQL5 Первый робот. Мультивалютная версия. Часть 1

Всем привет! Продолжаем изучать язык программирования mql5 на примере написания торгового эксперта на базе тестовой версии, которая у нас уже есть, а также вооружившись знанием о структурах, полученных на предыдущем уроке. Сегодня мы сделаем мультивалютный вариант нашего первого робота. Это видео будет аж в трех частях, такое решение обосновано тем, что организацию мультивалютной торговли можно разделить на три глобальных этапа. И мне бы очень хотелось подробно разобрать каждый шаг предстоящей работы. С одной стороны, а с другой мне не хочется пытать вас часовым или даже больше видеоуроком. Думаю, стоит начать с постановки глобальной задачи и в первую очередь ответить на вопрос "Зачем все это нужно?"

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

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

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

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

Имеется два варианта:

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

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

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

Далее можно просто скопировать нужный Input переменные, естественно, подправив их объявление внутри структуры "settings". Нам понадобятся количество сигнальных свечей, объем входа, targetprofit и outcommition. Не забудем и про переключатель использования фильтра по стохастику, их назначение не изменяется. Так что подробно останавливаться на этом смысла нет.

Итого у нас получилось 6 переменных. А теперь нужно добавить еще три, и на них мы остановимся чуть подробнее. Первое, типа "int", по имени "handle", для хранения хендла стохастика по указанному символу. Если, конечно, он потребуется. Без этой переменной наш робот будет использовать стохастик с того графика, на котором он установлен, игнорируя все остальные торговые инструменты. Вторая, тип "DateTime", имя "T", нужна для контроля открытия новой свечи на конкретном торговом инструменте. Без нее опять же все символы будут ориентироваться на тот график, на который будет установлен робот. И третья переменная будет типа "Enum", имя "TF". Это нужно, чтобы организовать для каждого символа свой период графика, ведь, согласитесь, никто не может гарантировать, что по результатам оптимизации у всех символов будет одинаковый таймфрейм.

Что ж, структура готова. Однако, когда появятся ее экземпляры, их поля нужно будет чем-то заполнять. К тому же, мы уже определились с тем, что настройки для всех символов будет задавать пользователь. Мы подумали, и я решил, что символов, на которых одновременно будет торговать наш робот, будет ровно три. Поэтому нам нужны три группы Input переменных, для каждого планируемого символа, состав этих групп должен точно соответствовать составу структуры "settings". Так что воспользуемся самым распространенным методом программирования.

Соответственно, отредактируем объявление во всех наборах и установим им значение по умолчанию. А попутно переименуем переменные в каждом наборе, добавив к именам переменных 0, 1 или 2, чтобы как-то отделить один набор от другого. Значение переменных я беру с потолка, но с таким расчетом, чтобы в одном прогоне в тестере стратегий наш робот явил себя как можно более разностороннее. Не стоит брать их за основу для реальной торговли.

Теперь можно закомментировать те Input переменные, чьи копии мы создали внутри структуры "Slim settings". Также закомментируем глобальную переменную "T", ведь у нас будет своя для каждого символа. Переменную "Stop handle" тоже долой, по той же причине. А в функции он и нет, баним все, что касается сигнальных свечей, лота, хендла стохастика и "Тarget профита". Теперь это не глобальные, а локальные параметры, и контроль за ними будет осуществляться в других местах программы.

Если теперь попытаться собрать программу, то получим много ошибок. Это произошло из-за того, что в коде появилась куча переменных, объявления которых уже закомментированы, и эти ошибки проживут с нами вплоть до третьей части этого урока. Но мы еще с первой не закончили. Кстати, не лишним будет запомнить количество ошибок на данном этапе, и если на каком-то этапе работы это количество увеличится, значит, мы таки добавили новые. Собственно, входные параметры робота готовы, остался маленький штришок. У нас есть только описание структуры "settings", однако, как нам уже известно, этого мало для того, чтобы вся проделанная нами сегодня работа обрела смысл. Нам нужны экземпляры этой структуры. Так что на глобальном уровне объявляем массив экземпляров структуры на три элемента. Расторгуем одновременно на трех символах. Нам нужны три. Возможно, вы уже догадались, для чего мы объявили их в виде массива, если так - пишите в комментариях, а если нет - наберитесь терпения. До новых встреч!


Содержание, оно же тайм-коды:

00:00:00 - Приветствие и вводная

00:00:44
- Постановка глобальной задачи

00:03:19
- Разделение внешних переменных на общие для всех символов и уникальные для каждого из них

00:05:03
- Структурирование уникальных для каждого символа внешних переменных

00:07:16 - Организация пользовательского ввода для уникальных внешних переменных

00:09:41
- Основа для ввода пользовательских настроек в контекст программы

MQL5 Первый робот. Мультивалютная версия. Часть 1
MQL5 Первый робот. Мультивалютная версия. Часть 1
  • 2023.04.14
  • www.youtube.com
Продолжаем изучать язык программирования MQL5 на примере написания торгового эксперта.Это первый видео-урок из трех, в котором мы на основе тестовой версии н...
 

MQL5 Первый робот. Мультивалютная версия. Часть 2


MQL5 Первый робот. Мультивалютная версия. Часть 2

Всем привет! Продолжаем написание мультивалютной версии нашего первого робота на MQL5. В прошлой части мы организовали внешние переменные, при помощи которых пользователь сможет настроить торговый алгоритм отдельно для разных торговых инструментов. Описали структуру "Slim settings" и объявили массив экземпляров этой структуры. Думаю, вы уже догадались, что каждый элемент массива экземпляров структуры "settings" будет отвечать за настройки нашего торгового алгоритма для определенного символа. И сегодня мы пропишем механизм заполнения массива "settings", а также рассмотрим все связанные с этим нюансы, в том числе возможность расширения числа участвующих в тестировании торговых инструментов, так и сокращение этого числа.

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

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

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

css

for(int i=0; i<SymbolS_TOTAL; i++) Symbols[i].symbol = "";

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

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

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

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

Будем использовать цикл "for". Вот с таким условием:

if(SymbolTotal(true) > 0)
{
    for(int i=0; i<SymbolsTotal(true); i++)
    {
        Symbols[i].symbol = SymbolName(i, true);
    }
}
Функция "SymbolTotal(true)" возвращает размер списка доступных в терминале символов. А единственный аргумент, типа "bool", имя которому "selected", как раз и отвечает за то, как формируется список доступных в терминале символов. Если "selected" равно "true", список будет составлен только из тех символов, которые выбраны в окне обзор рынка (или "Market Watch" по-английски). Иначе, список из всех символов.

Крайне желательно, чтобы единственный аргумент функции "SymbolTotal" и второй аргумент функции "SymbolName" совпадали, иначе может выйти конфуз. Если значение переменной "symbol[0]" не совпадает со значением, возвращаемым функцией "SymbolName", переходим к следующему символу из списка символов "symbol[100]".

Если же совпадают, нужно кое-что еще. Дело в том, что в терминале MT5 нельзя проводить торговую операцию на торговых инструментах, не выбранных в окне обзор рынка. Чтобы не напороться на неожиданности, нужно принудительно добавить требуемый символ в окно обзор рынка. Делается это функцией "SymbolSelect". Эта функция принимает два аргумента: первый типа "string", это имя символа, который будет добавлен или убран из окна обзор рынка, второй аргумент типа "bool" говорит функции, что нужно сделать с символом. Если значение "true", символ нужно добавить в обзор рынка, если "false" - символ нужно оттуда убрать.

Помимо поля "символ" в экземплярах структуры "Slim settings" есть и другие поля. Пришла пора взяться за них. Сделаем мы это в отдельной функции, и чтобы не плодить лишние сущности, в функции "OnInit" вызывать нашу новую функцию будем из функции "SetSymbols" в самом её конце.

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

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

if(Symbols[i].symbol != "")
{
    // манипуляции с элементом массива
}
Если же нет, то этот элемент массива "Symbols" пропускаем.

Теперь повторяем эти манипуляции для элементов массива "Symbols" с индексами 1 и 2.

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

Для двух предыдущих функций нам пришлось расписывать каждый шаг вручную. Это обусловлено тем, что значение, с которыми эти функции имели дело, задавались пользователями. А у пользовательского ввода есть одно неприятное свойство - он не имеет контекста. Другими словами, для программы это просто некие значения, но как их применить, она не знает, и нам, как программистам, нужно интегрировать эти значения в контекст программы. И сделать это нужно для каждого такого значения по отдельности. Этим мы занимались в функциях "SymbolTotal" и "SymbolExist". По результатам их работы, значения, которые передал пользователь в программу, обрели контекст, став на положенные им места в массиве "Symbols" или были отброшены по причине отсутствия для них места в контексте логики самой программы.

Однако в момент вызова функции "SetHandals" пользовательский ввод уже обработан. А значит, дальше можно опираться на уже описанный контекст. Так что мы воспользуемся циклом, в котором последовательно обработаем все элементы массива "Symbols", и если поле "символ" не пустое и поле "useStochastic" имеет значение "true", значит, в поле "handle" нужно присвоить handle индикатора стохастик. Естественно, все перечисленные поля относятся к элементу массива с индексом "i".

Таким образом, мы заполнили все поля элементов массива "Symbols". Теперь наш робот готов к работе с различными торговыми инструментами.

Если же это условие не выполняется, делать ничего не нужно и поле "handle" остается пустым. А вот что делать, если "handle" сгенерировать не удалось? Тут есть аж целых три варианта:

  1. Исключить данный символ из тестирования. Для этого полю "символ" элемента массива "Symbols" с индексом "i" присваиваем значение пустой строки. И в журнал сообщения о том, что "handle" стохастика по такому-то символу недействителен. Теперь у нас в тесте на один символ меньше, чем планировалось изначально.

  2. Отказаться от индикаторного фильтра на этом символе. Для этого просто изменим значение поля "use100handle" для элемента массива "Symbols" с индексом "i" на "false". И не забываем про сообщения в журнал.

  3. Прервать тестирование полностью. Тут чуть сложнее, сообщение в журнал все равно нужно. А вот дальше мы в цикле всем элементам массива "simpals" в поле "handle" были присвоены пустые строки, как в самом начале функции, и воспользуемся оператором "return", тем самым досрочно прервав работу функции.

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

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

Возвращать она будет значение типа int, называться будет "CountSymbols", и не будет иметь формальных параметров. В теле функции объявляем переменную "count", присваиваем ей нуль и возвращаем из функции эту переменную. А между этими двумя событиями перебираем все элементы массива "Symbols" в цикле, и если поле "символ" элемента с индексом "i" не пустая строка, увеличиваем значение переменной "count" на единицу.

Теперь, если функция "CountSymbols" возвращает 0, значит, по каким-то причинам у робота нет настроек ни под один торговый инструмент. Следовательно, дальнейшая работа программы бессмысленна.

Возвращаемся в функцию OnInit и после вызова функции Windows, которая в свою очередь вызовет остальные написанные сегодня функции, сразу же вызываем функцию CountSymbols и сохраняем ее значение в переменную "Count". Если значение переменной равно нулю, значит у нас нет ни одного символа для тестирования. Следовательно, продолжать дальше незачем. Сообщаем об этом пользователю и завершаем инициализацию. А если это условие не выполнено, то сообщим пользователю, сколько символов из максимума возможных будет участвовать в тестировании.

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

Также стоит разобрать еще один вариант некорректного пользовательского ввода, довольно экзотический, а именно случай, когда пользователь в настройках два раза указет один и тот же символ. Или, вы хотите сказать, что они попытались бы этого сделать? Поскольку имеем дело только с внешними переменными, отвечающими за именно символы, было бы логично реализовать этот механизм в функции SetSymbols, причем после вызова функции CountSymbols и перед вызовом функции SetSymbolsSettings. Ведь если тестировать нечего, то и продолжать дальше незачем. А задавать прочие настройки логично уже полностью проверенному массиву.

И тут нам снова понадобится цикл, который последовательно просмотрит массив символс. Если поле "символ" было элемента А и массива символс является пустой строкой, то переходим к следующему элементу массива singles. Если это не выполняется, значит, нужно убедиться, что во всех следующих за ним элементах массива Symbols, то есть тех, у которых индекс больше текущего значения переменной I, значение поля "символ" не повторяется. Для этого запустим еще один цикл, только переменную отвечающую за индексацию назовем "Z". Чтобы избежать конфликта имен, стартовое значение переменной Z будет значением I увеличенное на 1, то есть следующий за индекс массива символс, и сделаем так, чтобы начиная с индекса Z, наш внутренний цикл прошелся по всем элементам массива singles. Если цикл Z наткнется на совпадающие значения полей "символ" в элементе и элементе Z массива символс, присваиваем полю "символ" элемента массива Z пустую строку.

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

Теперь о количестве тестируемых торговых инструментов, они же символы. Если нужно протестировать эксперта на меньшем количестве символов, чем предусмотрено алгоритмом, достаточно просто неправильно указать их имена во входящих настройках. А вот если вам понадобится расширить количество символов в алгоритме, придется поработать. В первую очередь, придется размножить наборы Input переменных столько раз, сколько дополнительных символов потребуется добавить. Задач новый размер массива Symbols. Далее, в функции SetSymbols нужно добавить блоки для каждого дополнительного элемента в массиве Symbols, также нужно добавить соответствующие блоки кода в функции SetSymbolsSettings. Благо, все добавляемые блоки кода будут идентичными уже существующими, так что самый распространенный метод программирования вам в помощь. Главное, не забудьте про имена переменных и индексы массива.

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

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

Содержание, оно же тайм-коды:

00:00:00 - Краткое содержание предыдущей части

00:00:23 - План урока

00:01:15 - Обработка имен торговых инструментов

00:02:01 - Оговорка о функции ZeroMemory()

00:02:21 - Начальные значения имен торговых инструментов

00:02:43
- Проверка терминала на наличие требуемых торговых инструментов

00:03:26
- Описание функции SymbolsTotal()

00:04:09 - Описание функции SymbolName()

00:05:02
- Добавление символа в Обзор рынка и функция SymbolSelect()

00:06:35 - Альтернативный метод проверки наличия символа и описание функции SymbolExsist()

00:09:17 - Установка настроек алгоритма для проверенных символов

00:10:45 - Формируем индикаторные хендлы

00:12:38 - Варианты дествий на случай неудачи формирования хендла

00:14:12
- Прерывание инициализации. Причины и противодействие

00:16:50 - Защита от дублирования тестируемых символов

00:19:19 - Уменьшение и увеличение кол-ва символов в тесте

MQL5 Первый робот. Мультивалютная версия. Часть 2
MQL5 Первый робот. Мультивалютная версия. Часть 2
  • 2023.04.18
  • www.youtube.com
Продолжаем изучать язык программирования MQL5 на примере написания торгового эксперта.Продолжение создания мультивалютной версии нашего первого робота.В этом...
 

MQL5 Первый робот. Мультивалютная версия. Часть 3


MQL5 Первый робот. Мультивалютная версия. Часть 3

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

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

Для начала объединим вышеописанный функционал с функцией OnTick, используя цикл для доступа к массиву simples. Таким образом, код будет выполняться столько раз, сколько есть элементов в массиве simples.

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

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

Теперь количество ошибок значительно уменьшилось. Остается только вопрос с переменной T, которая отвечает за определение новой свечи. Теперь для каждого символа у нас есть своя копия переменной T, что требует постоянной перезаписи значения.

Этот момент решается довольно просто. Вместо использования глобальной переменной T, объявим ее как локальную в функции OnTick. Таким образом, для каждого символа у нас будет своя копия переменной T, что позволит избежать проблем с перезаписью значения и использовать ее в торговой логике каждого символа независимо.

Те, кто знаком с C++, уже знают решение - объявить ссылку на переменную типа DateTime с именем T и через неё взаимодействовать со значением поля T конкретного экземпляра структуры. Но в MQL5 нельзя объявить ссылку на произвольную переменную, и я, если честно, не совсем понимаю почему. Ну ладно, указатели, но ссылки проще и безопаснее. Горевать по этому поводу можно сколько угодно, но этим делом не поможешь, так что придется напрямую использовать поле T элемента с индексом i массива экземпляров.

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

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

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

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

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

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

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

Или, например, Notepad++. Не забываем избавиться от встроенных констант. В остальном, по ходу семантических ошибок, мы избавились. Но по ходу нажили еще одну синтаксическую ошибку. Не верите? Поставьте на паузу и внимательно изучите код на экране.

Все дело в функции SetStopLevels. Предопределенные константы - глобальные по своей сути, и их можно использовать в рамках программы как глобальные переменные, не обращая внимания на область видимости. Но мы заменили глобальные переменные на переменные Simple и TF, которые были объявлены в функции OnTick. Следовательно, это локальные переменные функции OnTick, и функция SetStopLevels о них не знает. Нужно ей об этих переменных сообщить, добавим два формальных параметра Simple и TF типа string, и соответственно остается лишь подправить вызовы этой функции из функций OnTick.

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

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

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

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

А еще, некорректное значение объема входа для одного из символов - зря, что ли, прописывали проверку корректности указанного пользователем лота? Можно запустить и сразу ставить тестирование на паузу.

Обратим внимание на некоторые моменты:

  1. Окно "Обзор рынка" - его занимают 4 символа: Франк (он тут в обязательном порядке, ведь на его график установлен робот), евро, фунт и йена. Присутствуют открытые окна графиков для всех указанных символов. Что характерно - нужные таймфреймы к ним уже применены по умолчанию.

  2. Открыто окно графика символа евро с использованием таймфрейма, который указан настройками тестирования.

  3. Обратите внимание на объемы входа, они скорректированы, значит и этот механизм работает, как нужно.

  4. Насчет времени входа на таймфреймах выше одного часа: у моего брокера торговая неделя начинается в 0 часов 0 минут понедельника, а торговый день заканчивается в 23:59 и новый начинается в 0 часов следующих суток.

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

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

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

Исходники написанного нами робота доступны по ссылке в описании под видео. Там же имеются возможности оказать материальную помощь каналу в эти нелегкие времена. До новых встреч! Всем пока!

Содержание, оно же тайм-коды:

00:00:00 - Приветствие и вступление

00:00:48
- Перебор массива с настройками

00:01:30
- "Отбраковка" неправильных символов

00:02:25
- Восстанавливаем переменные в главной функции

00:03:35
- Контроль времени открытия новой свечи и связанные с этим проблемы

00:06:54
- Отвязываем робот от текущего графика

00:11:42 - Правки вспомогательных функций

00:12:50 - Настройки тестирования

00:14:09 - Тест в штатном режиме

00:18:36 - Тест с неправильно указанным символом

00:19:18 - Тест с дублированием символа

00:20:34 - Чем чреваты неоднозначности в программе

00:23:27 - Почему не стоит торговать мультивалютой

00:26:39 - План на следующее видео

MQL5 Первый робот. Мультивалютная версия. Часть 3
MQL5 Первый робот. Мультивалютная версия. Часть 3
  • 2023.07.23
  • www.youtube.com
Продолжаем изучать язык программирования MQL5 на примере написания торгового эксперта.Это видео завершит многострадальный цикл по написанию мультивалютной ве...
 

MQL5 Приёмы и примеры. Анонс


MQL5 Приёмы и примеры. Анонс

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

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

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

Кроме того, откроется новый плейлист видео о сравнении вещественных чисел, подводных камнях этого действа и приемах обхода этих препятствий.

Жду ваши отзывы в комментариях! Там же оставляйте ваши пожелания на темы следующих видео в новом плейлисте. Обязательно подписывайтесь на канал, ставьте палец вверх или звоните в колокол, чтобы не пропустить новые видео-уроки.

До новых встреч! Всем пока!

MQL5 Приёмы и примеры. Анонс
MQL5 Приёмы и примеры. Анонс
  • 2022.02.13
  • www.youtube.com
Это анонс нового плей-листа на канале. А также планы на ближайшее обозримое будущее.Уголок компетентного дилетанта в Телеграмм: https://t.me/+BuMFJEjKclkzMzY...
 

MQL5 Сравниваем вещественные числа


MQL5 Сравниваем вещественные числа

Всем привет! В этом видео-уроке рассмотрим сравнение вещественных чисел в MQL5 и разберемся, почему сравнивать такие числа на равенство или неравенство может быть проблематично, а также как с этим бороться.

Для начала, создадим переменную типа double, равную 0.25, и увеличим ее в цикле на 0.01 с выводом результатов каждой итерации в журнал. Но если запустить этот код на выполнение, результат будет несколько неожиданным - в какой-то момент появляются неточности в 16 или 17 знаках после точки, хотя мы затрагивали только сотые или два знака после точки.

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

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

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

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

Не забудьте поставить палец вверх, подписаться на канал и звонить в колокол, чтобы не пропустить следующие видео-уроки. До новых встреч! Всем пока!

MQL5 Сравниваем вещественные числа
MQL5 Сравниваем вещественные числа
  • 2022.02.13
  • www.youtube.com
В этом видео-уроке рассмотрена проблема сравнения вещественных чисел, или чисел с плавающей точкой, при написании программ на языке программирования MQL5. И,...
 

MQL5 Расчет лота в процентах от...


MQL5 Расчет лота в процентах от...

Всем привет! В этом видео-уроке мы частично рассмотрим вопрос управления рисками, а именно расчет объема входа в рынок на основе допустимого риска на одну позицию.

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

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

Далее объявим переменную "профит" и проинициализируем ее значением 0. Эта переменная будет использоваться для расчета результата сделки.

С помощью функции "ордер_профит" рассчитаем результат сделки и проверим, не превышает ли он максимальный допустимый риск. Если превышает, выведем сообщение пользователю.

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

Если все условия выполняются, вернем значение "лота", иначе вернем недопустимое значение.

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

Вторая причина более тонкая. Дело в том, что на финансовых рынках есть торговые инструменты, у которых стоимость одного пункта постоянная, а есть и такие, у которых стоимость одного пункта плавающая. К первой группе относятся те инструменты, которые оцениваются в долларах США, например, валютные пары типа "евро/доллар" или "фунт/доллар", а также фьючерсы и сырьевые товары. Вот с такими торговыми инструментами функция "лот_сайз" в приведенном виде работает отлично.

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

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

Вот, в общем, все. Если было интересно и познавательно, ставьте палец вверх, подписывайтесь на канал и оставляйте комментарии. Всем пока!

MQL5 Расчет лота в процентах от...
MQL5 Расчет лота в процентах от...
  • 2022.02.18
  • www.youtube.com
В этом видео-уроке по языку программирования MQL5 рассмотрим такой животрепещущий момент, как расчет лота будущей сделки. Точнее, один из вариантов такого ра...
 

MQL5 Коррекция недопустимых котировок и лотов


MQL5 Коррекция недопустимых котировок и лотов

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

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

Начнем с минимального шага изменения котировки. У некоторых брокеров минимальный размер пункта котировки отличается. Чтобы узнать минимальный шаг изменения этой котировки, можно вызвать функцию "SymbolInfoDouble" с параметром "SYMBOL_POINT". А чтобы узнать минимальный шаг изменения котировки, можно вызвать ту же функцию, но с параметром "SYMBOL_TRADE_TICK_SIZE".

Далее рассмотрим пример с фьючерсами на евро сроком истечения в марте 2022 года. Теперь перейдем к нормализации значения котировки. Для этого создадим функцию "Normalize" с двумя параметрами: значение, которое нужно нормализовать, и имя торгового инструмента, к ограничениям которого нужно нормализовать значение.

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

Далее определим переменную "maxStep" без инициализации. Ее значение будем присваивать через серию проверок. Затем определим остаток от деления значения на точность. Если остаток равен нулю, значит котировка уже в порядке и дальнейших действий не требуется.

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

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

Если же значение "step" равно или превышает значение "maxStep", то значит ближайший шаг котировки находится уже в следующем порядке. В этом случае, увеличим текущее значение "а" и "value" на единицу и обнулим значение "lostValue". При этом, выйдем из цикла.

Теперь остается только собрать нормализованное значение котировки. Умножим значение "а" на "maxStep" и прибавим к полученному результату значение "lostValue". Результат этой операции будет являться нормализованным значением котировки в виде целого числа.

Вернем полученный результат из функции и закончим ее работу. Теперь применим функцию "Normalize" в нашем коде и нормализуем значение котировки перед отправкой торговых приказов.

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

После получения значений "requestSL" и "requestTP" (или аналогичных), прогоняем эти значения через функцию "Normalize". Затем повторяем те же действия для другого направления позиций.

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

Стоит обратить внимание на один момент: функция "QuotNormalize" нормализует переданное в нее значение котировки вверх. Но есть случаи, когда нормализовать котировку лучше вниз, например, значение стоп-лосса для покупок.

Если вы столкнетесь с такой необходимостью, просто отнимите от итогового результата значение "SymbolInfoDouble" с параметром "SYMBOL_TRADE_TICK_SIZE".

Теперь пришло время разобраться со значением "лот" (объема). Попробуем открыть еще одну позицию, но с измененным значением объема. В этот раз даже позицию не открыли, и терминал обзывает инвалидом требуемое нами значение объема позиции. Пора разобраться с этой ситуацией.

Нечто подобное мы уже проделывали в уроке о мани менеджменте. Ссылка на этот урок будет под роликом. Но там механизм нормализации значения объема намертво интегрирован с расчетами потенциальных убытков. А нам нужно просто нормализовать объем без привязки к каким-либо другим механизмам.

Для этого напишем еще одну функцию. Возвращать она тоже будет значение типа "double", и будет у нее два параметра. Назовем ее "LotNormalize". Предварительно вернем и значение "лот", чтобы не получать ошибок при промежуточных компиляциях.

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

Для начала стоит проверить, выходят ли значения "лот" за рамочные ограничения. Если это так, просто возвращаем значение "лот" мин или от "maxLot" в зависимости от ситуации.

Если не повезло, и не влезло в абсолютном значении случаев, идем дальше. Тут стоит заметить, что значения "lotMin", "lotMax" и "lotStep" всегда взаимосвязаны между собой. Если все три значения перевести в целые числа, "lotMin" и "lotMax" будут делиться на "lotStep" без остатка.

Без такого соответствия вся эта конструкция с ограничениями просто не работает. Вспоминается непреложная истина: пепелац без гравицапы не летает.

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

Для начала объявим переменную "newVolume". Придется дать ей имя с подчеркиванием, потому что, если поступить по аналогии с предыдущей функцией, нарвемся на вызов встроенной функции "

Далее смотрим, сколько раз значение "lotStep" помещается в значении "newVolume", при этом остаток от деления просто отбрасываем. И осталось лишь присвоить переменной "newVolume" значение переменной "lotStep" умноженное на значение "volue".

Не забываем привести возвращаемое из функции значение к типу "double" и вернуть полученный результат.

Теперь перед вызовом функции открытия позиции достаточно заданное в настройках значение "лот" пропустить через функцию "LotNormalize".

Обратите внимание на то, что мы нормализуем объем строго в сторону уменьшения. Если вам нужно увеличить итоговое значение, поступайте по аналогии с результатами работы функции "QuotNormalize".

Но это небезопасно смысле увеличения потенциальных убытков. И на данный момент все.

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

MQL5 Коррекция недопустимых котировок и лотов
MQL5 Коррекция недопустимых котировок и лотов
  • 2022.02.23
  • www.youtube.com
В этом видео-уроке, посвященном приемам программирования в MQL5 , мы вместе напишем функции, которые позволяют, в автоматическом режиме, побороть ситуацию, к...
 

MQL5 Приемы и примеры. Управление временем


MQL5 Приемы и примеры. Управление временем

Всем привет! Тема сегодняшнего видеоурока - временные пользовательские ограничения для программ на MQL5. Другими словами, рассмотрим, как ограничить работу программы в зависимости от даты и времени, без необходимости вмешиваться в работу программы через настройки торгового терминала.

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

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

После этого, переходим к написанию функции "allow_it_time", которая будет обрабатывать значения переменных "start_time" и "stop_time". Эти значения должен задавать пользователь. Функция будет типа bool и вернет true, если время работы программы находится в указанных пользователем интервалах, и false, если программа должна сопровождать или закрывать свои позиции.

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

Вот такая будет всего одна строка в теле функции:

return (TimeCurrent() >= StrToTime(startTime) && TimeCurrent() <= StrToTime(stopTime));
Таким образом, наша функция будет возвращать истину только в случае истинности указанного условия. А само условие крайне простое - текущее серверное время должно находиться в промежутке времени между началом торгового интервала и его окончания. В любом другом случае функция возвращает false.

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

if (PositionsTotal() == 0 && AllowItTime(startTradingTime, stopTradingTime))
{
    Alert("Можно открыть позицию!");
}
Теперь сообщение о возможности открыть позицию будет видно только с 8 утра до полудня по серверу, а в остальное время будет выводиться только сообщение о сопровождении позиций, если таковые конечно есть.

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

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

В описании под видео вы также найдете исходный код скрипта, который мы только что написали, а также много другой интересной информации. Всем пока!

MQL5 Приемы и примеры. Управление временем
MQL5 Приемы и примеры. Управление временем
  • 2022.07.30
  • www.youtube.com
Продолжаем осваивать типовые, и не очень, приемы программирования на MQL5. В этом видео-уроке будет рассмотрен вопрос тайм-менеджмента ваших программ, ограни...