Всем привет!
В прошлый раз мы с вами сделали болванку робота и выяснили, что из 36-ти исходных строк шаблона важной является фактически всего одна. Отсюда вывод - сложность написания роботов собственными руками преувеличена, делите ее на 36 :)
Понятно, что я утрирую, но действительно убежден, что это дело по силам любому человеку, способному структурировать свои мысли (а других в трейдинге и быть не может imho) и изложить логику своей торговой стратегии в виде набора непротиворечащих друг другу конструкций “если, то”.
Если вы смогли формализовать свою стратегию, то считайте полдела сделано, осталось только заложить эту логику в мозг робота. Вот этим сегодня и займемся.
Учебная стратегия
Для первого дееспособного робота возьмем очень простую и распространенную стратегию из разряда следящих за трендом, использующую пересечение двух простых скользящих средних (Simple Moving Average, SMA).
| Скрин 1 |
Закинул я на график EUR/USD Daily две SMA: красная с периодом 14, желтая с периодом 28. Первую будем звать быстрой скользящей средней, вторую - медленной. Кто не в курсе, тем поясню - чем меньше период MA, тем быстрее она реагирует на движение цены.
Почему 14 и 28? Да все очень просто: 14 - это дефолтное значение для MA в Метатрейдере, а 28 - это просто 14*2. Другими словами никакого сакрального смысла в эти числа не закладывал, взял просто для примера. Когда-нибудь в другой раз доберемся до тестирования и оптимизации этого нашего робота, тогда посмотрим какие периоды лучше сработают на “дневке”.
Итак, формализуем нашу торговую стратегию:
- Логика входа в позицию:
Если быстрая SMA пересекает медленную, то входим в позицию. - Если пересечение происходит снизу вверх (см. пример с зеленой стрелкой на скрине 1), то входим в длинную позицию по текущей цене.
- Если же сверху вниз (красная стрелка), то шортим по текущей цене.
- Логика выхода из позиции:
Она противоположна логике входа, то есть при пересечении “вверх” закрываем короткую позицию, а при пересечении “”вниз” закрываем длинную. Другими словами, при каждом пересечении текущая позиция, если она есть, инвертируется. - Логика мани-менеджмента:
Размер лота постоянный, лот берется минимально возможный - 0,01.
TakeProfit и StopLoss не используется.
Такая вот простенькая стратегия, но для тренировка пойдет.
Начинаем “прошивку мозга”
Поскольку вся наша стратегия завязана по сути на одно единственное событие - на пересечение двух SMA, первым делом научим робота “ловить” это событие, а потом уже научим его на него реагировать.
Давайте детальнее рассмотрим момент пересечения SMA.
| Скрин 2 |
На графике видно, что пересечение произошло примерно посередине между двумя свечами: той, над которой я поставил стрелку (сигнальная свеча), и предыдущей. Есть смысл фиксировать факт пересечения, дождавшись закрытия сигнальной свечи. Зачем? А чтобы отсечь ложные пересечения. Что за ложные пересечения? Попробуйте представить ситуацию на скрине 2 в динамике. Сигнальная свеча могла сначала пойти вниз, SMA могли пересечься, а после свеча могла бы взять и резко убежать вверх, при этом пересечение исчезло бы. Так бы мы и словили ложное пересечение. Так что общая рекомендация - дожидайтесь закрытия сигнальных свечек.
| Скрин 3 |
Итак, получается, что анализировать наличие или отсутствие пересечения мы будем даже не на сигнальной свече (#1 на скрине 3), а в самом начале следующей (#0). Свечи нумеруются справа налево, начиная с нуля: текущая имеет номер 0, предыдущая 1 и так далее до бесконечности, точнее покуда не кончатся исторические данные у вашего терминала.
Функция iMA
Для обнаружения пересечения роботу понадобятся 4 числа: значения обеих SMA на момент закрытия свечей #1 и #2. Вот в этой статье мы обсуждали как эти значения получить. Нужно использовать i-функцию соответствующего технического индикатора. В нашем случае iMA. Посмотрим с какими параметрами нужно вызывать эту функцию:
- symbol - имя нашей валютной пары;
- timeframe - тут, думаю, и без комментариев понятно о чем речь;
- ma_period - у нас используются два периода: 14 и 28;
- ma_shift - этот параметр пока игнорируем, просто ставим тут нолик и не путаем с просто shift (см. ниже);
- ma_method - тут задается метод усреднения для скользящей средней, в нашем случае используется простое усреднение, имеющее специальное обозначение MODE_SMA;
- applied_price - помните, у свечи есть 4 уровня OHLC? так вот для вычисления MA можно использовать любой из них, а по умолчанию цену закрытия PRICE_CLOSE;
- shift - номер свечи, для которой мы хотим получить значение индикатора.
По началу может показаться сложно, но надо один раз разобраться с этим набором параметров, а дальше будет гораздо проще, так как набор параметров у большинства технических индикаторов очень схожий.
Давайте пропишем пример вызова iMA для получения значения быстрой скользящей для сигнальной свечи:
double fast1=iMA(NULL, 0, 14, 0, MODE_SMA, PRICE_CLOSE, 1);
В результате выполнения этой строчки нашей программы в переменную fast1 будет записано искомое нами значение. Еще пару комментариев по параметрам:
- Обратите внимание на то, как задан параметр symbol - каким-то словом NULL. Что это? Ведь наша валютная пара - EUR/USD. Оказывается это очень удобное слово, оно указывает роботу, что имя символа надо взять с того графика, на котором работает робот. Получается очень универсально, рекомендую.
- Аналогично с параметром timeframe, только тут надо вписать не NULL, а просто нолик, и робот сам подставит сюда таймфрейм графика.
Все остальные значения параметров уже должны быть понятны.
Последнее, что нужно объяснить, - это кто такой double и почему fast1.
fast1 - так я решил назвать значение быстрой MA для свечи с номером 1, чтобы название переменной было “говорящим”, то есть чтобы по названию сразу было понятно назначение переменной. Вы вольны выбрать любое другое имя, какое вам покажется более подходящим.
А double - это тип значения переменной fast1. Каждой переменной всегда нужно явно указывать тип, иначе компилятор выдаст ошибку. Вообще существует много разных типов. Я чаще других использую:
- int - обозначает целое число;
- double - дробное число;
- string - строка символов, проще говоря какой-то текст;
- datetime - календарная дата и время.
Так вот наша переменная fast1 является дробным числом, так как значение именно такого типа возвращает переменная iMA, как и большинство других технических индикаторов.
Ну вот, теперь все должно быть понятно даже … пионЭру. Кстати, я так и остался на всю жизнь пионером, ну да мы сегодня не об этом :)
Теперь давайте добавим в код нашей программы, а точнее в главную функцию OnTick, строки под все четыре искомые значения. Продолжу именовать их в том же стиле: fast2, slow1 и slow2.
Жмем F7, получаем ‘0 errors’, радуемся :)
Ловим пересечение
Математически описать пересечение очень просто. Например, для выявления пересечения снизу вверх должны быть выполнены 2 условия:
- fast2<=slow2
- fast1>=slow1
Строго говоря, нельзя считать пересечением ситуацию, когда обе части вдруг окажутся равенствами, но вероятность такого расклада ничтожна.
В коде программы проверка на наличие пересечений будет выглядеть так:
Используется условный оператор if. Он проверяет выполняется ли условие, указанное сразу после него в круглых скобках. Чтобы указать роботу, что два или более условий должны выполняться одновременно, нужно поставить между ними && - это логический оператор И.
Если условие в скобках выполнится, то будет выполнена команда, стоящая сразу за скобками. Если же нужно будет выполнить не одну, а несколько команд, то их нужно заключить в фигурные скобки, что и сделано в нашем примере. Правда сейчас набор команд пустой, нам еще предстоит их прописать.
Единственное, что сейчас есть внутри фигурных скобок, - это текстовый комментарий. Чтобы вставить в код программы комментарий, нужно поставить двойной слеш // и после него можно писать все, что угодно в произвольном виде. Комментарием компилятор будет считать все, что находится между // и концом строки кода. Можно писать и многострочные комментарии, для этого используется конструкция /* строки_комментария */. Комментарии - штука полезная, рекомендую ими активно пользоваться, чтобы проще было понимать собственный код, ну или чтобы кто-нибудь другой мог без труда понять логику работы вашей программы.
Обрабатываем пересечение
Предположим, поймали мы пересечение снизу вверх. Теперь вроде бы надо входить в длинную позицию. Но для начала нужно проверить нет ли открытой короткой позиции, и, если она есть, закрыть ее.
Ищем короткую позицию
В одной из прошлый статей мы в первом приближении уже обсуждали как это сделать. Нам помогут функции OrdersTotal и OrderSelect. Давайте рассмотрим пример кода, приведенный в справке по функции OrdersTotal, точнее его кусочек. На его основе мы сделаем функцию обнаружения и закрытия короткой позиции. Итак:
В первой строке в целочисленную переменную total записывается общее количество текущих открытых ордеров/позиций.
Во второй строке … да вы уже знаете - комментарий. Повторюсь, код скопирован из справки и к нашему роботу прямого отношения не имеет, так что не заморачивайтесь кто там и что собирается записывать в файл.
В строке 3 мы встречаем очень полезную конструкцию - цикл for. Этот оператор заставляет программу несколько раз повторить одну и ту же команду или, опять таки, набор команд (если используется конструкция {}), но, как правило, с разными параметрами. Параметры цикла определятся в круглых скобках сразу после for. В примере мы видим следующие параметры:
(int pos=0;pos<total;pos++)
Давайте разберем все три части этого выражения (они разделены точкой с запятой):
- int pos=0; //Означает, что на первой итерации цикла переменная pos будет иметь значение 0 (ноль).
- pos<total; //Означает, что цикл будет выполняться покуда значение переменной pos будет меньше total.
- pos++ //Означает, что для каждой следующей итерации значение pos будет увеличиваться на единицу.
Таким образом, первая часть задает исходное значение для переменной цикла, вторая определяет условие прекращения цикла, третья задает действие при переходе к каждой следующей итерации цикла.
В строке 5 с помощью уже знакомого нам условного оператора if проверяется доступна ли информация об ордере с номером pos (сам номер содержится в этой переменной). Если да, то в строке 6 параметры этого ордера записываются в некий файл. Если же нет, то цикл for просто переходит к следующему ордеру - используется команда continue.
Переделаем этот код под наши цели, кроме того вынесем его в отдельную функцию. Зачем, спросите, отдельная функция? Можно обойтись и без нее, вставив код напрямую в основную функцию OnTick, но давайте все же потренируемся создавать самодельные функции. Это вам очень пригодится.
Вот так может выглядеть простейший вариант функции CloseAllShort():
Почему простейший? Потому, что здесь не учтен ряд нюансов, и о части из них компилятор нам сообщит - покажет 2 warnings. Но warnings (предупреждения) это не ошибки, их, конечно, лучше избегать, но и с ними робот будет работать. На начальной стадии предупреждения компилятора можно игнорировать, вернемся к ним попозже.
Ищем длинную позицию
Забегая вперед, давайте путем минимальных изменений уже написанной функции сделаем аналогичную для закрытия всех длинных позиций:
Вызов самодельной функции
Теперь мы можем использовать наши функции в основном коде, то есть в функции OnTick:
Собственно вызов самодельной функции ничем не отличается от вызова штатной функции - указываем имя функции и в скобках набор параметров. В нашем примере функции не имеют параметров, поэтому скобки пустые.
Открываем новую позицию
Нам потребуется функция OrderSend:
Готово!
Собственно вот и все, мы с вами написали первого нашего робота. Итоговый код выглядит так:
А помните как мы в прошлый раз “резали” исходный шаблон советника? В нем было 36 строк кода при том, что он ничего полезного не делал. А сейчас у нас работающий робот и всего 49 строк кода. Конечно, этот код еще далек от совершенства. Я намеренно сделал в нем ряд допущений, чтобы отдельно обсудить их в будущих статьях. Тем не менее робот получил “мозги” и теперь умеет торговать. Я даже для интереса прогнал его на тестере в самом грубом режиме тестирования. Интересно, что получилось? Конечно интересно. А вот что:
| Скрин 4 |
Это график баланса за период с июня 2006 года и по настоящее время. Стартовый баланс был 1000 USD. Прибыль в конечной точке тестирования составила 330 USD. Как я уже объяснял в предыдущих статьях, обольщаться этим результатом с учетом низкого качества тестирования не надо. Но все же он дает общее представление о жизнеспособности стратегии.
Шмагла?
Обращаюсь к тем, кто впервые во время чтения этой или предыдущей статьи “взял в руки” MQL4, прошел со мной все шаги и добился успешной компиляции нашего робота. Ну что, друзья, у вас получилось? Вы это сделали?
Если да, и если вы не просто скопировали мой код, а полностью или хотя бы бОльшей частью его поняли, то теперь вам мало что может помешать самостоятельно воплощать в жизнь свои задумки по автоматизации вашей торговли, а я дальше лишь буду помогать, что-то подсказывать. Будут вопросы - обязательно пишите в комменты.
Что дальше?
А дальше будем совершенствовать код, добавлять возможность произвольно менять период скользящих средних, чтобы подобрать оптимальные параметры на тестировании. И само тестирование тоже будем совершенствовать. В общем, скучно не будет :)
А пока - пока!
С уважением,
Игорь Шепелев
Комментариев нет:
Отправить комментарий