English 中文 Español Deutsch 日本語 Português
Вклад Томаса Демарка в технический анализ

Вклад Томаса Демарка в технический анализ

MetaTrader 4Трейдинг | 3 марта 2016, 12:18
16 747 7
Ivan Morozov
Ivan Morozov

Введение

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

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

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


1. TD-точки

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

Бычья TD-точкаМедвежья TD-точка

Рис. 1. Бычья и медвежья TD-точки

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

Выше были рассмотрены TD-точки только 1 уровня. Это значит, что сравнивается цена определяемой свечи только с одной до нее и одной — после. Если же нужно построить TD-точку 2 уровня, нужно сравнить максимальную цену определяемой свечи с двумя  до нее и двумя — после. Аналогично всё происходит для минимальных цен.

TD-точка 2 уровня

Рис. 2. Пример TD-точки 2 уровня

На изображении выше показана TD-точка 2 уровня. Заметно, что максимальная цена определяемой свечи выше двух максимальных цен свечей до нее и двух — после. 

TD-точка 40 уровня

Рис. 3. TD-точка 40 уровня.

Уровень TD-точек может быть и больше 2, в зависимости от количества значений максимумов и минимумов, с которыми нужно сравнивать определяемую свечу. При этом логично, что TD-точка, например, третьего уровня одновременно является точкой младших  уровней — второго и первого. Томас Демарк в своей книге описывает точки до 3 уровня.

Стоит отметить, что индикатор, работающий по подобному принципу, уже давно существует. Действительно, фракталы Билла Вильямса есть не что иное, как TD-точки 2 уровня. Напомню, они строятся, если минимум 2 свечи до определяемой и 2 — после нее имеют более низкие максимумы или более высокие минимумы, что полностью повторяет определение TD-точек 2 уровня.


2. TD-линии

Сами по себе TD-точки — всего лишь экстремумы. Для построения линии тренда нам понадобятся две точки (два максимума или два минимума). При этом Томас Демарк использовал только две последние  точки, как наиболее важные.

TD-линии 1 уровняTD-линии 2 уровня

Рис. 4. TD-линии первого и второго уровней.

На левом изображении построены две TD-линии 2 уровня (синяя линия — по минимумам, а зеленая — по максимумам ). Уровень линии обозначает уровень точек, по которым эта линия была построена. На правом изображении построены те же TD-линии, но 3 уровня.


3. Создание индикаторов

3.1. iTDDots

Было бы утомительно по каждой новой свече вручную строить новые точки и линии. Я считаю, что если можно что-то автоматизировать без потери качества, то необходимо это сделать. Ниже я расскажу о процессе создания индикатора, который строит TD-точки. В этом примере мы работаем с языком MQL4.

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

План работы индикатора:

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

Свечи, использованные в индикаторе

Рис. 5. Свечи, использованные в индикаторе

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

Теперь я покажу, как был разработан код.

Индикатор должен иметь два буфера для отображения точек, ведь одна свеча может быть одновременно и с TD-точкой, построенной по максимуму, и с TD-точкой, построенной по минимуму. Значит, начало программы выглядит так:

#property indicator_chart_window        //Для отображения индикатора в окне графика
#property indicator_buffers 2           //Используем 2 буфера
#property indicator_plots   2           //Два буфера будет отображено
#property indicator_color1 clrGreen     //Стандартный цвет первого буфера
#property indicator_type1   DRAW_ARROW  //Тип рисования первого буфера
#property indicator_width1 2            //Стандартная толщина линии отображения первого буфера
#property indicator_color2 clrBlue      //Стандартный цвет второго буфера
#property indicator_type2   DRAW_ARROW  //Тип рисования второго буфера
#property indicator_width2 2            //Стандартная толщина линии отображения второго буфера

Далее, при установке на график, пользователь должен определить для индикатора уровень построения D-точек:

input int Level = 1;

Для работы индикатора используются следующие переменные, которые объявлены как глобальные:

bool new_candle = true;         
double  pdH,                    //Для максимальной цены определяемой свечи
        pdL,                    //Для минимальной цены определяемой свечи
        pricesH[],              //Массив для хранения максимальных цен
        pricesL[];              //Массив для хранения минимальных цен
bool    DOTH,                   //Для отображения точки(по максимуму)
        DOTL;                   //Для отображения точки(по минимуму)
double  UpDot[],                //Массив буфера для значений точек, построенных по максимумам
        DownDot[];              //Массив буфера для значений точек, построенных по минимумам

Ниже приведена функция Init():

int OnInit()
  {
   ChartRedraw(0);                              //Это нужно, чтобы при переходе из одного таймфрейма в другой не было проблем с отображением
   SetIndexBuffer(0, UpDot);
   SetIndexBuffer(1, DownDot);
   SetIndexEmptyValue(0,0.0);
   SetIndexEmptyValue(1,0.0);
   SetIndexArrow(0,159);                        //Задаем номер символа из шрифта Wingdings
   SetIndexArrow(1,159);
   SetIndexLabel(0, "TD " + Level + " High");   //Эти названия будут отображены в окне данных
   SetIndexLabel(1, "TD " + Level + " Low");
   return(INIT_SUCCEEDED);
  }

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

void GetVariables(int start_candle, int level)
  {
   /*В этой функции индикатор собирает информацию со свеч для построения точки. Все переменные, используемые тут, уже объявлены, как глобальные*/
   pdH = iHigh(NULL, 0, start_candle + 1 + level);      //High определяемой свечи
   pdL = iLow(NULL, 0, start_candle + 1 + level);       //Low определяемой свечи
   
   ArrayResize(pricesH, level * 2 + 1);                 //Задаем размер массивам
   ArrayResize(pricesL, level * 2 + 1);                 //
   
   for (int i = level * 2; i >= 0; i--){                //Собираем все цены (максимумы и минимумы) нужных мне свеч в массивы
      pricesH[i] = iHigh(NULL, 0, start_candle + i + 1);
      pricesL[i] = iLow(NULL, 0, start_candle + i + 1);
  }

И, наконец, самое интересное — код функции start():

int start()
  {
   int i = Bars - IndicatorCounted();                   //Это нужно, чтобы не считать одно и то же всякий раз при появлении новой свечи
   for (; i >= 0; i--)
     {                                                  //Тут происходит все для построения TD-точек
      DOTH = true;
      DOTL = true;
      GetVariables(i, Level);                           //Получение текущих значений цен
      
      for(int ii = 0; ii < ArraySize(pricesH); ii++)
        {                                              //Определение, есть ли в этом промежутке TD-точка
         if (pdH < pricesH[ii]) DOTH = false;
         if (pdL > pricesL[ii]) DOTL = false;
        }   
         
      if(DOTH) UpDot[i + Level + 1] = pdH;              //Если да, то ее построение 
      if(DOTL) DownDot[i + Level + 1] = pdL;
      
      if(UpDot[i + Level + 1] ==  UpDot[i + Level + 2]) UpDot[i + Level + 2] = 0;       //Здесь я предусматриваю случай, в котором две свечи последовательно
      if(DownDot[i + Level + 1] ==  DownDot[i + Level + 2]) DownDot[i + Level + 2] = 0; //имеют одинаковые минимальные или максимальные цены и строю TD-точку
     }                                                                                  //на последней свече в паре
   return(0);
  }

Итак, вы познакомились с ходом написания индикатора для построения TD-точек. Ниже показаны два графика с установленным индикатором TDDots. На первом значение переменной Level = 1, а на втором Level = 10. Это значит, что на первом графике все TD-точки окружены минимум одной свечой с более низкими максимумами или более высокими минимумами, а на втором — 10 такими свечами. Эти графики предоставлены для демонстрации работы индикатора.

Все TD-точки 1 уровняВсе TD-точки 10 уровня

Рис. 6. Образец работы индикатора: построение TD-точек 1 и 10 уровней.


3.2. iTDLines

Как я уже написал, Томас Демарк использовал только последние две точки для построения TD-линии. В своем индикаторе я автоматизирую его тактику. Задача индикатора заключается в построении двух прямых линий через заданные точки. Но как их построить? Безусловно эту задачу можно решить, используя линейную функцию вида: y = kx + b. Для этого нужно найти коэффициенты k и b так, чтобы прямая проходила строго через заданные точки.

Чтобы построить прямую линию через две точки, нужно знать их координаты. Используя представленные ниже формулы, найдем k и b для линейной функции. При этом за x мы принимаем номера свеч от правого края графика, а за y — цены.

k = (y2 - y1) / (x2 - x1),
b = (x2 * y1 - x1 * y2) / (x2 - x1),
где x1 - номер свечи с первой точкой,
      x2 - номер свечи со второй точкой,
      y1 - цена первой точки,
      y2 - цена второй точки.

Зная k и b, остается решить простое линейное уравнение на каждой свече, и получится цена точки на TD-линии. 

Ниже приводится код индикатора:

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2

#property indicator_color1 clrGreen
#property indicator_color2 clrBlue

input int Level = 1;

double LU[], LD[];

//Эта переменная будет использована для сокращения расчетов.
datetime LastCount;

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

Далее представлена функция, которая сразу находит значения k и b для каждой из линий. Она будет описываться по частям:

void GetBK(double &Ub, double &Uk, double &Db, double &Dk, int &EndUN, int &EndDN)
  {
   double TDU[];
   double TDD[];
   int TDU_n[];
   int TDD_n[];
   ArrayResize(TDU, 2, 2);
   ArrayResize(TDD, 2, 2);
   ArrayResize(TDU_n, 2, 2);
   ArrayResize(TDD_n, 2, 2);

Функция возвращает 6 значений. Если назначение первых четырех переменных понятно, то с двумя последними все сложнее. Они пригодятся нам для отображения линий. Каждая из них означает длину линии в свечах, начиная от текущей.

//Получаем значения цен точек и номера свечей от начала
   int Ui = 0;
   int Di = 0;
   for(int i = 0;; i++)
     {
      double current_bar_U = iCustom(NULL, 0, "TDDots", Level, 0, i);
      double current_bar_D = iCustom(NULL, 0, "TDDots", Level, 1, i);
      
      if(current_bar_U > 0 && Ui < 2)
        {
         TDU[Ui] = current_bar_U;   //Цена
         TDU_n[Ui] = i;             //Номер
         Ui++;
        }
      if(current_bar_D > 0 && Di < 2)
        {
         TDD[Di] = current_bar_D;
         TDD_n[Di] = i;
         Di++;
        }
      if(Ui == 2 && Di == 2) break;
     }

Эта часть кода получает значения цен двух последних TD-точек. Цены сохраняются в массивы для дальнейшей работы с ними.

   Ub = ( (TDU_n[0] * TDU[1]) - (TDU[0] * TDU_n[1]) ) / ( TDU_n[0] - TDU_n[1] );
   Uk = (TDU[0] - TDU[1]) / (TDU_n[0] - TDU_n[1]);
   
   Db = ( (TDD_n[0] * TDD[1]) - (TDD_n[1] * TDD[0]) ) / ( TDD_n[0] - TDD_n[1] );
   Dk = (TDD[0] - TDD[1]) / (TDD_n[0] - TDD_n[1]);   
   
   EndUN = TDU_n[1];
   EndDN = TDD_n[1];
  }

Поскольку номера свеч имеют направление нумераци как в тайм-сериях (счет ведется справа налево), то следует использовать противоположные значения точек. Иными словами, в вышеприведенных формулах вместо x2 надо подставить x1, а вместо x1 — x2 и так далее. Тогда они выглядят так:

b = (x1 * y2 - x2 * y1) / (x1 - x2),
k = (y1 - y2) / (x1 - x2),
где x1 - номер свечи с первой точкой,
      x2 - номер свечи со второй точкой,
      y1 - цена первой точки,
      y2 - цена второй точки.

Далее следует функция (Init):

int OnInit()
  {
   SetIndexBuffer(0, LU);
   SetIndexLabel(0, "TDLU");
   SetIndexBuffer(1, LD);
   SetIndexLabel(1, "TDLD");
      
   SetIndexEmptyValue(0, 0);
   SetIndexEmptyValue(1, 0);
   
   LastCount = iTime(NULL, 0, 1);
   
   return(INIT_SUCCEEDED);
  }

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

Затем пропишем функцию start():

int start()
  {
   //Если новая свеча или первый запуск
   if(iTime(NULL, 0, 0) != LastCount)
     {
      double Ub, Uk, Db, Dk;
      int eUp, eDp;
      
      GetBK(Ub, Uk, Db, Dk, eUp, eDp);
      
      //Удаляем старые значения
      for(int i = 0; i < IndicatorCounted(); i++)
        {
         LU[i] = 0;      
         LD[i] = 0;
        }
         
      //Строим новые значения
      for(i = 0; i <= eUp; i++)
        {
         LU[i] = Uk * i + Ub;
        }
         
      for(i = 0; i <= eDp; i++)
        {
         LD[i] = Dk * i + Db;
        }
         
      LastCount = iTime(NULL, 0, 0);
     }
      
   return 0;
  }

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

В итоге индикатор работает так:

Образец работы индикатора

Рис. 7. Образец работы индикатора: построение TD-линий по TD-точкам 5 уровня.

Как нетрудно понять, на рис. 7 показана работа индикатора со значением переменной Level = 5.


3.3 Индикатор горизонтальных уровней

Безусловно, существует множество путей для определения цены горизонтальных уровней на графике. Я предлагаю простой способ построения двух уровней относительно текущей цены. В работе индикатора будет использоваться уже написанный нами iTDDots (см. п. 3.1).

Суть проста:

  1. Индикатор получает значения n числа TD-точек определенного, заданного пользователем уровня
  2. Подсчитывается среднее значение цен этих точек
  3. По нему отображается горизонтальная прямая на графике

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

Рассмотрим код индикатора.

Используются 2 буфера и 3 переменных, значения которых должен ввести пользователь:

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
#property indicator_color1 clrGreen
#property indicator_type1   DRAW_LINE
#property indicator_width1 2
#property indicator_color2 clrBlue
#property indicator_type2   DRAW_LINE
#property indicator_width2 2

input int TDLevel = 1;       //Уровень точек
input int NumberOfDots = 3;  //Количество точек
input double Delta = 0.001;  //Максимальное расстояние между двумя точками

double TDLU[], TDLD[];

В этом индикаторе создаются два горизонтальных уровня. Поскольку они могут иметь ценность при более долгосрочном анализе, чем трендовые линии в индикаторе TDLines, я решил использовать для их построения графические объекты — горизонтальные прямые. Однако использовать этот индикатор в дальнейшем будет намного проще, если значения горизонтальных уровней будут храниться в индикаторных буферах. Я решил хранить эти значения на текущей свече с индексом 0, чтобы всегда можно было, обратившись к iCustom из другой программы, простым способом получить значение цены уровня.

int OnInit()
  {
   SetIndexBuffer(0,TDLU);
   SetIndexBuffer(1,TDLD);
   SetIndexEmptyValue(0,0.0);
   SetIndexEmptyValue(1,0.0);
   SetIndexLabel(0, "ГУ U");
   SetIndexLabel(1, "ГУ D");
   ObjectCreate(0, "Горизонтальный уровень U", OBJ_HLINE, 0, iTime(NULL, 0, 0), 0);
   ObjectCreate(0, "Горизонтальный уровень D", OBJ_HLINE, 0, iTime(NULL, 0, 0), 0);
   return(INIT_SUCCEEDED);
  }

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

double GetLevelPrice(int ud,int n,double delta,int level)
  {
   /* ud - показатель типа линии. 0 - U, другое значение - D.
   n - количество точек.
   delta - максимальное расстояние между ними
   level - уровень точек.*/

   //Подготавливаются массивы для хранения цен точек
   double TDU[];
   double TDD[];
   ArrayResize(TDU,n,n);
   ArrayResize(TDD,n,n);
   ArrayInitialize(TDU,0);
   ArrayInitialize(TDD,0);
 
   //Цикл работает дважды, так как существует только 2 буфера данных
   for(int Buffer=0; Buffer<2; Buffer++)
     {
      int N=0;
      int Fails=0;
      bool r=false;
      for(int i=0; r==false; i++)
        {
         double d=iCustom(NULL,0,"TDDots",level,Buffer,i);
         if(d>0)
           {
            if(N>0)
              {
               if(Buffer==0) double cp=TDU[N-1];
               else cp=TDD[N-1];
               if(MathAbs(d-cp)<=delta)
                 {
                  if(Buffer == 0)
                     TDU[N] = d;
                  else TDD[N]=d;
                  N++;
                 }
               //Если расстояние слишком большое, то добавится 1 к ошибке
               else
                 {
                  Fails++;
                 }
              }
            else
              {
               if(Buffer == 0)
                  TDU[N] = d;
               else TDD[N]=d;

               N++;
              }
           }
         //Если набирается много ошибок, цикл прекращается
         if(Fails>2 || N>n) r=true;
        }
     }
   
   //Получение среднего значения
   double ATDU = 0;
   double ATDD = 0;
   N=0;
   for(i=0; i<ArraySize(TDU); i++)
     {
      ATDU=ATDU+TDU[i];
      if(TDU[i]==0)
        {
         i=ArraySize(TDU);
        }
      else
        {
         N++;
        }
     }
   ATDU=ATDU/N;
   N=0;
   for(i=0; i<ArraySize(TDD); i++)
     {
      ATDD=ATDD+TDD[i];
      if(TDD[i]==0)
        {
         i=ArraySize(TDD);
        }
      else
        {
         N++;
        }
     }
   ATDD=ATDD/N;

   //Функция возвращает значение
   if(ud == 0) return ATDU;
   else return ATDD;
  }

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

void start()
  { 
   //Удаляю значения буферов индикатора на предыдущей свече
   TDLD[1] = 0;
   TDLU[1] = 0;

   TDLD[0] = GetLevelPrice(1, TDLevel, Delta, NumberOfDots);
   TDLU[0] = GetLevelPrice(0, TDLevel, Delta, NumberOfDots);
   
   //Если неким образом объекты пропали
   if(ObjectFind("Горизонтальный уровень U") < 0)
     {
      ObjectCreate(0, "Горизонтальный уровень U", OBJ_HLINE, 0, iTime(NULL, 0, 0), 0);
     }
   if(ObjectFind("Горизонтальный уровень D") < 0)
     {
      ObjectCreate(0, "Горизонтальный уровень D", OBJ_HLINE, 0, iTime(NULL, 0, 0), 0);
     }
   
   ObjectSetDouble(0, "Горизонтальный уровень U", OBJPROP_PRICE, TDLU[0]);
   ObjectSetDouble(0, "Горизонтальный уровень D", OBJPROP_PRICE, TDLD[0]);
  }

Для удобства использования после удаления индикатора следует удалить и объекты:

void OnDeinit(const int reason)
  {
   if(!ObjectDelete("Горизонтальный уровень U")) Print(GetLastError());
   if(!ObjectDelete("Горизонтальный уровень D")) Print(GetLastError());
  }

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

Образец работы индикатора

Рис. 8. Образец работы индикатора. 



4. Эксперт для торговли по индикатору горизонтальных уровней

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

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

Условия для покупки:

  • Цена пробила верхний горизонтальный уровень
  • Цена выросла на n пунктов от значения цены верхнего горизонтального уровня

Условия для продажи зеркальны:

  • Цена пробила нижний горизонтальный уровень
  • Цена уменьшилась на n пунктов от значения цены нижнего горизонтального уровня

Иначе можно это изобразить так:

Сигнал на покупкуСигнал на продажу

Рис. 9. Условия на покупку и продажу

Эксперт будет открывать только один ордер и сопровождать его Trailing Stop'ом, то есть, будет перемещать Stop Loss на количество пунктов, заданное пользователем при его запуске.

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

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

input int MagicNumber = 88341;      //С таким магическим числом эксперт будет открывать ордера
input int GL_TDLevel = 1;           //Уровень TD-точек, который будет использовать индикатор горизонтальных уровней
input int GL_NumberOfDots = 3;      //Количество точек, которое будет использовать индикатор горизонтальных уровней
input double S_ExtraPoints = 0.0001;//Число дополнительных пунктов, которые и являются добавкой L из иллюстраций выше
input double GL_Delta = 0.001;      //Расстояние, на котором значения TD-точек принимаются во внимание индикатором горизонтальных уровней
input int StopLoss = 50;            //Уровень стопа
input double Lot = 0.01;            //Размер лота

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

int GetSignal(string symbol,int TF,int TDLevel,int NumberOfDots,int Delta,double ExtraPoints)
  {
//Значение цены уровня, построенного по верхним TD точкам
   double UL=iCustom(symbol,TF,"iglevels",GL_TDLevel,GL_NumberOfDots,GL_Delta,0,0)+ExtraPoints;
//...по нижним
   double DL=iCustom(symbol,TF,"iglevels",GL_TDLevel,GL_NumberOfDots,GL_Delta,1,0)-ExtraPoints;

   if(Bid<DL && iLow(symbol,TF,1)>DL)
     {
      return 1;
     }
   else
     {
      if(Ask>UL && iHigh(symbol,TF,1)<UL)
        {
         return 0;
        }
      else
        {
         return -1;
        }
     }
  }

Затем для работы эксперта необходимо объявить следующие переменные глобально:

int Signal = -1;          //Текущий сигнал
datetime LastOrder;       //Дата последней совершенной сделки. Необходима для избежания случаев многократного открытия ордеров на одной свече.

Функция Init() должна присвоить переменной LastOrder какую-нибудь прошедшую дату, чтобы можно было открывать новый ордер.

int OnInit()
  {
   LastOrder = iTime(NULL, 0, 1);
   return(INIT_SUCCEEDED);
  }

Далее приведена функция OnTick, в которой и происходит все самое главное:

void OnTick()
  {
   bool order_is_open=false;
//Происходит поиск открытого ордера
   for(int i=0; i<OrdersTotal(); i++)
     {
      if(!OrderSelect(i,SELECT_BY_POS)) Print(GetLastError());

      if(OrderMagicNumber()==MagicNumber)
        {
         order_is_open=true;
         break;
        }
     }

//Получение текущего сигнала
   Signal=GetSignal(Symbol(),0,GL_TDLevel,GL_NumberOfDots,GL_Delta,S_ExtraPoints);

//Расчет размера стопа
   double tsl=NormalizeDouble(StopLoss*MathPow(10,-Digits),Digits);

   if(order_is_open==true)
     {
      //Расчет цены стопа
      double p=NormalizeDouble(Ask-tsl,Digits);
      if(OrderType()==1) p=NormalizeDouble(Ask+tsl,Digits);

      if(OrderType()==0 && OrderStopLoss()<p)
        {
         if(!OrderModify(OrderTicket(),OrderOpenPrice(),p,0,0)) Print(GetLastError());
        }
      if(OrderType()==1 && OrderStopLoss()>p)
        {
         if(!OrderModify(OrderTicket(),OrderOpenPrice(),p,0,0)) Print(GetLastError());
        }
     }
//Если ордеров нет
   if(order_is_open==false)
     {
      //Если на текущей свече еще не открывались ордера
      if(iTime(NULL,0,0)!=LastOrder)
        {
         //Покупка
         if(Signal==0)
           {
            if(!OrderSend(NULL,0,Lot,Ask,5,Ask-tsl,0,NULL,MagicNumber)) Print(GetLastError());
            LastOrder=iTime(NULL,0,0);
           }
         //Продажа
         if(Signal==1)
           {
            if(!OrderSend(NULL,1,Lot,Bid,5,Ask+tsl,0,NULL,MagicNumber)) Print(GetLastError());
            LastOrder=iTime(NULL,0,0);
           }
        }
     }
  }

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

Эксперт работает следующим образом:

Пример работы эксперта

Рис.10. Образец работы эксперта в тестере стратегий

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


Результат тестирования

Рис.11. Результат тестирования

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


5. Эксперт для торговли по TD-линиям

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

Эксперт торгует только два сигнала:

Сигнал на продажу      Сигнал на покупку

Рис.12. Сигналы, по которым торгует etdlines

Позиция на покупку открывается, если бычья TD-линия была пробита снизу вверх, при этом цена прошла еще некоторое количество пунктов (которое задается пользователем) в том же направлении. К тому же TD-линия должна быть направлена вниз, то есть более поздней по дате свече должно соответствовать меньшее значение цены линии.

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

Позиции сопровождаются Trailing Stop'ом фиксированного размера. Закрытие позиции осуществляется только по стопу.

Ниже приведен код, который описывает переменные, вводимые пользователем:

input int Level=1;                 //Уровень TD-линий
input double Lot=0.01;             //Размер лота
input int AddPips=3;               //Добавочное число пунктов
input int Magic=88342;             //Магическое число ордеров
input int Stop=50;                 //Размер стопа в пунктах
input int Bars_To_Open_New_Order=2;//Промежуток в барах до открытия нового ордера

datetime LastBar;
datetime Trade_is_allowed;

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

В функции OnInit эксперт присваивает значения переменным LastBar и Trade_is_allowed:

int OnInit()
  {
   LastBar=iTime(NULL,0,1);
   Trade_is_allowed=iTime(NULL,0,0);

   return(INIT_SUCCEEDED);
  }

Следующая функция — GetSignal, которая возвращает торговый сигнал:

int GetSignal()
  {
//Если новая свеча
   if(LastBar!=iTime(NULL,0,0))
     {
      double DU = iCustom(NULL, 0, "itdlines", Level, 0, 0);
      double DD = iCustom(NULL, 0, "itdlines", Level, 1, 0);
      double DU1 = iCustom(NULL, 0, "itdlines", Level, 0, 1);
      double DD1 = iCustom(NULL, 0, "itdlines", Level, 1, 1);
     }

   double add_pips=NormalizeDouble(AddPips*MathPow(10,-Digits),Digits);

//Пробитие линии U --> покупка
   if(Ask>DU+add_pips && iLow(NULL,0,0)<Ask && DU<DU1)
     {
      return 0;
     }
   else
     {
      //Пробитие линии D --> продажа
      if(Bid<DD-add_pips && iHigh(NULL,0,0)>Bid && DD>DD1)
        {
         return 1;
        }
      //Нет пробития --> сигнал отсутствует
      else
        {
         return -1;
        }
     }

   return -1;
  }

Переменная LastBar используется для определения новой свечи. Это нужно, чтобы не рассчитывать значения двух последних точек TD-линии с приходом каждого нового тика, ведь они рассчитываются только один раз, когда появляется новая свеча. Функция возвращает: 0 — покупка, 1 — продажа, -1 — нет сигнала.

Наконец, функция OnTick, в которой и происходят все операции с ордерами:

void OnTick()
  {
   int signal=GetSignal();
   bool order_is_open=false;

//Поиск открытого ордера
   for(int i=0; i<OrdersTotal(); i++)
     {
      if(!OrderSelect(i,SELECT_BY_POS)) Print(GetLastError());

      //По значению магического числа
      if(OrderMagicNumber()==Magic)
        {
         order_is_open=true;
         i=OrdersTotal();
        }
     }

//Размер стопа
   double stop=Stop*MathPow(10,-Digits);
//Если ордер уже открыт
   if(order_is_open==true)
     {
      //Проверка возможности перенести стоп

      //Значение цены стопа ордера
      double order_stop=NormalizeDouble(OrderStopLoss(),Digits);

      //Если ордер на покупку
      if(OrderType()==0)
        {
         if(order_stop<NormalizeDouble(Ask-stop,Digits))
           {
            if(!OrderModify(OrderTicket(),OrderOpenPrice(),NormalizeDouble(Ask-stop,Digits),0,0))Print(GetLastError());
           }
        }
      //Если ордер на продажу
      if(OrderType()==1)
        {
         if(order_stop>NormalizeDouble(Bid+stop,Digits))
           {
            if(!OrderModify(OrderTicket(),OrderOpenPrice(),NormalizeDouble(Bid+stop,Digits),0,0))Print(GetLastError());
           }
        }
      Trade_is_allowed=iTime(NULL,0,0)+ChartPeriod(0)*60*Bars_To_Open_New_Order;
     }
//Если еще нет открытого ордера
   else
     {
      if(signal>=0 && iTime(NULL,0,0)>Trade_is_allowed)
        {
         if(signal==0)
           {
            if(!OrderSend(NULL,signal,Lot,Ask,5,Ask-stop,0,NULL,Magic)) Print(GetLastError());
           }
         if(signal==1)
           {
            if(!OrderSend(NULL,signal,Lot,Bid,5,Bid+stop,0,NULL,Magic)) Print(GetLastError());
           }

        }
     }
  }

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

Данный эксперт торгует следующим образом:

Пример работы в тестере

Рис.13. Пример работы эксперта etdlines

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

Результат тестирования

Рис.14. Результат тестирования etdlines



Заключение

Моей целью было описать TD-точки и TD-линии Томаса Демарка и показать реализацию его разработок на языке MQL4. В статье описывается пример создания 3 индикаторов и 2 экспертов. Как можно увидеть, идеи Демарка логично встраиваются в торговые системы и дают широкие перспективы для использования. 


Для того, чтобы использовать приведенные ниже индикаторы и эксперты, необходимо в первую очередь установить itddots.mq4, так как этот индикатор используется во всех остальных программах. Чтобы etdlines.mq4 работал, необходимо еще установить itdlines.mq4, и аналогично с eglevels.mq4: необходимо сперва установить iglevels.mq4. Это крайне важно, так как без установки необходимых индикаторов зависимая от них программа не будет работать и может привести к остановке работы терминала.
Прикрепленные файлы |
itddots.mq4 (2.91 KB)
itdlines.mq4 (3.26 KB)
iglevels.mq4 (4.62 KB)
eglevels.mq4 (3.57 KB)
etdlines.mq4 (3.96 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (7)
Violetta Novak
Violetta Novak | 22 сент. 2016 в 11:15
Спасибо огромное за статью автору! Вопрос по индикатору горизонтальных линий iglevels, при добавлении строки  #property strict выдает ошибки.  Не работает Delta.Строка  double d=iCustom(NULL,0,"itddots",level,Buffer,i); когда itddots содержит только input int Level=1; //Dot's level. Для itdlines Вы берете double current_bar_U = iCustom(NULL, 0, "itddots", Level, 0, i); то есть другим образом (там все работает). Если кто-то направил сам индикатор, подскажите как. Спасибо
Violetta Novak
Violetta Novak | 22 сент. 2016 в 12:30
И потом в iglevels  линии на графике не должны отображаться согласно коду
#property indicator_color1 clrGreen
#property indicator_type1   DRAW_LINE
#property indicator_width1 2
#property indicator_color2 clrBlue
#property indicator_type2   DRAW_LINE
#property indicator_width2 2                      а не красные? Извините, может я что-то не понимаю.
Violetta Novak
Violetta Novak | 26 сент. 2016 в 09:17
Извиняюсь, разобралась.
Pavel Trofimov
Pavel Trofimov | 30 апр. 2017 в 16:38
Как бы идея не нова, главное чтобы не была скопирована: https://www.mql5.com/ru/market/product/9420

И еще ранее, делал скрипт. https://www.mql5.com/ru/code/12439

А вот развитие в горизонтальные уровни, построение трендов и советник - зачет!

Mihail Marchukajtes
Mihail Marchukajtes | 3 мая 2017 в 23:20

Очень интересная статья, автор респект и уважуха :-)

Сам я являюсь фанатом Демарка. Потому как в его книге "Технический анализ новая наука"  описаны не только TD точки, но и многое что другое. Например классификатор пробития линии. Почемубы его не добавить в эксперта. Это что касаемо раздела о TD точках. Но там есть ещё и другие разделы которые интересно знать. Поэтому рекомендую книгу. Но и статья мне понравилась, так что спасибо за предоставленную возможность высказатся.

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

П.С. Если TD точка имеет уровень 10 то увидеть её вы сможете ТОЛЬКО через 10 бар. Другими словами чем выше уровень тем больше запаздывание точки.

Графические интерфейсы II: Элемент "Главное меню" (Глава 4) Графические интерфейсы II: Элемент "Главное меню" (Глава 4)
Это завершающая глава второй части серии о графических интерфейсах. В ней мы рассмотрим создание такого элемента управления, как «Главное меню». Будет продемонстрирован процесс его разработки и настройка обработчиков классов библиотеки для правильной реакции на действия пользователя. Также мы рассмотрим, как подключить к пунктам главного меню контекстные меню. Кроме того, затронем тему блокировки неактивных на текущий момент элементов.
Графические интерфейсы II: Настройка обработчиков событий библиотеки (Глава 3) Графические интерфейсы II: Настройка обработчиков событий библиотеки (Глава 3)
В предыдущих статьях были реализованы классы для создания всех составных частей главного меню. Теперь же настало время познакомиться с обработчиками событий в главных базовых классах и в классах созданных элементов управления. Отдельное внимание уделено управлению состоянием графика в зависимости от того, где находится курсор мыши.
Рецепты MQL5 - Программируем скользящие каналы Рецепты MQL5 - Программируем скользящие каналы
В данной статье представлен способ программирования системы равноудалённых каналов. Рассматриваются некоторые нюансы построения таких каналов. Приводится типизация каналов, предлагается способ универсального типа скользящих каналов. При реализации кода используется инструментарий ООП.
Метод площадей Метод площадей
Торговая система "Метод площадей" работает на необычной интерпретации показаний осциллятора RSI. В настоящей статье приводится индикатор, который визуализирует метод площадей, и советник, торгующий по этой системе. Статья дополнена подробными результатами тестирования советника для различных символов, таймфреймов и значений площади.