Расчёт интегральных характеристик излучений индикаторов

Sergey Pavlov | 29 января, 2013

Введение

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

В предыдущей моей статье "Построение излучений индикаторов в MQL5" был рассмотрен алгоритм построения излучений и перечислены основные его свойства. Напомню:

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

При этом точки излучения имеют некоторые особенности:

Галерея излучений:

Emission of DCMV Emission of iMA & iEnvelopes
Emission of DCMV Emission of iMA & iEnvelopes

Рис. 1. Образцы построения излучений. Слева: излучение индикатора DCMV. Справа: излучение индикаторов iMA и iEnvelopes.

В качестве примера расчёта интегральных характеристик излучений, возьмём конверты скользящей средней (Envelopes) и сами скользящие (Moving Average) вот с такими входными данными:

//--- внешняя переменная для хранения периода усреднения индикатора iEnvelopes
input int   ma_period=140; // период усреднения индикатора iEnvelopes
//--- массив для хранения отклонений индикатора iEnvelopes
double      ENV[]={0.01,0.0165,0.0273,0.0452,0.0747,01234,0.204,0.3373,0.5576,0.9217,1.5237};
//--- массив для хранения периодов индикатора iMA
int         MA[]={4,7,11,19,31,51,85};

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

Индикаторы выбраны, теперь приступим к созданию советника, который и будет базовой программой для анализа излучения. Нам потребуется получить рассчитанные данные от технических индикаторов iMA и iEnvelopes. Предлагаю воспользоваться вариантом рассмотренным в статье "Получение значений технических индикаторов в своих экспертах".

Для создания линий, пересечения которых мы и будем искать, достаточно задать по две точки для каждой. Следовательно, значения индикаторов достаточно получить только для двух баров (например, текущего и предыдущего). На предыдущем баре цена статична, а на текущем - динамична, поэтому с каждым тиком могут создаваться всё новые и новые точки. А вот и сам код:

//+------------------------------------------------------------------+
//|                                      emission_of_MA_envelope.mq5 |
//|                                           Copyright 2013, DC2008 |
//|                           https://www.mql5.com/ru/users/DC2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, DC2008"
#property link      "https://www.mql5.com/ru/users/DC2008"
#property version   "1.00"
//---
#include <GetIndicatorBuffers.mqh>
#include <Emission.mqh>
//--- внешняя переменная для хранения периода усреднения индикатора iEnvelopes
input int   ma_period=140;      // период усреднения индикатора iEnvelopes
//--- массив для хранения отклонений индикатора iEnvelopes
double      ENV[]={0.01,0.0165,0.0273,0.0452,0.0747,01234,0.204,0.3373,0.5576,0.9217,1.5237};
//--- массив для хранения периодов индикатора iMA
int         MA[]={4,7,11,19,31,51,85};
//--- массив для хранения указателей индикатора iMA и iEnvelopes
int         handle_MA[];
int         handle_Envelopes[];
//--- рыночная информация
datetime    T[],prevTimeBar=0;
double      H[],L[];
#define     HL(a, b) (a+b)/2
//--- экземпляры классов
CEmission      EnvMa(0,300);
PointEmission  pEmission;
//--- стили рисования точек излучения
#define     COLOR_UPPER  C'51,255,255'
#define     COLOR_LOWER  C'0,51,255'
#define     COLOR_MA     C'255,51,255'
color       colorPoint[]={COLOR_UPPER,COLOR_LOWER,COLOR_MA};
CodeColor   styleUpper={158,COLOR_UPPER,SMALL};
CodeColor   styleLower={158,COLOR_LOWER,SMALL};
CodeColor   styleMA={158,COLOR_MA,SMALL};
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArraySetAsSeries(T,true);
   ArraySetAsSeries(H,true);
   ArraySetAsSeries(L,true);
//---
   int size=ArraySize(MA);
   ArrayResize(handle_MA,size);
//--- создание указателя на объект - индикатор iMA
   for(int i=0; i<size; i++)
     {
      handle_MA[i]=iMA(NULL,0,MA[i],0,MODE_SMA,PRICE_MEDIAN);
      //--- если произошла ошибка при создании объекта, то выводим сообщение
      if(handle_MA[i]<0)
        {
         Print("Объект iMA[",MA[i],"] не создан: Ошибка исполнения = ",GetLastError());
         //--- принудительное завершение программы
         return(-1);
        }
     }
//---
   size=ArraySize(ENV);
   ArrayResize(handle_Envelopes,size);
//--- создание указателя на объект - индикатор iEnvelopes
   for(int i=0; i<size; i++)
     {
      handle_Envelopes[i]=iEnvelopes(NULL,0,ma_period,0,MODE_SMA,PRICE_MEDIAN,ENV[i]);
      //--- если произошла ошибка при создании объекта, то выводим сообщение
      if(handle_Envelopes[i]<0)
        {
         Print("Объект iEnvelopes[",ENV[i],"] не создан: Ошибка исполнения = ",GetLastError());
         //--- принудительное завершение программы
         return(-1);
        }
     }
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- рыночная информация
   CopyTime(NULL,0,0,2,T);
   CopyHigh(NULL,0,0,2,H);
   CopyLow(NULL,0,0,2,L);
//--- заполнение объявленных массивов текущими значениями из всех индикаторных буферов
   string name;
   uint GTC=GetTickCount();
//---- indicator buffers
   double   ibMA[],ibMA1[];      // массивы для индикатора iMA
   double   ibEnvelopesUpper[];  // массив для индикатора iEnvelopes (UPPER_LINE)
   double   ibEnvelopesLower[];  // массив для индикатора iEnvelopes (LOWER_LINE)
   for(int i=ArraySize(handle_MA)-1; i>=0; i--)
     {
      if(!CopyBufferAsSeries(handle_MA[i],0,0,2,true,ibMA))
         return;
      //---
      for(int j=ArraySize(handle_Envelopes)-1; j>=0; j--)
        {
         if(!GetEnvelopesBuffers(handle_Envelopes[j],0,2,ibEnvelopesUpper,ibEnvelopesLower,true))
            return;
         //--- найдём точку пересечения двух индикаторов iEnvelopes(UPPER_LINE) и iMA
         pEmission=EnvMa.CalcPoint(ibEnvelopesUpper[1],ibEnvelopesUpper[0],ibMA[1],ibMA[0],T[0]);
         if(pEmission.real) // если такая точка существует, то рисуем её на графике
           {
            name="iEnvelopes(UPPER_LINE)"+(string)j+"=iMA"+(string)i+(string)GTC;
            EnvMa.CreatePoint(name,pEmission,styleUpper);
           }
         //--- найдём точку пересечения двух индикаторов iEnvelopes(LOWER_LINE) и iMA
         pEmission=EnvMa.CalcPoint(ibEnvelopesLower[1],ibEnvelopesLower[0],ibMA[1],ibMA[0],T[0]);
         if(pEmission.real) // если такая точка существует, то рисуем её на графике
           {
            name="iEnvelopes(LOWER_LINE)"+(string)j+"=iMA"+(string)i+(string)GTC;
            EnvMa.CreatePoint(name,pEmission,styleLower);
           }
        }
      //---
      for(int j=ArraySize(handle_MA)-1; j>=0; j--)
        {
         if(i!=j)
           {
            if(!CopyBufferAsSeries(handle_MA[j],0,0,2,true,ibMA1))
               return;
            //--- найдём точку пересечения двух индикаторов iMA и iMA
            pEmission=EnvMa.CalcPoint(ibMA1[1],ibMA1[0],ibMA[1],ibMA[0],T[0]);
            if(pEmission.real) // если такая точка существует, то рисуем её на графике
              {
               name="iMA"+(string)j+"=iMA"+(string)i+(string)GTC;
               EnvMa.CreatePoint(name,pEmission,styleMA);
              }
           }
        }
     }
//--- удаление графических объектов излучения: чтобы не засорять график
   if(T[0]>prevTimeBar) // удаляем один раз в бар
     {
      int  total=ObjectsTotal(0,0,-1);
      prevTimeBar=T[0];
      for(int obj=total-1;obj>=0;obj--)
        {
         string obj_name=ObjectName(0,obj,0,OBJ_TEXT);
         datetime obj_time=(datetime)ObjectGetInteger(0,obj_name,OBJPROP_TIME);
         if(obj_time<T[0])
            ObjectDelete(0,obj_name);
        }
      Comment("Излучение © DC2008       Объектов = ",total);
     }
//---
  }

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

//+------------------------------------------------------------------+
//|                                                     Emission.mqh |
//|                                           Copyright 2013, DC2008 |
//|                           https://www.mql5.com/ru/users/DC2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, DC2008"
#property link      "https://www.mql5.com/ru/users/DC2008"
#property version   "1.00"
#define  BIG   7    // размер точки
#define  SMALL 3    // размер точки
//+------------------------------------------------------------------+
//| Структура pMABB                                                  |
//+------------------------------------------------------------------+
struct PointEmission
  {
   double            x;       // X-координата точки time
   double            y;       // Y-координата точки price
   datetime          t;       // t-координата времени точки
   bool              real;    // существует ли такая точка
  };
//+------------------------------------------------------------------+
//| Структура CodeColor                                              |
//+------------------------------------------------------------------+
struct CodeColor
  {
   long              Code;    // код символа точки 
   color             Color;   // цвет точки
   int               Width;   // размер точки
  };
//+------------------------------------------------------------------+
//| Базовый клас для излучений                                       |
//+------------------------------------------------------------------+
class CEmission
  {
private:
   int               sec;
   int               lim_Left;   // предельная дальность видимости в барах
   int               lim_Right;  // предельная дальность видимости в барах

public:
   PointEmission     CalcPoint(double   y1,  // Y-координата 1 прямой на [1] баре
                               double   y0,  // Y-координата 1 прямой на [0] баре
                               double   yy1, // Y-координата 2 прямой на [1] баре 
                               double   yy0, // Y-координата 2 прямой на [0] баре
                               datetime t0   // t-координата текущего бара Time[0]
                               );
   bool              CreatePoint(string name,            // имя точки
                                 PointEmission &point,   // координаты точки
                                 CodeColor &style);      // стиль отображения точки
                                 CEmission(int limitLeft,int limitRight);
                    ~CEmission();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CEmission::CEmission(int limitLeft,int limitRight)
  {
   sec=PeriodSeconds();
   lim_Left=limitLeft;
   lim_Right=limitRight;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CEmission::~CEmission()
  {
  }
//+------------------------------------------------------------------+
//| Метод CalcPoint класса CEmission                                 |
//+------------------------------------------------------------------+
PointEmission CEmission::CalcPoint(double   y1, // Y-координата 1 прямой на [1] баре
                                   double   y0, // Y-координата 1 прямой на [0] баре
                                   double   yy1,// Y-координата 2 прямой на [1] баре 
                                   double   yy0,// Y-координата 2 прямой на [0] баре
                                   datetime t0  // t-координата текущего бара Time[0]
                                   )
  {
   PointEmission point={NULL,NULL,NULL,false};
   double y0y1=y0-y1;
   double y1yy1=y1-yy1;
   double yy0yy1=yy0-yy1;
   double del0=yy0yy1-y0y1;
   if(MathAbs(del0)>0)
     {
      point.x=y1yy1/del0;
      if(point.x<lim_Left || point.x>lim_Right) return(point);
      point.y=y1+y0y1*y1yy1/del0;
      if(point.y<0) return(point);
      point.t=t0+(int)(point.x*sec);
      point.real=true;
      return(point);
     }
   return(point);
  }
//+------------------------------------------------------------------+
//| Метод CreatePoint класса CEmission                               |
//+------------------------------------------------------------------+
bool CEmission::CreatePoint(string name,            // имя точки
                            PointEmission &point,  // координаты точки
                            CodeColor &style)      // стиль отображения точки
  {
   if(ObjectCreate(0,name,OBJ_TEXT,0,0,0))
     {
      ObjectSetString(0,name,OBJPROP_FONT,"Wingdings");
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_CENTER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,style.Width);
      ObjectSetString(0,name,OBJPROP_TEXT,CharToString((uchar)style.Code));
      ObjectSetDouble(0,name,OBJPROP_PRICE,point.y);
      ObjectSetInteger(0,name,OBJPROP_TIME,point.t);
      ObjectSetInteger(0,name,OBJPROP_COLOR,style.Color);
      return(true);
     }
   return(false);
  }

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

Рис. 2. Исходное излучение индикаторов iMA и iEnvelopes

Рис. 2. Исходное излучение индикаторов iMA и iEnvelopes

 

Интегральные характеристики излучений

Итак, запустив на графике предложенный советник, мы получили множество точек разного цвета (см. Рис.2):

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

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

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

Рис. 3. Горизонтальные линии средней цены для каждого типа точек

Рис. 3. Горизонтальные линии средней цены для каждого типа точек

Для этого внесём в имеющийся код несколько дополнительных блоков. В раздел данных:

//--- массивы для расчёта и отображения интегральных характеристик излучения
#define     NUMBER_TYPES_POINT   3
double      sum[NUMBER_TYPES_POINT],sumprev[NUMBER_TYPES_POINT];
datetime    sum_time[NUMBER_TYPES_POINT];
int         n[NUMBER_TYPES_POINT],W[NUMBER_TYPES_POINT];
color       colorLine[]={clrAqua,clrBlue,clrMagenta};

В модуль OnTick():

//--- расчёт интегральных характеристик излучения
   ArrayInitialize(n,0);
   ArrayInitialize(sum,0.0);
   ArrayInitialize(sum_time,0.0);
   for(int obj=total-1;obj>=0;obj--)
     {
      string   obj_name=ObjectName(0,obj,0,OBJ_TEXT);
      datetime obj_time=(datetime)ObjectGetInteger(0,obj_name,OBJPROP_TIME);
      if(obj_time>T[0])
        {
         color    obj_color=(color)ObjectGetInteger(0,obj_name,OBJPROP_COLOR);
         double   obj_price=ObjectGetDouble(0,obj_name,OBJPROP_PRICE);
         for(int i=ArraySize(n)-1; i>=0; i--)
            if(obj_color==colorPoint[i])
              {
               n[i]++;
               sum[i]+=obj_price;
               sum_time[i]+=obj_time;
              }
        }
     }
//--- отображение интегральных характеристик излучения
   for(int i=ArraySize(n)-1; i>=0; i--)
     {
      if(n[i]>0)
        {
         name="H.line."+(string)i;
         ObjectCreate(0,name,OBJ_HLINE,0,0,0,0);
         ObjectSetInteger(0,name,OBJPROP_COLOR,colorLine[i]);
         ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_DASHDOT);
         ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
         ObjectSetDouble(0,name,OBJPROP_PRICE,sum[i]/n[i]);
        }
     }

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

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

Рис. 4. Маркеры в точках пересечения средней цены и среднего времени

Рис. 4. Маркеры в точках пересечения средней цены и среднего времени

Хотя нет, лучше использовать их процентное соотношение - так будет удобней анализировать. Поскольку главными точками излучения являются те, которые получились в результате пересечения индикаторов iMA и iEnvelopes, то вот их сумму и возьмём за 100%. Посмотрите что получилось:

Рис. 5. Процентное соотношение для каждого типа точек излучения

Рис. 5. Процентное соотношение для каждого типа точек излучения

Если сложить все три значения, то 100% не получится, а будет больше. Значение 34.4 фиолетового цвета на рисунке - это характеристика точек пересечения iMA и iMA в конкретный момент времени, т.е. индикатор пересекался сам с собой, но с разными входными данными. В данном случае, это значение носит справочный характер, и может быть в дальнейшем мы придумаем  как его использовать для анализа рынка.

Однако, при получении процентных соотношений количества точек, возникает новая проблема: как зафиксировать в истории ещё и процентные значения характеристик излучения? Ведь они тоже изменяются!

 

Графический анализ

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

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

Рис. 6. Интегральный канал излучения индикаторов

Рис. 6. Интегральный канал излучения индикаторов

Ну вот, вроде и нашли применение излучению "iMA & iMA" (фиолетовый цвет на графике). Мы получили новый индикатор - интегральная скользящая средняя.

Давайте вернёмся к коду советника и посмотрим какие там произошли изменения в модуле OnTick():

//--- отображение интегральных характеристик излучения
   ArrayInitialize(W,10);
   W[ArrayMaximum(n)]=20;
   W[ArrayMinimum(n)]=3;
   for(int i=ArraySize(n)-1; i>=0; i--)
     {
      if(n[i]>0)
        {
         //--- горизонтальные линии средних цен
         name="H.line."+(string)i;
         ObjectCreate(0,name,OBJ_HLINE,0,0,0,0);
         ObjectSetInteger(0,name,OBJPROP_COLOR,colorLine[i]);
         ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_DASHDOT);
         ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
         ObjectSetDouble(0,name,OBJPROP_PRICE,sum[i]/n[i]);
         //--- маркеры
         name="P."+(string)i;
         ObjectCreate(0,name,OBJ_TEXT,0,0,0);
         ObjectSetString(0,name,OBJPROP_FONT,"Wingdings");
         ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_CENTER);
         ObjectSetInteger(0,name,OBJPROP_FONTSIZE,17);
         ObjectSetString(0,name,OBJPROP_TEXT,CharToString(163));
         ObjectSetInteger(0,name,OBJPROP_COLOR,colorLine[i]);
         ObjectSetDouble(0,name,OBJPROP_PRICE,sum[i]/n[i]);
         ObjectSetInteger(0,name,OBJPROP_TIME,sum_time[i]/n[i]);
         //--- интегральные кривые
         name="T"+(string)i+".line"+(string)T[1];
         ObjectCreate(0,name,OBJ_TREND,0,0,0);
         ObjectSetInteger(0,name,OBJPROP_COLOR,colorLine[i]);
         ObjectSetInteger(0,name,OBJPROP_WIDTH,W[i]);
         if(sumprev[i]>0)
           {
            ObjectSetDouble(0,name,OBJPROP_PRICE,0,sumprev[i]);
            ObjectSetInteger(0,name,OBJPROP_TIME,0,T[1]);
            ObjectSetDouble(0,name,OBJPROP_PRICE,1,(sum[i]/n[i]));
            ObjectSetInteger(0,name,OBJPROP_TIME,1,T[0]);
           }
         //--- численные значения интегральных характеристик
         name="Text"+(string)i+".control";
         ObjectCreate(0,name,OBJ_TEXT,0,0,0);
         ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
         ObjectSetInteger(0,name,OBJPROP_FONTSIZE,30);
         ObjectSetInteger(0,name,OBJPROP_COLOR,colorLine[i]);
         string str=DoubleToString((double)n[i]/(double)(n[0]+n[1])*100,1);
         ObjectSetString(0,name,OBJPROP_TEXT,str);
         ObjectSetDouble(0,name,OBJPROP_PRICE,0,(sum[i]/n[i]));
         ObjectSetInteger(0,name,OBJPROP_TIME,0,sum_time[i]/n[i]);
        }
     }

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

Нас интересует, какой маркер находился ближе по времени к нулевому бару.

Опережение на верхней границе канала
Опережение на нижней границе канала

Рис. 7. Опережение по времени интегральных характеристик. Слева: опережение на верхней границе канала. Справа: опережение на нижней границе канала.

Решить эту задачу можно следующим образом: наложить на график цен линию по ценам (PRICE_MEDIAN), которая меняла бы цвет в зависимости от того, какой маркер (голубой или синий) ближе к последнему бару (см. Рис.7). А в код добавим вот этот блок:

//---
   if(n[ArrayMinimum(n)]>0)
     {
      datetime d[2];
      for(int j=0;j<2;j++)
        {
         d[j]=sum_time[j]/n[j];
        }
      int i=ArrayMinimum(d);

      name="Price.line"+(string)T[1];
      ObjectCreate(0,name,OBJ_TREND,0,0,0);
      ObjectSetInteger(0,name,OBJPROP_WIDTH,8);
      ObjectSetDouble(0,name,OBJPROP_PRICE,0,HL(H[1],L[1]));
      ObjectSetInteger(0,name,OBJPROP_TIME,0,T[1]);
      ObjectSetDouble(0,name,OBJPROP_PRICE,1,HL(H[0],L[0]));
      ObjectSetInteger(0,name,OBJPROP_TIME,1,T[0]);
      ObjectSetInteger(0,name,OBJPROP_COLOR,colorLine1[i]);
     }
//---

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

      //--- излучение от интегральных характеристик исходного излучения
      pEmission=EnvMa.CalcPoint(sumprev[0],sum[0]/n[0],sumprev[2],sum[2]/n[2],T[0]);
      if(pEmission.real) // если такая точка существует, то рисуем её на графике
        {
         name="test/up"+(string)GTC;
         EnvMa.CreatePoint(name,pEmission,styleUpper2);
        }
      pEmission=EnvMa.CalcPoint(sumprev[1],sum[1]/n[1],sumprev[2],sum[2]/n[2],T[0]);
      if(pEmission.real) // если такая точка существует, то рисуем её на графике
        {
         name="test/dn"+(string)GTC;
         EnvMa.CreatePoint(name,pEmission,styleLower2);
        }

А в раздел данных добавим следующее:

#define     COLOR_2_UPPER  C'102,255,255'
#define     COLOR_2_LOWER  C'51,102,255'
CodeColor   styleUpper2={178,COLOR_2_UPPER,BIG};
CodeColor   styleLower2={178,COLOR_2_LOWER,BIG};

Результат можно оценить на рисунке ниже. Появились новые точки, которые пока ни о чём не говорят.

Рис. 8. Излучение интегральных линий

Рис. 8. Излучение интегральных линий

Естественно, для новых точек так же можно построить интегральные характеристики (см. Рис.9), а от них новое излучение и так до тех пор, пока оно не выродится!

Emission Emission
Emission Emission

Рис. 9. Интегральные характеристики излучения

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

 

Таймсерии излучений

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

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

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

Причём от привычных таймсерий они отличаются тем, что данные в них не упорядочены по времени, хотя время является ключевым полем.

Таймсерии излучений

Рис. 10. Таймсерии характеристик излучения

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

//+------------------------------------------------------------------+
//|                                                 TimeEmission.mqh |
//|                                           Copyright 2013, DC2008 |
//|                           https://www.mql5.com/ru/users/DC2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, DC2008"
#property link      "https://www.mql5.com/ru/users/DC2008"
#property version   "1.00"
//---
#include <Emission.mqh>
#define ARRMAX       64
#define ARRDELTA     8
//+------------------------------------------------------------------+
//| Структура pIntegral                                              |
//+------------------------------------------------------------------+
struct pIntegral
  {
   double            y;       // Y-координата точки price (средняя цена точек с одним временем)
   datetime          t;       // t-координата времени точки
   int               n;       // n-количество точек с одним временем
  };
//+------------------------------------------------------------------+
//| Базовый клас для таймсерий излучений                             |
//+------------------------------------------------------------------+
class CTimeEmission
  {
private:
   pIntegral         time_series_Emission[]; // таймсерия излучения
   int               size_ts; // количество элементов в таймсерии 
   datetime           t[1];
public:
   //--- метод записи новых элементов в таймсерию излучения
   void              Write(PointEmission &point);
   //--- метод чтения интегральных характеристик излучения
   pIntegral         Read();
                     CTimeEmission();
                    ~CTimeEmission();
  };

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CTimeEmission::CTimeEmission()
  {
   ArrayResize(time_series_Emission,ARRMAX,ARRMAX);
   size_ts=ArraySize(time_series_Emission);
   for(int i=size_ts-1; i>=0; i--)
      time_series_Emission[i].t=0;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CTimeEmission::~CTimeEmission()
  {
  }
//+------------------------------------------------------------------+
//| Метод Write класса CTimeEmission                                 |
//+------------------------------------------------------------------+
void CTimeEmission::Write(PointEmission &point)
  {
   CopyTime(NULL,0,0,1,t);
   size_ts=ArraySize(time_series_Emission);
   for(int k=0;k<size_ts;k++)
     {
      if(time_series_Emission[k].t<t[0]) // находим первую свободную ячейку
        {
         if(k>size_ts-ARRDELTA)
           {   // если необходимо, то увеличиваем размер массива
            int narr=ArrayResize(time_series_Emission,size_ts+ARRMAX,ARRMAX);
            for(int l=size_ts-1;l<narr;l++)
               time_series_Emission[l].t=0;
           }
         time_series_Emission[k].y=point.y;
         time_series_Emission[k].t=point.t;
         time_series_Emission[k].n=1;
         return;
        }
      if(time_series_Emission[k].t==point.t) // находим первую похожую ячейку
        {
         time_series_Emission[k].y=(time_series_Emission[k].y*time_series_Emission[k].n+point.y)/(time_series_Emission[k].n+1);
         time_series_Emission[k].n++;
         return;
        }
     }
  }
//+------------------------------------------------------------------+
//| Метод Read класса CTimeEmission                                  |
//+------------------------------------------------------------------+
pIntegral CTimeEmission::Read()
  {
   CopyTime(NULL,0,0,1,t);
   pIntegral property_Emission={0.0,0,0};
   size_ts=ArraySize(time_series_Emission);
   for(int k=0;k<size_ts;k++)
     {
      if(time_series_Emission[k].t>=t[0])
        {
         property_Emission.y+=time_series_Emission[k].y*time_series_Emission[k].n;
         property_Emission.t+=(time_series_Emission[k].t-t[0])*time_series_Emission[k].n;
         property_Emission.n+=time_series_Emission[k].n;
        }
     }
   if(property_Emission.n>0)
     {
      property_Emission.y=property_Emission.y/property_Emission.n;
      property_Emission.t=property_Emission.t/property_Emission.n+t[0];
     }
   return(property_Emission);
  }

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

 

Экономный расчёт интегральных характеристик

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

//+------------------------------------------------------------------+
//|                                   emission_of_MA_envelope_ts.mq5 |
//|                                           Copyright 2013, DC2008 |
//|                           https://www.mql5.com/ru/users/DC2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, DC2008"
#property link      "https://www.mql5.com/ru/users/DC2008"
#property version   "1.00"
//---
#include <GetIndicatorBuffers.mqh>
#include <Emission.mqh>
#include <TimeEmission.mqh>
//--- количество типов точек
#define     NUMBER_TYPES_POINT   3
//--- массив для хранения периодов индикатора iMA
int      MA[]={4,7,11,19,31,51,85};
//--- внешняя переменная для хранения периода усреднения индикатора iEnvelopes
input int ma_period=140; // период усреднения индикатора iEnvelopes
//--- массив для хранения отклонений индикатора iEnvelopes
double   ENV[]={0.01,0.0165,0.0273,0.0452,0.0747,01234,0.204,0.3373,0.5576,0.9217,1.5237};
//--- массив для хранения указателей индикатора iMA
int      handle_MA[];
//--- массив для хранения указателей индикатора iEnvelopes
int      handle_Envelopes[];
//--- рыночная информация
datetime    T[],prevTimeBar=0;
double      H[],L[];
#define     HL(a, b) (a+b)/2
//--- экземпляры классов
CEmission      EnvMa(0,200);
PointEmission  pEmission;
CTimeEmission  tsMA[NUMBER_TYPES_POINT];
pIntegral      integral[NUMBER_TYPES_POINT];
//--- стили рисования точек излучения
#define     DEL            500
//--- массивы для расчёта и отображения интегральных характеристик излучения
double      sumprev[NUMBER_TYPES_POINT];
int         n[NUMBER_TYPES_POINT],W[NUMBER_TYPES_POINT];
color       colorLine[]={clrAqua,clrBlue,clrMagenta};
int         fontPoint[]={30,30,30};
int         fontMarker[]={16,16,16};
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArraySetAsSeries(T,true);
   ArraySetAsSeries(H,true);
   ArraySetAsSeries(L,true);
   ArrayInitialize(sumprev,0.0);
//---
   int size=ArraySize(MA);
   ArrayResize(handle_MA,size);
//--- создание указателя на объект - индикатор iMA
   for(int i=0; i<size; i++)
     {
      handle_MA[i]=iMA(NULL,0,MA[i],0,MODE_SMA,PRICE_MEDIAN);
      //--- если произошла ошибка при создании объекта, то выводим сообщение
      if(handle_MA[i]<0)
        {
         Print("Объект iMA[",MA[i],"] не создан: Ошибка исполнения = ",GetLastError());
         //--- принудительное завершение программы
         return(-1);
        }
     }
//+------------------------------------------------------------------+
   size=ArraySize(ENV);
   ArrayResize(handle_Envelopes,size);
//--- создание указателя на объект - индикатор iEnvelopes
   for(int i=0; i<size; i++)
     {
      handle_Envelopes[i]=iEnvelopes(NULL,0,ma_period,0,MODE_SMA,PRICE_MEDIAN,ENV[i]);
      //--- если произошла ошибка при создании объекта, то выводим сообщение
      if(handle_Envelopes[i]<0)
        {
         Print("Объект iEnvelopes[",ENV[i],"] не создан: Ошибка исполнения = ",GetLastError());
         //--- принудительное завершение программы
         return(-1);
        }
     }
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- рыночная информация
   CopyTime(NULL,0,0,2,T);
   CopyHigh(NULL,0,0,2,H);
   CopyLow(NULL,0,0,2,L);
//--- заполнение объявленных массивов текущими значениями из всех индикаторных буферов
   string name;
   uint GTC=GetTickCount();
//---- indicator buffers
   double   ibMA[],ibMA1[];      // массивы для индикатора iMA
   double   ibEnvelopesUpper[];  // массив для индикатора iEnvelopes (UPPER_LINE)
   double   ibEnvelopesLower[];  // массив для индикатора iEnvelopes (LOWER_LINE)
   for(int i=ArraySize(handle_MA)-1; i>=0; i--)
     {
      if(!CopyBufferAsSeries(handle_MA[i],0,0,2,true,ibMA))
         return;
      //---
      for(int j=ArraySize(handle_Envelopes)-1; j>=0; j--)
        {
         if(!GetEnvelopesBuffers(handle_Envelopes[j],0,2,ibEnvelopesUpper,ibEnvelopesLower,true))
            return;
         //--- найдём точку пересечения двух индикаторов iEnvelopes(UPPER_LINE) и iMA
         pEmission=EnvMa.CalcPoint(ibEnvelopesUpper[1],ibEnvelopesUpper[0],ibMA[1],ibMA[0],T[0]);
         if(pEmission.real) // если такая точка существует, то добавляем её в таймсерию излучения
            tsMA[0].Write(pEmission);
         //--- найдём точку пересечения двух индикаторов iEnvelopes(LOWER_LINE) и iMA
         pEmission=EnvMa.CalcPoint(ibEnvelopesLower[1],ibEnvelopesLower[0],ibMA[1],ibMA[0],T[0]);
         if(pEmission.real) // если такая точка существует, то добавляем её в таймсерию излучения
            tsMA[1].Write(pEmission);
        }
      //---
      for(int j=ArraySize(handle_MA)-1; j>=0; j--)
        {
         if(i!=j)
           {
            if(!CopyBufferAsSeries(handle_MA[j],0,0,2,true,ibMA1))
               return;
            //--- найдём точку пересечения двух индикаторов iMA и iMA
            pEmission=EnvMa.CalcPoint(ibMA1[1],ibMA1[0],ibMA[1],ibMA[0],T[0]);
            if(pEmission.real) // если такая точка существует, то добавляем её в таймсерию излучения
               tsMA[2].Write(pEmission);
           }
        }
     }
//--- удаление графических объектов излучения: чтобы не засорять график
   if(T[0]>prevTimeBar)
     {
      prevTimeBar=T[0];
      //---
      for(int i=ArraySize(n)-1; i>=0; i--)
         sumprev[i]=integral[i].y;
      //---
      for(int obj=ObjectsTotal(0,0,-1)-1;obj>=0;obj--)
        {
         string obj_name=ObjectName(0,obj,0,OBJ_TREND);
         datetime obj_time=(datetime)ObjectGetInteger(0,obj_name,OBJPROP_TIME);
         if(obj_time<T[0]-DEL*PeriodSeconds())
            ObjectDelete(0,obj_name);
        }
      Comment("Излучение © DC2008   Графических объектов = ",ObjectsTotal(0,0,-1));
     }
//--- расчёт интегральных характеристик излучения
   for(int i=ArraySize(n)-1; i>=0; i--)
      integral[i]=tsMA[i].Read();
//--- отображение интегральных характеристик излучения
   ArrayInitialize(W,5);
   if(integral[0].n>integral[1].n)
     {
      W[0]=20;
      W[1]=10;
     }
   else
     {
      W[0]=10;
      W[1]=20;
     }
   for(int i=ArraySize(n)-1; i>=0; i--)
     {
      //--- горизонтальные линии средних цен
      name="H.line."+(string)i;
      ObjectCreate(0,name,OBJ_HLINE,0,0,0,0);
      ObjectSetInteger(0,name,OBJPROP_COLOR,colorLine[i]);
      ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_DASHDOT);
      ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
      ObjectSetDouble(0,name,OBJPROP_PRICE,integral[i].y);
      //--- маркеры
      name="P."+(string)i;
      ObjectCreate(0,name,OBJ_TEXT,0,0,0);
      ObjectSetString(0,name,OBJPROP_FONT,"Wingdings");
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_CENTER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontMarker[i]);
      ObjectSetString(0,name,OBJPROP_TEXT,CharToString(163));
      ObjectSetInteger(0,name,OBJPROP_COLOR,colorLine[i]);
      ObjectSetDouble(0,name,OBJPROP_PRICE,integral[i].y);
      ObjectSetInteger(0,name,OBJPROP_TIME,integral[i].t);
      //--- интегральные кривые
      name="T"+(string)i+".line"+(string)T[1];
      ObjectCreate(0,name,OBJ_TREND,0,0,0);
      ObjectSetInteger(0,name,OBJPROP_COLOR,colorLine[i]);
      ObjectSetInteger(0,name,OBJPROP_WIDTH,W[i]);
      if(sumprev[i]>0)
        {
         ObjectSetDouble(0,name,OBJPROP_PRICE,0,sumprev[i]);
         ObjectSetInteger(0,name,OBJPROP_TIME,0,T[1]);
         ObjectSetDouble(0,name,OBJPROP_PRICE,1,integral[i].y);
         ObjectSetInteger(0,name,OBJPROP_TIME,1,T[0]);
        }
      //--- численные значения интегральных характеристик
      if(integral[0].n+integral[1].n>0)
        {
         name="Text"+(string)i+".control";
         ObjectCreate(0,name,OBJ_TEXT,0,0,0);
         ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
         ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontPoint[i]);
         ObjectSetInteger(0,name,OBJPROP_COLOR,colorLine[i]);
         string str=DoubleToString((double)integral[i].n/(double)(integral[0].n+integral[1].n)*100,1);
         ObjectSetString(0,name,OBJPROP_TEXT,str);
         ObjectSetDouble(0,name,OBJPROP_PRICE,0,integral[i].y);
         ObjectSetInteger(0,name,OBJPROP_TIME,0,integral[i].t);
        }
     }
  }

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

 

Как использовать интегральные характеристики в торговле

Интегральные характеристики излучений можно использовать как генератор сигналов на:

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

Рис.  11. Торговые сигналы на пересечении интегральных характеристик излучения

Рис.  11. Торговые сигналы на пересечении интегральных характеристик излучения

 

Заключение

  1. Расчёт интегральных характеристик излучений индикаторов позволяет использовать новые инструменты и методы для анализа рынка (временных рядов).
  2. Применение таймсерий излучений увеличило скорость расчёта интегральных характеристик.
  3. Появилась возможность создания автоматизированных торговых стратегий использующих излучения.