Быстрый старт: краткий курс для начинающих

Dmitriy Parfenovich | 14 сентября, 2012

Введение

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

Советник и его структура

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

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

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

Основные блоки советника
  Рис. 1. Основные блоки советника

  1. Блок параметров - этот блок содержит информацию для терминала, которая помогает ему правильно обслуживать советник. Самыми распространенными параметрами являются: версия советника, название компании-производителя, краткое описание.

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

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

  4. Блок OnTick() - этот блок вызывается каждый раз, когда приходит новая информация от сервера о символе (валютной паре). В этом блоке указываются условия, при которых будет совершена сделка и функции самой сделки.

 Пример нового документа создаваемого по умолчанию редактором MetaEditor
    Рис. 2. Пример нового документа создаваемого по умолчанию редактором MetaEditor

Поясню на примере. Вот код "пустого" советника, так сказать "болванка", которую нам потом необходимо будет заполнить.
Вот здесь видно:

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

Индикаторы и работа с ними

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

Все индикаторы можно разделить на два типа: трендовые и осцилляторы.

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

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

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

Пример индикатора Moving Average
   Рис. 3. Пример индикатора Moving Average

Индикаторный буфер - это массив, в котором первый элемент (с индексом 0) содержит данные на самой правой свече, следующий элемент (с индексом 1) - данные на второй справа свече и т.д. Такое расположение элементов называется таймсерией.

Рассмотрим пример:
Пусть валютная пара будет EUR/USD, таймфрейм 1 час.
Для начала необходимо подключить индикатор к советнику и получить его хендл.

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

int iMA_handle; 
iMA_handle=iMA("EURUSD",PERIOD_H1,10,0,MODE_SMA,PRICE_CLOSE);
Разберем подробно.

В первой строке мы определим переменную которая будет хранить хендл индикатора. Во второй строке вызываем индикатор (в данном случае индикатор Moving Average), указываем параметры этого индикатора и сохраняем хендл в переменную для дальнейшей работы.
При работе в редакторе MetaEditor, после написания "iMA(" над этой строкой появится подсказка по этому индикатору с параметрами вызова через запятую.

Пример подсказки по параметрам индикатора Moving Average
   Рис. 4. Пример подсказки по параметрам индикатора Moving Average

Видим следующие параметры, перечисленные слева направо:

  1. имя символа (в подсказке выделено жирным) - это текстовый параметр, валютная пара (символ);
  2. таймфрейм;
  3. период индикатора (в данном случае период усреднения);
  4. смещение графика на N баров вперед/назад. Положительно число говорит о смещении графика на N баров вперед, отрицательное - назад;
  5. метод усреднения;
  6. используемая цена или хендл другого индикатора.

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

Пример вызова справки по индикатору по нажатию F1
    Рис. 5. Пример вызова справки по индикатору по нажатию F1

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

ChartIndicatorAdd(ChartID(),0,iMA_handle);

Теперь давай разберемся, что значит эта строка. Ставим курсор на команду ChartIndicatorAdd и нажимаем F1, после читаем в справочном окне, для чего нужна эта команда. А там написано, что эта команда:

Добавляет на указанное окно графика индикатор с указанным хэндлом.

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

Изменив код на следующий:

ChartIndicatorAdd(ChartID(),1,iMA_handle);

наш индикатор появится в подокне, расположенном под графиком цены.

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

double iMA_buf[];
ArraySetAsSeries(iMA_buf,true);
CopyBuffer(iMA_handle,0,0,3,iMA_buf);

В данном примере мы объявили динамический массив iMA_buf[] с типом double, т.к. индикатор Moving Average строится по ценам, а они имеют дробную часть.

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

Последней строкой мы копируем значения индикатора в массив iMA_buf[]. Теперь эти данные можно применять. 

 

Ордера, сделки и позиции

Начнем с ордеров.

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

Чтобы стало более понятно приведу пример: открываем длинную позицию размером 1 лот, т.е. мы выставляем ордер по текущей цене (к примеру) и размером 1 лот. Если запрос был верным, то он отправляется на сервер для обработки. Как только он обработается, то в терминале на вкладке "Торговля" появится позиция с параметрами ордера. Потом мы решили открыть еще одну длинную позицию, тоже размером 1 лот. После обработки приказа на вкладке "Торговля" мы не увидим два ордера, а одну позицию с объемом 2 лота. Т.е. позиция это есть результат выполнения нескольких ордеров.

Теперь перейдем к практике. Чтобы сделать запрос нужно заполнить поля структуры:

struct MqlTradeRequest
{
ENUM_TRADE_REQUEST_ACTIONS action; // Тип выполняемого действия
ulong magic; // Штамп эксперта (идентификатор magic number)
ulong order; // Тикет ордера
string symbol; // Имя торгового инструмента
double volume; // Запрашиваемый объем сделки в лотах
double price; // Цена 
double stoplimit; // Уровень StopLimit ордера
double sl; // Уровень Stop Loss ордера
double tp; // Уровень Take Profit ордера
ulong deviation; // Максимально приемлемое отклонение от запрашиваемой цены
ENUM_ORDER_TYPE type; // Тип ордера
ENUM_ORDER_TYPE_FILLING type_filling; // Тип ордера по исполнению
ENUM_ORDER_TYPE_TIME type_time; // Тип ордера по времени действия
datetime expiration; // Срок истечения ордера (для ордеров типа ORDER_TIME_SPECIFIED)
string comment; // Комментарий к ордеру
};

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

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

Стоп-лосс и тейк-профит

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

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

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

Работа со стандартными библиотеками

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

Торговые библиотеки (смотри  также торговые классы) располагаются по следующему пути: Include\Trade\ и подключаются директивой #include.
Пример:

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>

Приведенные выше в примере классы можно назвать основными, т.к. большинство советников можно построить используя только эти два класса (библиотеки). Я называю эти классы бибилотеками:


Иногда еще применяют и третью библиотеку:
#include <Trade\OrderInfo.mqh>
Она содержит функции для работы с ордерами, если, допустим, наша стратегия подразумевает использование отложенных ордеров.

Помните структуру торгового запроса, с ее большим количеством параметров, которые нужно знать когда применять?
Теперь я покажу пример торгового запроса с использованием библиотеки:
CTrade m_Trade;
m_Trade.Sell(lot,symbol_name,price,sl,tp,comment);

Тут всего 6 параметров из которых только один является обязательным (самый первый - это размер ордера).
Теперь подробно опишу каждый из параметров:

Закрыть позицию можно несколькими способами:

  1. закрыть всю позицию
    CPositionInfo m_Position;
    m_Position.Select(symbol_name);
    m_Trade.PositionClose(symbol_name);
  2. закрыть встречным ордером, объемом равным объему позиции
    CTrade m_Trade;
    m_Trade.Buy(lot,symbol_name,price,sl,tp,comment);
  3. использовать более сложный метод, при котором сначала перебираются все открытие позиции, из них выбираются необходимые по параметрам (символ, тип, магик-номер, идентификатор позиции и т.д.),  и позиция закрывается.
    Пример приводить не буду ввиду сложности его понимания на начальном этапе.

Соберем все вместе

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

//+------------------------------------------------------------------+
//|                                           fast-start-example.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>                                         //подключаем библиотеку для совершения торговых операций
#include <Trade\PositionInfo.mqh>                                  //подключаем библиотеку для получения информации о позициях

int               iMA_handle;                                      //переменная для хранения хендла индикатора
double            iMA_buf[];                                       //динамический массив для хранения значений индикатора
double            Close_buf[];                                     //динамический массив для хранения цены закрытия каждого бара

string            my_symbol;                                       //переменная для хранения символа
ENUM_TIMEFRAMES   my_timeframe;                                    //переменная для хранения таймфрейма

CTrade            m_Trade;                                         //структура для выполнения торговых операций
CPositionInfo     m_Position;                                      //структура для получения информации о позициях
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   my_symbol=Symbol();                                             //сохраним текущий символ графика для дальнейшей работы советника именно на этом символе
   my_timeframe=PERIOD_CURRENT;                                    //сохраним текущий период графика для дальнейшей работы советника именно на этом периоде
   iMA_handle=iMA(my_symbol,my_timeframe,40,0,MODE_SMA,PRICE_CLOSE);  //подключаем индикатор и получаем его хендл
   if(iMA_handle==INVALID_HANDLE)                                  //проверяем наличие хендла индикатора
   {
      Print("Не удалось получить хендл индикатора");               //если хендл не получен, то выводим сообщение в лог об ошибке
      return(-1);                                                  //завершаем работу с ошибкой
   }
   ChartIndicatorAdd(ChartID(),0,iMA_handle);                      //добавляем индикатор на ценовой график
   ArraySetAsSeries(iMA_buf,true);                                 //устанавливаем индексация для массива iMA_buf как в таймсерии
   ArraySetAsSeries(Close_buf,true);                               //устанавливаем индексация для массива Close_buf как в таймсерии
   return(0);                                                      //возвращаем 0, инициализация завершена
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   IndicatorRelease(iMA_handle);                                   //удаляет хэндл индикатора и освобождает память занимаемую им
   ArrayFree(iMA_buf);                                             //освобождаем динамический массив iMA_buf от данных
   ArrayFree(Close_buf);                                           //освобождаем динамический массив Close_buf от данных
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   int err1=0;                                                     //переменная для хранения результатов работы с буфером индикатора
   int err2=0;                                                     //переменная для хранения результатов работы с ценовым графиком
   
   err1=CopyBuffer(iMA_handle,0,1,2,iMA_buf);                      //копируем данные из индикаторного массива в динамический массив iMA_buf для дальнейшей работы с ними
   err2=CopyClose(my_symbol,my_timeframe,1,2,Close_buf);           //копируем данные ценового графика в динамический массив Close_buf  для дальнейшей работы с ними
   if(err1<0 || err2<0)                                            //если есть ошибки
   {
      Print("Не удалось скопировать данные из индикаторного буфера или буфера ценового графика");  //то выводим сообщение в лог об ошибке
      return;                                                                                      //и выходим из функции
   }

   if(iMA_buf[1]>Close_buf[1] && iMA_buf[0]<Close_buf[0])          //если значение индикатора были больше цены закрытия и стали меньше
     {
      if(m_Position.Select(my_symbol))                             //если уже существует позиция по этому символу
        {
         if(m_Position.PositionType()==POSITION_TYPE_SELL) m_Trade.PositionClose(my_symbol);  //и тип этой позиции Sell, то закрываем ее
         if(m_Position.PositionType()==POSITION_TYPE_BUY) return;                             //а если тип этой позиции Buy, то выходим
        }
      m_Trade.Buy(0.1,my_symbol);                                  //если дошли сюда, значит позиции нет, открываем ее
     }
   if(iMA_buf[1]<Close_buf[1] && iMA_buf[0]>Close_buf[0])          //если значение индикатора были меньше цены закрытия и стали больше
     {
      if(m_Position.Select(my_symbol))                             //если уже существует позиция по этому символу
        {
         if(m_Position.PositionType()==POSITION_TYPE_BUY) m_Trade.PositionClose(my_symbol);   //и тип этой позиции Buy, то закрываем ее
         if(m_Position.PositionType()==POSITION_TYPE_SELL) return;                            //а если тип этой позиции Sell, то выходим
        }
      m_Trade.Sell(0.1,my_symbol);                                 //если дошли сюда, значит позиции нет, открываем ее
     }
  }
//+------------------------------------------------------------------+

Протестируем наш советник со следующими параметрами:

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

И вот результаты беглого тестирования на истории. 

Результат тестирование нашего советника
    Рис. 6. Результат тестирование нашего советника

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

Заключение

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