11
Июль
2016

Как написать советник для MT4 по японским свечным моделям




Всем привет. Сегодняшнее мое “извержение” мысли будет повествовать о том, как написать советник для MT4 по японским свечным моделям. При этом я рассмотрю всего две разворотных модели: утреннюю звезду и вечернюю звезду.

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

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

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

  • определение точек входа и выхода;
  • управление рисками;
  • управление деньгами.

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

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

А теперь давайте посмотрим на эти самые свечные модели – утреннюю и вечернюю звезду. Если вы до настоящего времени не прочли книгу Стива Нисона “За гранью японских свечей”, то рекомендую это сделать. Весьма полезно в образовательных целях. Итак, графически эти модели представляются так:

Cвечные модели утренняя звезда и вечерняя звезда

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

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

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

каждый на рынке видит ровно столько, сколько готов увидеть и понять.

Тем не менее, Стив Нисон особо отмечает, что слом тренда на этих моделях наиболее вероятен в том случае, когда модели максимально соответствуют классическому описанию.

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

Думаю, теории вполне достаточно. Приступим к практической части, в которой и ответим на главный вопрос: “Как написать советник для MT4 по свечным моделям?”. Давайте, все-таки формализует нашу торговую систему. Для начала определимся с условиями открытия длинных (покупок) и коротких (продаж) позиций, т.е. опишем точки входа:

  • Мы будем покупать финансовый инструмент в том случае, когда на падающем рынке образуется свечная модель утренняя звезда.
  • Мы будем продавать финансовый инструмент в том случае, когда на растущем рынке образуется свечная модель вечерняя звезда.
  • Падающий и растущий рынки будем определять исходя из направления трендовой прямой, проходящей через ценовые бары с индексами от некоторой, заданной параметром depth глубины до текущего бара. Параметры этой трендовой прямой (коэффициент наклона – k и смещение – b) будем определять методом наименьших квадратов. Именно по знаку коэффициента k определяется вид тренда: при k>0 – тренд восходящий, k<0 - тренд нисходящий, при k->0 (стремящемся к нулю) – флэт. По значению модуля коэффициента k будем судить о силе тренда, сравнивая его с определенным параметром angle. Таким образом, тренд будем считать восходящим и достаточной силы при условии, если k>=angle, нисходящим и достаточной силы, при условии, если k<=-angle.

Теперь определимся с условиями фиксации прибыли и убытка (ограничение рисков):

  • Профит-ордер короткой позиции будем размещать либо на фиксированном уровне, расположенном на заданном расстоянии от уровня открытия позиции, либо на заранее условленном уровне Фибоначчи. При этом сетку Фибоначчи “растягиваем” от предыдущего локального ценового минимума до максимальной цены центральной свечи модели вечерняя звезда.
  • Профит-ордер длинной позиции будем размещать либо на фиксированном уровне, расположенном на заданном расстоянии от уровня открытия позиции, либо на заранее условленном уровне Фибоначчи. При этом сетку Фибоначчи “растягиваем” от предыдущего локального ценового максимума до минимальной цены центральной свечи модели утренняя звезда.
  • В этих целях введем параметр tp, которым будем задавать расстояние от уровня открытия позиции до уровня фиксации прибыли. Этот же параметр будет определять и режим фиксации прибыли профит-ордером:
    - при tp, отличном от нуля, профит (в пунктах) будет величиной постоянной для каждой открываемой позиции и не будет зависеть от текущей ситуации на рынке;
    - при tp=0 цель по прибыли будет величиной переменной для каждой открываемой позиции. Уровень профит-ордера в данном случае будет определятся с учетом предыдущего “размаха” ценового движения и исходя из предопределенного параметром fibo_level уровня Фибоначчи: считаем, что свечные модели утренняя и вечерняя звезда образуют новый локальный экстремум (т.е. в центральной точке этих моделей происходит излом предыдущего тренда), и строим уровни Фибоначчи для пары соседних экстремумов, одним из которых является предшествующий свечным моделям соответствующий локальный экстремум, а другим – вновь образованный данными свечными моделями.

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

  • Стоп-ордер короткой позиции будем размещать несколько выше (через расстояние-фильтр) уровня максимальной цены центральной свечи модели вечерняя звезда.
  • Стоп-ордер длинной позиции будем размещать несколько ниже (через расстояние-фильтр) уровня минимальной цены центральной свечи модели утренняя звезда.
  • Это расстояние-фильтр будем определять в параметре sl_filter.

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

Определимся с правилами управления деньгами торгового счета:

  • Условимся, что мы можем изменять объем каждой вновь открываемой позиции по определенному правилу, в зависимости от наличия свободных средств торгового счета. Например, условимся, что в номинальном режиме объем сделки вычисляется исходя из того, что на каждую 1000 дол. свободных средств торгового счета приходится объем сделки равный 0.01 лот. Введя некий коэффициент, это соотношение можно изменять как в сторону увеличения, так и в сторону уменьшения. Назовем этот коэффициент параметром Risk.
  • Введем правило Мартингейл, при котором в случае закрытия позиции с убытком, следующая позиция будет открываться с увеличенным на заданный коэффициент объемом. Назовем этот коэффициент параметром KLot.

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

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

//загрузка депозита 
extern int Risk = 5;
//коэффициент повышения объема следующей сделки, если предыдущая была закрыта с убытком
extern double KLot = 1;
//фильтр для стоп лосса (предупреждает преждевременный вынос с рынка)
extern double sl_filter = 100;
//тейк профит, пунктов (5 знак). Если tp = 0, то советник сам будет управлять уровнем tp
//для каждой сделки исходя из заданного уровня Фибоначчи
extern int tp = 0;
//индекс уровеня Фибоначчи (от 0 до 7)
extern int fibo_level = 5;
double fibo[]={0.236, 0.382, 0.5, 0.618, 1.0, 1.618, 2.618, 4.236};
//тело первой свечи имеет размер не менее чем, в k1 раз больше размера среднего тела 
//рассчитанного для последних свечей на глубине depth
extern double k1 = 2.1;
//тело второй свечи имеет размер не более чем, в k2 раз меньше размера среднего тела 
//рассчитанного для последних свечей на глубине depth
extern double k2 = 0.2; 
//глубина выборки свечей для определения среднего размера свечи и 
//построения трендовой линии по МНК
extern int depth = 72;
//значение коэффициента наклона трендовой линии, при котором считается, что тренд имеет
//достаточную силу
extern double angle = 10;
//использование режима трейлинг стоп, если режим активен, то стоп лосс перемещается по 
//максимумам\минимумам свечей в зависимости от направления сделки
extern bool tral = false;
//допустимое проскальзывание для исполнения типа "инстант" 
//для исполнения типа "маркет" (по рынку) игнорируется, пунктов (5 знак)
extern int slip = 20; 
//уникальный номер ордеров, открываемых этим советником
extern int MagicNumber = 100500;
 
//эта переменная будет использоваться для хранения времени свечи, 
//на которой открывалась позиция 
datetime time = 0;

Поскольку каждая японская свеча характеризуется целым набором параметров:

  • ценой открытия и закрытия;
  • максимальной и минимальной ценой;
  • видом свечи: бычья, медвежья либо доджи;
  • размером тела и теней,

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

//структура будет описывать свечу
   struct candle {
     //параметры свечи
     double open, close, high, low, body; //цены
     bool bullish, bear, doji, big; //вид свечи: бычья, медвежья, доджи (без тела), большая белая/черная
     datetime t; //время свечи
     double up_shadow, down_shadow; //верхняя и нижняя тени
    //функция инициализирует параметры свечи, принимая в качестве аргумента индекс значения из таймсерии 
     void load (int i) { 
        bullish=false; bear=false; doji = false; big = false;
        open  = NormalizeDouble(iOpen(Symbol(),Period(),i), Digits);  //цена открытия
        close = NormalizeDouble(iClose(Symbol(),Period(),i), Digits); //цена закрытия
        high  = NormalizeDouble(iHigh(Symbol(),Period(),i), Digits);  //максимальная цена
        low   = NormalizeDouble(iLow(Symbol(),Period(),i), Digits);   //минимальная цена 
        t     = iTime(Symbol(),Period(),i); //время закрытия
        body  = MathAbs(close-open); //размер тела
        //проверка на "бычье тело"
        if ( close > open )  {
            bullish = true;         //бычье тело
            up_shadow = high-close; //верхняя тень
            down_shadow = open-low; //нижняя тень
        }
        //проверка на "медвежье тело"
        if ( close < open )  {
            bear = true;             //медвежье тело
            up_shadow = high-open;   //верхняя тень
            down_shadow = close-low; //нижняя тень
        }
        //проверка на доджи - свеча без тела
        if ( close == open )  {
            doji = true;            //счеча без тела - доджи
            up_shadow = high-close; //верхняя тень
            down_shadow = open-low; //нижняя тень
        }
        //проверка на большую свечу (размер тела минимум в 2 раза больше размера теней)
        if ( body>=2*(up_shadow+down_shadow) ) big = true; 
     }   
   };

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

candle c1, c2, c3;

И инициализируем эти переменные:

//инициализируем свечки, описывающие модели
   c1.load(3); c2.load(2); c3.load(1);

Вы обратили внимание, что первой свече соответствует 3 индекс таймсерии, а третьей свече соответствует 1 индекс таймсерии?

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

Вдумчиво прочтя описание свечных моделей утренняя звезда и вечерняя звезда, вы, наверняка, задались вопросом о том, как определить, что тело первой свечи модели достаточно большое, а тело второй свечи модели достаточно маленькое, чтобы признать модель именно той, за которую она “себя выдает”. Большая и маленькая, простите, это сколько в “граммах”? :)

Предлагаю следующий подход. Мы берем некую выборку последних свечей в количестве depth “штук”. Определяем “средний арифметический размер тела”. Так вот, большое тело – это то тело, отношение которого к среднему телу дает коэффициент, больший или равный некого заданного коэффициента k1 (при этом k1 должен быть заведомо больше 1). Соответственно, маленькое тело – это то тело, отношение которого к среднему телу дает коэффициент, меньший или равный некого заданного коэффициента k2 (при этом k2 должен быть заведомо меньше 1). Задав эти самые коэффициенты k1 и k2, реализуем только что сказанное в коде:

   body_size = 0; // инициализация среднего размера тела для выборки
   //"пройдемся" от текущей свечи по предыдущим свечам на глубину depth
   //и определим сумму длин тел свечей этой выборки
   for (trade = 1; trade<depth+1; trade++) { 
 
       body_size+=MathAbs(iClose(Symbol(),Period(),trade)-iOpen(Symbol(),Period(),trade)); 
   }
   body_size/=depth; //средний арифметический размер тела из выборки на глубину depth
   body1 = body_size*k1; //минимальный размер тела первой свечи
   body2 = body_size*k2; //максимальный размер тела второй свечи

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

  • Проверка на “вечернюю звезду”
   if ( c1.bullish &&                          //первая свеча - бычья
        c1.big &&                              //первая свеча имеет ярко выраженное большое тело,
        c1.body >= body1 &&                    //причем, тело размером не меньше определенного
        (c2.bullish || c2.bear || c2.doji)  && //Вторая свеча бычья или медвежья либо без тела вообще
        c2.body <= body2 &&                    //размер тела второй свечи не больше определенного
        !c2.big &&                             //тело второй свечи меньше размера ее теней
        c2.up_shadow>=c2.down_shadow &&        //верхняя тень должна быть больше или равна нижней тени
        c3.bear &&                             //третья свеча - медвежья
        c3.close<=c1.close-c1.body/2 &&        //уровень ее закрытия должен быть глубоко
                                               //(не менее, чем до середины) 
                                               //погружен в тело первой свечи 
        angle_k > angle &&                     //проверка на восходящий тренд
        time != iTime(Symbol(),Period(),0) )   //сравниваем время свечи со временем свечи, 
                                               //на которой открывали предыдущую позицию 
                                               //(чтобы повторно не открываться на этой же свече)
    {
          ...
    }
  • Проверка на “утреннюю звезду”
   if ( c1.bear &&                            //первая свеча - медвежья
        c1.big &&                             //первая свеча имеет ярко выраженное большое тело,
        c1.body>=body1 &&                     //причем, ее тело размером не меньше определенного
        (c2.bear || c2.bullish || c2.doji) && //Вторая свеча бычья или медвежья либо без тела вообще
        c2.body <= body2 &&                   //размер тела второй свечи не больше определенного
        !c2.big &&                            //тело второй свечи меньше размера ее теней
        c3.bullish &&                         //третья свеча - бычья
        c3.close>=c1.close + c1.body/2 &&     //уровень ее закрытия должен быть глубоко 
                                              //(не менее, чем до середины)
                                              //погружен в тело первой свечи 
        c2.up_shadow<=c2.down_shadow  &&      //верхняя тень должна быть меньше или равна нижней тени
        angle_k < -angle &&                   //проверка на нисходящий тренд
        time != iTime(Symbol(),Period(),0) )  //сравниваем время свечи со временем свечи, 
                                              //на которой открывали предыдущую позицию
                                              //(чтобы повторно не открываться на этой же свече)
   {
      ...
   }

В условиях наличия свечных моделей утренней и вечерней звезды существует проверка на нисходящий и восходящий тренд (сравнение angle_k с заданным параметром angle). Выше было отмечено, падающий и растущий рынки мы будем определять исходя из направления трендовой прямой, проходящей через ценовые бары с индексами от некоторой, заданной параметром depth глубины до текущего бара. Параметры этой трендовой прямой мы условились определять методом наименьших квадратов. Напишем соответствующую процедуру:

//Функция определения коэффициентов прямой у=kx+b методом наименьших квадратов
//n - число баров истории, type - тип цены (Close, Open, High, Low),
//коэффициенты прямой записываются в массив k
void trend_parametr_MNK (double& k[], int n, int type) {
   double c0=0, c1=0, c2=0, c3=0, y[];
   int i;
   //копируем нужные (в зависимости от типа цены) ценовые данные в наш буфер
   switch (type) {
      case PRICE_CLOSE: 
         ArrayCopy(y,Close,0,0,n);
      break;
      case PRICE_OPEN: 
         ArrayCopy(y,Open,0,0,n);
      break;
      case PRICE_HIGH: 
         ArrayCopy(y,High,0,0,n);
      break;
      case PRICE_LOW: 
         ArrayCopy(y,Low,0,0,n);
      break;
      default:
         ArrayCopy(y,Close,0,0,n);
      break;
   }
   for ( i=0; i<n; i++ ) {
      c0+=i;
      c1+=y[n-i-1];
      c2+=i*y[n-i-1];
      c3+=i*i;
   }
   //коэффициент наклона прямой - именно этот коэффициент покажет направление тренда 
   //(трендовой линии) на заданном участке истории глубиной n - depth в настройках советника.
   k[0] = (n*c2-(c0*c1))/(n*c3-c0*c0);
   //смещение прямой относительно оси у
   k[1] =(c1-k[0]*c0)/n;
}

Теперь получим параметры трендовой линии по методу наименьших квадратов. Параметры будем хранить в массиве k, размерность которого будет равна 2, при этом k[0] содержит угловой коэффициент прямой, а k[1] – действительное число, определяющее смещение прямой относительно оси y (в нашем случае в роли оси у-цена финансового инструмента)

   //определим параметры трендовой прямой по методу наименьших квадратов (МНК)
   ArrayResize(k, 2); //установим размерность массива параметров прямой равной двум
   trend_parametr_MNK(k, depth, PRICE_CLOSE); //определим параметры прямой исходя из цен закрытия

В целях удобства (работа с большими числами) переменная angle_k связана с расчетным угловым коэффициентом трендовой линии k[0] посредством предопределенной переменной Point (pазмер пункта текущего инструмента в валюте котировки):

angle_k = k[0]/Point;

А ведь неплохо было бы сделать так, чтобы мы могли увидеть на ценовом графике эту саму трендовую линию, параметры которой вычислили методом наименьших квадратов. Здесь мы перейдем на ступень выше в освоении MQL4 и “победим” графические объекты для советника на МТ4. Давайте напишем процедуру, которая будет строить на графике произвольную прямую, проходящую через две заданные точки. Эта прямая может иметь произвольный цвет, стиль и толщину линии, т.е. эти, как и другие параметры процедуры, мы будем задавать:

//процедура рисует трендовую линию по двум точкам
void TrendLine (long            chart_ID,   //ID графика
                string          name,       //имя трендовой линии - оно же название объекта
                int             sub_window, //номер подокна
                datetime        t1,         //время первой точки
                double          p1,         //цена первой точки
                datetime        t2,         //время второй точки
                double          p2,         //цена второй точки
                color           ink,        //цвет трендовой линии
                int             style,      //стиль трендовой линии
                int             width,      //толщина трендовой линии
                bool            back,       //возможность рисования на заднем плане
                bool            selection,  //возможность выделения для перемещений
                bool            ray_right,  //продолжать ли трендовую линии вправо - дальше 2 точки
                bool            hidden,     //возможность скрыть в списке объектов
                long            z_order)    //приоритет на нажатие мышью
{
      //создадим объект типа OBJ_TREND трендовая линия
      ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,t1,p1,t2,p2);
      //цвет трендовой линии
      ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,ink);   
      //стиль отображения трендовой линии
      ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style); 
      //толщина трендовой линии
      ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width); 
      //режим отображения трендовой линии на переднем (false) или заднем (true) плане
      ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);   
      //включим (true) или отключим (false) режим перемещения трендовой линии мышью
      ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
      ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
      //включим (true) или отключим (false) режим продолжения отображения трендовой линии вправо
      ObjectSetInteger(chart_ID,name,OBJPROP_RAY_RIGHT,ray_right);
      //скроем (true) или отобразим (false) имя графического объекта в списке объектов
      ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);
      //установим приоритет на получение события нажатия мыши на графике
      ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order);
}

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

TrendLine(0, "TrendLine_"+ticket, 0, 
               iTime(Symbol(),Period(),depth), k[0]*0+k[1],
               iTime(Symbol(),Period(),1),     k[0]*(depth-1)+k[1], 
               ink, STYLE_SOLID, 2, false, false, false, true, 0);

где ticket – это тикет номер открытого ордера, а ink – цвет трендовой линии. При этом линия сплошная (STYLE_SOLID) и имеет удвоенную толщину (2).

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

//ищет предыдущий ценовой локальный экстремум из ломаной ZigZag
//g-глубина выборки истории, количество баров, среди которых будем
//отыскивать первый попавшийся нужный экстремум (минимум или максимум)
//t-определяет, что искать: локальный минимум либо локальный максимум
//t=1 - ищем на истории первый локальный максимум
//t=2 - ищем на истории первый локальный минимум
double local_extremum (int g, int t) {
   double m;
   int i = 3; //шагаем вглубь истории начиная с 3-ей позиции 
   while ( i<g+3 ) {  //идем пока не перебрали все бары заданной глубины
      m = iCustom(Symbol(), Period(), "ZigZag", 12, 5, 3, t, i);
      if ( m > 0 ) { //если в нужном буфере индикатора не пустое значение, то
         return m;   //завершаем поиск и возвращаем результат
      }
      i++;
   }
   return 0; //неудачный поиск
}

Как видите, для нахождения локальных минимумов и максимумов используется довольно популярный индикатор ZigZag, включенный в стандартный “комплект поставки” терминала MT4. Данный индикатор относится к категории индикаторов – “перерисовщиков”, но данное обстоятельство все равно не будет нам сильно мешать, если написать советник по японским свечным моделям утренняя и вечерняя звезда. Чтобы отыскать, например, предыдущий локальный минимум, следует обратиться к вышеуказанной функции local_extremum вот так:

//определим предшествующий локальный минимум
local_min = local_extremum(200,2);

где 200 предельная глубина поиска, а 2 определяет, что ищем именно локальный минимум.

Локальный максимум, соответственно, ищем вот таким вызовом функции:

//определим предшествующий локальный максимум
local_max = local_extremum(200,1);

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

//Расчитаем объем позиции, исходя из определенной загрузки депозита
double Lots = NormalizeDouble (Risk*AccountEquity()/100000, 2);
if ( Lots<0.01 ) { //если расчетный объем меньше минимального, 
        Lots = 0.01;  //то объем позиции приравняем к минимальному
}

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

   //А вот здесь введем возможность применения элементов т.н. мартингейла.
   //"Пробежимся по истории", найдя последнюю закрытую позицию, которую открывал советник.
   //И если эта позиция была закрыта с убытком, то увеличим объем следующей позиции в KLot раз.
   //Для отключения "мартина" KLot в настройках зададим равным 1.
   for ( trade = OrdersHistoryTotal() - 1; trade >= 0; trade-- )    {
         if ( OrderSelect(trade, SELECT_BY_POS, MODE_HISTORY) && 
              (OrderType() == OP_BUY || OrderType() == OP_SELL) && 
              OrderMagicNumber() == MagicNumber && 
              OrderSymbol() == Symbol() )  {
              //Вот здесь итог последней закрытой позиции, в котором учтены и своп, и комиссия брокера.
              if ( OrderProfit()+OrderSwap()+OrderCommission()<0 ) {
                  Lots*=KLot;
                  //округляем до сотых (для брокеров, где шаг равен 0.01 лот)
                  NormalizeDouble (Lots, 2);
              }
              break;
         }
   }

Итак, мы задали все необходимые параметры нашей автоматической торговой системы (советника для МТ4), написали необходимые процедуры, а также реализовали в коде условия отыскания на свечном графике моделей утренняя звезда и вечерняя звезда, “закодили” правила торговой системы. Теперь, объединив все эти элементы в единое целое, дадим шанс “на жизнь” нашему советнику:

//+------------------------------------------------------------------+
//|                                     morning_and_evening_star.mq4 |
//|                                       (C) 2016 moneyinnetwork.ru |
//|                                         http://moneyinnetwork.ru |
//+------------------------------------------------------------------+
#property copyright "(C) 2016 moneyinnetwork.ru"
#property link      "http://moneyinnetwork.ru"
 
//загрузка депозита 
extern int Risk = 5;
//коэффициент повышения объема следующей сделки, если предыдущая была закрыта с убытком
extern double KLot = 1;
//фильтр для стоп лосса (предупреждает преждевременный вынос с рынка)
extern double sl_filter = 100;
//тейк профит, пунктов (5 знак). Если tp = 0, то советник сам будет управлять уровнем tp
//для каждой сделки исходя из заданного уровня Фибоначчи
extern int tp = 0;
//индекс уровеня Фибоначчи (от 0 до 7)
extern int fibo_level = 5;
double fibo[]={0.236, 0.382, 0.5, 0.618, 1.0, 1.618, 2.618, 4.236};
//тело первой свечи имеет размер не менее чем, в k1 раз больше размера среднего тела 
//рассчитанного для последних свечей на глубине depth
extern double k1 = 2.1;
//тело второй свечи имеет размер не более чем, в k2 раз меньше размера среднего тела 
//рассчитанного для последних свечей на глубине depth
extern double k2 = 0.2; 
//глубина выборки свечей для определения среднего размера свечи и 
//построения трендовой линии по МНК
extern int depth = 72;
//значение коэффициента наклона трендовой линии, при котором считается, что тренд имеет
//достаточную силу
extern double angle = 10;
//использование режима трейлинг стоп, если режим активен, то стоп лосс перемещается по 
//максимумам\минимумам свечей в зависимости от направления сделки
extern bool tral = false;
//допустимое проскальзывание для исполнения типа "инстант" 
//для исполнения типа "маркет" (по рынку) игнорируется, пунктов (5 знак)
extern int slip = 20; 
//уникальный номер ордеров, открываемых этим советником
extern int MagicNumber = 100500;
 
//эта переменная будет использоваться для хранения времени свечи, 
//на которой открывалась позиция 
datetime time = 0;
 
//структура будет описывать свечу
struct candle {
     //параметры свечи
     double open, close, high, low, body; //цены
     bool bullish, bear, doji, big; //вид свечи: бычья, медвежья, доджи (без тела), большая белая/черная
     datetime t; //время свечи
     double up_shadow, down_shadow; //верхняя и нижняя тени
     //функция инициализирует параметры свечи, принимая в качестве аргумента индекс значения из таймсерии 
     void load (int i) { 
        bullish=false; bear=false; doji = false; big = false;
        open  = NormalizeDouble(iOpen(Symbol(),Period(),i), Digits);  //цена открытия
        close = NormalizeDouble(iClose(Symbol(),Period(),i), Digits); //цена закрытия
        high  = NormalizeDouble(iHigh(Symbol(),Period(),i), Digits);  //максимальная цена
        low   = NormalizeDouble(iLow(Symbol(),Period(),i), Digits);   //минимальная цена 
        t     = iTime(Symbol(),Period(),i); //время закрытия
        body  = MathAbs(close-open); //размер тела
        //проверка на "бычье тело"
        if ( close > open )  {
            bullish = true;         //бычье тело
            up_shadow = high-close; //верхняя тень
            down_shadow = open-low; //нижняя тень
        }
        //проверка на "медвежье тело"
        if ( close < open )  {
            bear = true;             //медвежье тело
            up_shadow = high-open;   //верхняя тень
            down_shadow = close-low; //нижняя тень
        }
        //проверка на доджи - свеча без тела
        if ( close == open )  {
            doji = true;            //счеча без тела - доджи
            up_shadow = high-close; //верхняя тень
            down_shadow = open-low; //нижняя тень
        }
        //проверка на большую свечу (размер тела минимум в 2 раза больше размера теней)
        if ( body>=2*(up_shadow+down_shadow) ) big = true; 
     }   
};
 
//функция инициализации
int init() {   
   return (0);
}
//функция деинициализации
int deinit() {
   return (0);
}
 
int start()
{
   //локальные переменные
   int ink;
   int op_type, ticket, trade;
   double open_price, take_profit, stop_loss, body_size, 
          local_min, local_max, k[], body1, body2, angle_k;
   candle c1, c2, c3;
   //Обновляем переменные терминала
   RefreshRates();
   //инициализируем свечки, описывающие модели
   c1.load(3); c2.load(2); c3.load(1);
   body_size = 0; // инициализация среднего размера тела для выборки
   //"пройдемся" от текущей свечи по предыдущим свечам на глубину depth
   //и определим сумму длин тел свечей этой выборки
   for (trade = 1; trade<depth+1; trade++) { 
 
       body_size+=MathAbs(iClose(Symbol(),Period(),trade)-iOpen(Symbol(),Period(),trade)); 
   }
   body_size/=depth; //средний арифметический размер тела из выборки на глубину depth
   body1 = body_size*k1; //минимальный размер тела первой свечи
   body2 = body_size*k2; //максимальный размер тела второй свечи
   //определим параметры трендовой прямой по методу наименьших квадратов (МНК)
   ArrayResize(k, 2); //установим размерность массива параметров прямой равной двум
   trend_parametr_MNK(k, depth, PRICE_CLOSE); //определим параметры прямой исходя из цен закрытия
   angle_k = k[0]/Point;
   //если задан режим трал, то перенесем стоплосс у открытых ордеров в сторону движения цены
   //по максимумам\минимумам свечей
   if ( tral ) {
     //"пройдемся" по всем открытым позициям и выберем те, которые были открыты данным советником
     for ( trade = OrdersTotal() - 1; trade >= 0; trade-- )    {
         //проверка условия на то, то ордер открыт именно этим советником на нужном инструменте
         if ( OrderSelect(trade, SELECT_BY_POS, MODE_TRADES) && 
              (OrderType() == OP_BUY || OrderType() == OP_SELL) && 
              OrderMagicNumber() == MagicNumber && 
              OrderSymbol() == Symbol() )  {
              if (OrderType() == OP_BUY && OrderStopLoss()<c3.low) {
                  //модификация ордера
                  ticket = OrderModify( OrderTicket(),    //тикет ордера
                                        OrderOpenPrice(), //цена открытия
                                        NormalizeDouble(c3.low, Digits), //стоп лосс
                                        OrderTakeProfit(), //тейк профит
                                        0,                 //время - для отложек
                                        clrBlue            //цвет линии на графике отображения позиции
                                       );
              }
              if (OrderType() == OP_SELL && OrderStopLoss()>c3.high) {
                  ticket = OrderModify( OrderTicket(),
                                        OrderOpenPrice(),
                                        NormalizeDouble(c3.high, Digits),
                                        OrderTakeProfit(),
                                        0, 
                                        clrRed
                                       );
              }
        }
     }
   }
   op_type = -1; //зададим заведомо недопустимый тип операции
   //проверка на "вечернюю звезду" -> сигнализирует о походе "вниз"
   if ( c1.bullish &&                          //первая свеча - бычья
        c1.big &&                              //первая свеча имеет ярко выраженное большое тело,
        c1.body >= body1 &&                    //причем, тело размером не меньше определенного
        (c2.bullish || c2.bear || c2.doji)  && //Вторая свеча бычья или медвежья либо без тела вообще
        c2.body <= body2 &&                    //размер тела второй свечи не больше определенного
        !c2.big &&                             //тело второй свечи меньше размера ее теней
        c2.up_shadow>=c2.down_shadow &&        //верхняя тень должна быть больше или равна нижней тени
        c3.bear &&                             //третья свеча - медвежья
        c3.close<=c1.close-c1.body/2 &&        //уровень ее закрытия должен быть глубоко 
                                               //(не менее, чем до середины) 
                                               //погружен в тело первой свечи 
        angle_k > angle &&                     //проверка на восходящий тренд
        time != iTime(Symbol(),Period(),0) ) { //сравниваем время свечи со временем свечи, 
                                               //на которой открывали предыдущую позицию 
                                               //(чтобы повторно не открываться на этой же свече)
          //цена открытия
          open_price = NormalizeDouble(Ask, Digits);
          //уровень стоп лосса по максимуму центральной свечи модели        
          stop_loss = NormalizeDouble(c2.high+sl_filter*Point, Digits);
          //рассчет уровня тейк профита
          if ( tp==0 ) {
              //определим предшествующий локальный минимум
              local_min = local_extremum(200,2);
              if ( c2.high-local_min<=0 ) return (0);
              take_profit = NormalizeDouble(c2.high-(c2.high-local_min)*fibo[fibo_level], Digits);
          }
          else  {
              take_profit = NormalizeDouble(c3.close - tp*Point, Digits);
          }
          op_type = OP_SELL;         //тип операции - продажа 
          ink = clrLightGreen;
   }
   //проверка на утреннюю звезду -> сигнализирует о походе "вверх"
   if ( c1.bear &&                            //первая свеча - медвежья
        c1.big &&                             //первая свеча имеет ярко выраженное большое тело,
        c1.body>=body1 &&                     //причем, ее тело размером не меньше определенного
        (c2.bear || c2.bullish || c2.doji) && //Вторая свеча бычья или медвежья либо без тела вообще
        c2.body <= body2 &&                   //размер тела второй свечи не больше определенного
        !c2.big &&                            //тело второй свечи меньше размера ее теней
        c3.bullish &&                         //третья свеча - бычья
        c3.close>=c1.close + c1.body/2 &&     //уровень ее закрытия должен быть глубоко
                                              //(не менее, чем до середины) 
                                              //погружен в тело первой свечи 
        c2.up_shadow<=c2.down_shadow  &&      //верхняя тень должна быть меньше или равна нижней тени
        angle_k < -angle &&                   //проверка на нисходящий тренд
        time != iTime(Symbol(),Period(),0) ) {//сравниваем время свечи со временем свечи, 
                                              //на которой открывали предыдущую позицию
                                              //(чтобы повторно не открываться на этой же свече)
          //цена открытия
          open_price = NormalizeDouble(Bid, Digits);
          //уровень стоп лосса по минимуму центральной свечи модели                
          stop_loss = NormalizeDouble(c2.low-sl_filter*Point, Digits);
          //рассчет уровня тейк профита
          if ( tp==0 )  {
             //определим предшествующий локальный максимум
             local_max = local_extremum(200,1);
             if ( local_max-c2.low<=0 ) return 0;
             take_profit = NormalizeDouble(c2.low+(local_max-c2.low)*fibo[fibo_level], Digits);
          }
          else  {
             take_profit = NormalizeDouble(c3.close + tp*Point, Digits);
          }
          op_type = OP_BUY;        //тип операции - покупка
          ink = clrRed;
   }
   if ( op_type==-1 ) return (0);   //выходим, если недопустимый тип операции
   //Расчитаем объем позиции, исходя из определенной загрузки депозита
   double Lots = NormalizeDouble (Risk*AccountEquity()/100000, 2);
   if ( Lots<0.01 ) { //если расчетный объем меньше минимального, 
        Lots = 0.01;  //то объем позиции приравняем к минимальному
   }
   //А вот здесь введем возможность применения элементов т.н. мартингейла.
   //"Пробежимся по истории", найдя последнюю закрытую позицию, которую открывал советник.
   //И если эта позиция была закрыта с убытком, то увеличим объем следующей позиции в KLot раз.
   //Для отключения "мартина" KLot в настройках зададим равным 1.
   for ( trade = OrdersHistoryTotal() - 1; trade >= 0; trade-- )    {
         if ( OrderSelect(trade, SELECT_BY_POS, MODE_HISTORY) && 
              (OrderType() == OP_BUY || OrderType() == OP_SELL) && 
              OrderMagicNumber() == MagicNumber && 
              OrderSymbol() == Symbol() )  {
              //Вот здесь итог последней закрытой позиции, в котором учтены и своп, и комиссия брокера.
              if ( OrderProfit()+OrderSwap()+OrderCommission()<0 ) {
                  Lots*=KLot;
                  //округляем до сотых (для брокеров, где шаг равен 0.01 лот)
                  NormalizeDouble (Lots, 2);
              }
              break;
         }
   }
   ticket = OrderSend( Symbol(),    //символ (текущий фин. инструмент)
                       op_type,     //тип операции (покупка\продажа)
                       Lots,        //объем
                       open_price,  //цена открытия - цена закрытия пред. бара
                       slip,        //проскальзывание
                       stop_loss,   //уровень стоп лосс
                       take_profit, //уровень тейк профит
                       "",          //комментарий 
                       MagicNumber, //уникальный номер
                       0,           //время -> для отложек
                       clrGreen     //цвет
                     );
  if ( !IsOptimization() )  {
     //рисуем нашу трендовую линию
     TrendLine(0, "TrendLine_"+ticket, 0, 
               iTime(Symbol(),Period(),depth), k[0]*0+k[1],
               iTime(Symbol(),Period(),1),     k[0]*(depth-1)+k[1], 
               ink, STYLE_SOLID, 2, false, false, false, true, 0);
  }
  time = iTime(Symbol(),Period(),0); //запоминаем время свечи, на которой открылись
  return (0);
}
//Функция определения коэффициентов прямой у=kx+b методом наименьших квадратов
//n - число баров истории, type - тип цены (Close, Open, High, Low),
//коэффициенты прямой записываются в массив k
void trend_parametr_MNK (double& k[], int n, int type) {
   double c0=0, c1=0, c2=0, c3=0, y[];
   int i;
   //копируем нужные (в зависимости от типа цены) ценовые данные в наш буфер
   switch (type) {
      case PRICE_CLOSE: 
         ArrayCopy(y,Close,0,0,n);
      break;
      case PRICE_OPEN: 
         ArrayCopy(y,Open,0,0,n);
      break;
      case PRICE_HIGH: 
         ArrayCopy(y,High,0,0,n);
      break;
      case PRICE_LOW: 
         ArrayCopy(y,Low,0,0,n);
      break;
      default:
         ArrayCopy(y,Close,0,0,n);
      break;
   }
   for ( i=0; i<n; i++ ) {
      c0+=i;
      c1+=y[n-i-1];
      c2+=i*y[n-i-1];
      c3+=i*i;
   }
   //коэффициент наклона прямой - именно этот коэффициент покажет направление тренда 
   //(трендовой линии) на заданном участке истории глубиной n - depth в настройках советника.
   k[0] = (n*c2-(c0*c1))/(n*c3-c0*c0);
   //смещение прямой относительно оси у
   k[1] =(c1-k[0]*c0)/n;
}
 
//ищет предыдущий ценовой локальный экстремум из ломаной ZigZag
//g-глубина выборки истории, количество баров, среди которых будем
//отыскивать первый попавшийся нужный экстремум (минимум или максимум)
//t-определяет, что искать: локальный минимум либо локальный максимум
//t=1 - ищем на истории первый локальный максимум
//t=2 - ищем на истории первый локальный минимум
double local_extremum (int g, int t) {
   double m;
   int i = 3; //шагаем вглубь истории начиная с 3-ей позиции 
   while ( i<g+3 ) {  //идем пока не перебрали все бары заданной глубины
      m = iCustom(Symbol(), Period(), "ZigZag", 12, 5, 3, t, i);
      if ( m > 0 ) { //если в нужном буфере индикатора не пустое значение, то
         return m;   //завершаем поиск и возвращаем результат
      }
      i++;
   }
   return 0; //неудачный поиск
}
 
//процедура рисует трендовую линию по двум точкам
void TrendLine (long            chart_ID,   //ID графика
                string          name,       //имя трендовой линии -оно же название объекта
                int             sub_window, //номер подокна
                datetime        t1,         //время первой точки
                double          p1,         //цена первой точки
                datetime        t2,         //время второй точки
                double          p2,         //цена второй точки
                color           ink,        //цвет трендовой линии
                int             style,      //стиль трендовой линии
                int             width,      //толщина трендовой линии
                bool            back,       //возможность рисования на заднем плане
                bool            selection,  //возможность выделения для перемещений
                bool            ray_right,  //продолжать ли трендовую линии вправо - дальше 2 точки
                bool            hidden,     //возможность скрыть в списке объектов
                long            z_order)    //приоритет на нажатие мышью
{
      //создадим объект типа OBJ_TREND трендовая линия
      ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,t1,p1,t2,p2);
      //цвет трендовой линии
      ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,ink);   
      //стиль отображения трендовой линии
      ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style); 
      //толщина трендовой линии
      ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width); 
      //режим отображения трендовой линии на переднем (false) или заднем (true) плане
      ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);   
      //включим (true) или отключим (false) режим перемещения трендовой линии мышью
      ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
      ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
      //включим (true) или отключим (false) режим продолжения отображения трендовой линии вправо
      ObjectSetInteger(chart_ID,name,OBJPROP_RAY_RIGHT,ray_right);
      //скроем (true) или отобразим (false) имя графического объекта в списке объектов
      ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);
      //установим приоритет на получение события нажатия мыши на графике
      ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order);
}

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

  • Во-первых, это параметр tp. Выше мы определили, что уровень профита (в пунктах) можно задавать самостоятельно. Так, при tp больше 0 за профит в пунктах принимается значение tp, т.е. оптимизируем советник по параметру tp, например, в диапазоне от 100 до 1000 пунктов с шагом 10 пунктов (пятизначные котировки). Если же tp равно 0, то советник сам будет управлять уровнем тейк профита, рассчитывая его для каждой открываемой позиции по “хитрому алгоритму” с использованием уровней Фибоначчи, о чем я подробно написал выше.
  • Во-вторых, в случае с “автоматическим” уровнем тейк профита (tp=0) оптимизации подлежит параметр fibo_level, определяющий именно тот уровень Фибоначчи, который будем являться уровнем тейк профита. Т.е. у нас есть одномерный массив fibo[]={0.236, 0.382, 0.5, 0.618, 1.0, 1.618, 2.618, 4.236} (кстати, этот массив вы можете расширить, включив в него и другие фибо уровни), а параметр fibo_level определяет индекс элемента массива fibo, который и применяется в расчете тейк профита:

    Для длинных позиций (покупки по модели утренняя звезда) уровень тейк профита определяется из выражения: c2.low+(local_max-c2.low)*fibo[fibo_level].
    Для коротких позиций (продажи по модели вечерняя звезда) уровень тейк профита определяется из выражения: c2.high-(c2.high-local_min)*fibo[fibo_level]

    Очевидно, если советник будет работать на откатах, то уровень Фибоначчи следует “искать” где-то в пределах 0.236 – 0.618 (т.е. для нашего массива fibo оптимизация по fibo_level от 0 до 3 с шагом 1), если же будем “ловить” разворот, то, вероятно, уровень Фибоначчи следует “искать” за пределами 0.618 (т.е. для нашего массива fibo оптимизация по fibo_level от 3 до 7 с шагом 1).

  • В-третьих, это параметр k1, определяющий во сколько раз тело первой свечи моделей должно быть больше среднего тела, рассчитанного для выборки из последних свечей на заданную глубину. Вероятно, параметр k1 следует отыскивать где-то из диапазона от 1 до 4 с шагом 0.1.
  • В-четвертых, это параметр k2, определяющий во сколько раз тело второй свечи модели должно быть меньше среднего тела, рассчитанного для выборки из последних свечей на заданную глубину. Вероятно, в этом случае поиск следует проводить из диапазона от 0 до 0.5 с шагом 0.1
  • В-пятых, это параметр depth (глубина выборки свечей для определения среднего размера свечи и построения трендовой линии по МНК), на котором “завязано” много других параметров. Он определяет предел той самой “исторической глубины”, в которую заглядывает наш советник, учитывающий ее динамику в своих алгоритмах. Вероятно, что здесь стоит говорить о значениях параметра depth никак не меньших 30-50 свечей. Т.е. оптимизацию лучше проводить из значений, например, от 30 до 150 с шагом в 1.
  • В-шестых, это параметр angle, определяющий нижний порог силы тренда. Если сила тренда (коэффициент угла наклона трендовой прямой), рассчитанная алгоритмом советника, ниже параметра angle, то свечная модель игнорируется и сделка по ней не открывается.

Советник для МТ4 по свечным моделям написали, с параметрами определились. Осталось только оптимизировать параметры и тестировать на истории. А это будем делать в следующий раз.

И вы знаете, у меня, честно говоря, есть предчувствие, что ничего хорошего из этой затеи не выйдет. :)

Материалы схожей тематики:
Поделитесь с другими:

Получать информацию о новых заметках:

Комментарии


Ваш комментарий

Подписаться без комментирования



amarkets