Исследования технических фигур Меррилла

19 июля 2019, 12:46
Alexander Fedosov
12
3 119

Содержание

Введение

Первая попытка создания системы ценовых фигур была сделана Робертом Леви в 1971 году. Он применял пятиконечные фигуры колебания цены и затем тестировал их на значимость. Значительных результатов он не добился, но его работы через 10 лет продолжил Артур Меррилл. 

Он разбил фигуры на две категории с очертаниями английских букв M и W. В каждой категории было по 16 фигур. В каждой категории были свои подкатегории. Также Меррилл выделил 6 подкатегорий:

  • Восходящий тренд
  • Нисходящий тренд
  • Треугольник
  • Расширение
  • Голова плечи
  • Перевернутая голова плечи

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

Теория и область применения

Для того чтобы было предельно ясно каким образом и для каких входных данных мы будем применять модель технических фигур Меррилла, необходимо разобраться что они из себя представляют. Основные две категории — это фигуры, которые выглядят в виде английских букв M и W. Они называются M-паттернами и W-паттернами. В каждой из этих категорий находиться по 16 фигур.

На рис.1 представлены 16 М-паттернов внешне, так или иначе, напоминающих букву М. Как видно из картинки, различие их состоит во взаимном расположении пяти точек, составляющих паттерн.  


Рис.1 Визуальное представление М-паттернов

Чуть ниже на рис.2 16 W-паттернов, напоминающих букву W. Общий набор из этих двух групп мы будем находить на графиках цены и индикаторах, а также исследовать, оценивать и искать возможные закономерности.

Рис.2 Визуальное представление М-паттернов

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

Чтобы было максимально ясно в какой области и как будут исследоваться технические фигуры Меррилла, приведем несколько примеров. На рис.3 представлен обычный линейный график цен валютной пары USDCAD на часовом таймфрейме. Сейчас данный тип представления используется нечасто, потому как более популярными стали японские свечи или бары.

Рис.3 Линейный график по ценам закрытия валютной пары USDCAD на часовом таймфрейме

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

Второй областью исследования будут индикаторы осцилляторного типа, такие как:

  • Average True Range (ATR) — показатель волатильности рынка. 
  • Commodity Channel Index (CCI) — индекс товарного канала, измеряющий отклонение цены инструмента от его среднестатистической цены.
  • DeMarker (DeM) — технический индикатор Демарка.
  • Force Index (FRC) — технический индикатор индекса силы.
  • Williams’ Percent Range (WPR) — процентный диапазон Вильямса, динамический индикатор, определяющий состояние перекупленности/перепроданности. 
  • Relative Strength Index (RSI) — технический индикатор "Индекс относительной силы". 
  • Momentum — технический индикатор темпа. Измеряет величину изменения цены финансового инструмента за определенный период.

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

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


Разработка инструмента для тестирования

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

Вкладка Анализ содержит:

  1. Два набора кнопок для выбора типов тестируемых технических фигур. Также имеются кнопки All M и All W для быстрого выбора/снятие группы фигур М и W типа.
  2. Набор кнопок для выбора тестируемых таймфреймов и кнопка ALL для выбора/снятия всей группы кнопок.
  3. Поле ввода "Пороговое значение тренда в пунктах". Это значение прибыли в пунктах, которую должна достичь цена максимум за три свечи после идентификации исследуемой фигуры Меррилла.
  4. Кнопка, открывающая диалоговое окно с выбором Начальной и Конечной даты и времени тестирования. 
  5. Поле ввода с чекбоксом и кнопкой представляет собой фильтр для поиска нужных торговых инструментов. Имеет один пресет — Major. Он показывает мажорные валютные пары. Чекбокс отключает фильтр и показывает все доступные торговые инструменты.
  6. Выбранные с помощью фильтра торговые инструменты в таблице, по выбору которых из списка происходит анализ технических фигур.
  7. Таблица результатов, состоящая из семи колонок: 
    • Имя фигуры. В столбце показывается имя исследуемой фигуры по Мерриллу. Например M10 или W12.
    • Найдено. Количество найденных фигур заданного типа на установленной выборке.
    • Таймфрейм. Показывает таймфрейм, на котором происходил анализ заданной фигуры.
    • P, Uptrend. Это вероятность движения цены на значение "Порогового тренда в пунктах" после появления технической фигуры в восходящем направлении.
    • P, Dntrend. Это вероятность движения цены на значение "Порогового тренда в пунктах" после появления технической фигуры в нисходящем направлении.
    • K, UpTrend/K, DnTrend. Это коэффициент, описанный в моей статье  Исследование методов свечного анализа (Часть I): Проверка существующих паттернов. Смысл его в том, что он оценивает насколько быстро цена достигает заданного профита после появления исследуемой технической фигуры в восходящем и нисходящем направлении тренда.

На рис.4 представлена визуальная реализация всех описанных выше инструментов и параметров.

Рис.4 Реализация вкладки Анализ

Теперь рассмотрим вторую вкладку, это Настройки:

  1. Используемый индикатор. Предоставляет выбор к какому из индикаторов будет применятся поиск и исследование технических фигур Меррилла.
  2. Весовые коэффициенты. Используются при расчете описанных выше коэффициентов K, UpTrend/DnTrend. 
  3. Язык интерфейса. Выпадающий список выбора языка интерфейса: русский или английский.

Внешний вид вкладки с настройками на рис.5 ниже:

Рис.5 Реализация вкладки Настройки

Последний раздел настроек использует окно "Свойств эксперта" (горячая клавиша F7) и там установлены настройки применяемых индикаторов, которые перечислены под заголовком Используемый индикатор. На рис.6 показано окно последнего раздела настроек.

Рис.6 Окно настроек используемых индикаторов

При определении настроек в этом окне нужно учитывать следующие особенности:

  • Первая настройка "Applied price" используется переменную типа перечисление ENUM_APPLIED_PRICE, которое имеет семь значений: цены открытия, закрытия, цены High и Low, а также медианная, типичная и средневзвешенная цена. Так вот,  для исследования от цены цены графика следует использовать первые четыре из этого перечисление, потому как остальные предназначены для расчетов индикаторов.
  • При намерении использовать в исследовании фигур индикаторы настройка "Applied price" будет влиять на индикаторы, которые используют в своих расчетах переменную типа ENUM_APPLIED_PRICE. А именно: ATR, CCI, RSI.

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

Для создания графического интерфейса используется метод CreateGUI(), который состоит из методов создающих главное окно интерфейса CreateWindow() и диалоговой окно CreateWindowSetting1() выбора временного диапазона для исследования.

//+------------------------------------------------------------------+
//| Создаёт графический интерфейс программы                          |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- Создание панели
   if(!CreateWindow("Merrill Patterns"))
      return(false);
//--- Создание диалогового окна
   if(!CreateWindowSetting1("Настройки диапазона дат"))
      return(false);
//--- Завершение создания GUI
   CWndEvents::CompletedGUI();
   return(true);
  }

Теперь посмотрим из чего состоят каждый из этих методов. Первым выберем главное окно интерфейса. Оно состоит из реализации вкладки Анализ, состоящей из элементов, описанных на рис.4.

//+------------------------------------------------------------------+
//| Вкладка Analyze                                                  |
//+------------------------------------------------------------------+
//--- Создание кнопок набора паттернов
   if(!CreatePatternSet(m_patterns,10,10))
      return(false);
//--- Заголовок таймфреймов
   if(!CreateTFLabel(m_text_labels[1],10,105,0))
      return(false);
//--- Создание кнопок набора таймфреймов
   if(!CreateTimeframeSet(m_timeframes,10,125,0))
      return(false);
//--- Поле поиска фильтра символом 
   if(!CreateSymbolsFilter(m_symb_filter1,m_request1,10,180,0))
      return(false);
//--- Создание кнопки выбора диапазона дат
   if(!CreateDateRange(m_request3,280,180,0))
      return(false);
//--- Создаёт поле ввода порогового значения прибыли
   if(!CreateThresholdValue(m_threshold1,400,180,100,0))
      return(false);
//--- Создание таблицы символов
   if(!CreateSymbTable(m_symb_table1,10,225,0))
      return(false);
//--- Создание таблицы результатов
   if(!CreateTable1(m_table1,120,225,0))
      return(false);

И из вкладки Настройки, описанной на рис.5

//+------------------------------------------------------------------+
//| Вкладка Settings                                                 |
//+------------------------------------------------------------------+
//---
   if(!CreateButtonsGroup1(10,50))
      return(false);
//--- Текстовые метки
   if(!CreateTextLabel(m_text_labels[0],10,100))
      return(false);
   if(!CreateTextLabel(m_text_labels[3],10,10))
      return(false);
//--- Поля ввода
   if(!CreateCoef(m_coef1,10,140,"K1",1))
      return(false);
   if(!CreateCoef(m_coef2,100,140,"K2",0.5))
      return(false);
   if(!CreateCoef(m_coef3,200,140,"K3",0.25))
      return(false);
   if(!CreateLanguageSetting(m_lang_setting,10,180,1))
      return(false);
//--- Статусная строка
   if(!CreateStatusBar(1,26))
      return(false);
//---
   return(true);
  }

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

Метод, реализующий диалоговое окно установки временной выборки, выглядит так:

//+------------------------------------------------------------------+
//| Создаёт диалоговой окно выбора диапазона дат во вкладке Анализ   |
//+------------------------------------------------------------------+
bool CProgram::CreateWindowSetting1(const string caption_text)
  {
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_window[2]);
//--- Координаты
   int x=m_request3.X();
   int y=m_request3.Y()+m_request3.YSize();
//--- Свойства
   m_window[2].XSize(372);
   m_window[2].YSize(230);
   m_window[2].WindowType(W_DIALOG);

//--- Создание формы
   if(!m_window[2].CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   if(!CreateCalendar(m_calendar1,m_window[2],10,25,D'01.01.2019',1))
      return(false);
   if(!CreateCalendar(m_calendar2,m_window[2],201,25,m_calendar2.Today(),1))
      return(false);
//---
   if(!CreateTimeEdit(m_time_edit1,m_window[2],10,200,"Время",1))
      return(false);
   if(!CreateTimeEdit(m_time_edit2,m_window[2],200,200,"Время",1))
      return(false);
//---
   return(true);
  }

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

//--- Подключение класса приложения
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| Входные параметры эксперта                                       |
//+------------------------------------------------------------------+
input ENUM_APPLIED_PRICE   Inp_Price1              =  PRICE_CLOSE;   // Applied price
input int                  Inp_ATR_Peroid          =  5;             // ATR Period
input int                  Inp_CCI_Peroid          =  5;             // CCI Period
input int                  Inp_DeM_Peroid          =  5;             // DeMarker Period
input int                  Inp_ForcePeriod         =  13;            // ForceIndex Period
input ENUM_MA_METHOD       Inp_ForceMAMethod       =  MODE_SMA;      // ForceIndex MA method
input ENUM_APPLIED_PRICE   Inp_ForceAppliedPrice   =  PRICE_CLOSE;   // ForceIndex Applied price
input ENUM_APPLIED_VOLUME  Inp_ForceAppliedVolume  =  VOLUME_TICK;   // ForceIndex Volumes
input int                  Inp_WPR_Period          =  5;             // WPR Period
input int                  Inp_RSI_Period          =  5;             // RSI Period
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//---
   program.OnInitEvent();
//--- Установим торговую панель
   if(!program.CreateGUI())
     {
      ::Print(__FUNCTION__," > Не удалось создать графический интерфейс!");
      return(INIT_FAILED);
     }
//---
   program.InitializePrice(Inp_Price1);
   program.InitializeATR(Inp_ATR_Peroid);
   program.InitializeCCI(Inp_CCI_Peroid);
   program.InitializeDeM(Inp_DeM_Peroid);
   program.InitializeForce(Inp_ForcePeriod,Inp_ForceMAMethod,Inp_ForceAppliedPrice,Inp_ForceAppliedVolume);
   program.InitializeWPR(Inp_WPR_Period);
   program.InitializeRSI(Inp_RSI_Period);
   return(INIT_SUCCEEDED);
  }

Помимо входных параметров индикаторов, в секции OnInit() идет создание графической оболочки и далее идет инициализация данных установленных в окне Свойств.Все методы передают внешние настройки во внутренние переменные.

//---
   void              InitializePrice(ENUM_APPLIED_PRICE price)    { m_applied_price=price;        }
   void              InitializeATR(int period)                    { m_atr_period=period;          }
   void              InitializeCCI(int period)                    { m_cci_period=period;          }
   void              InitializeDeM(int period)                    { m_dem_period=period;          }
   void              InitializeWPR(int period)                    { m_wpr_period=period;          }
   void              InitializeRSI(int period)                    { m_rsi_period=period;          }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::InitializeForce(int period,ENUM_MA_METHOD ma_method,ENUM_APPLIED_PRICE price,ENUM_APPLIED_VOLUME volume)
  {
   m_force_period=period;
   m_force_ma_method=ma_method;
   m_force_applied_price=price;
   m_force_applied_volume=volume;
  }
//+-----------------------------------------------------------------

После этого приложение готово к использованию и все остальные настройки передаются созданному графическому интерфейсу. Чуть выше говорилось о том, что запуск расчета происходит по выбору валютного инструмента из таблицы символов(поз.6 рис.4), также это происходит по окончанию ввода "Порогового значение тренда в пунктах" (поз.3 рис.4). Оба этих события запускаю один и тот же метод ChangeSymbol1() для начала сбора установленных данных, чтобы подготовить их для начала анализа.

//+------------------------------------------------------------------+
//| Выбор символа во вкладке Анализ                                  |
//+------------------------------------------------------------------+
bool CProgram::ChangeSymbol1(const long id)
  {
//--- Проверка идентификатора элемента
   if(id!=m_symb_table1.Id())
      return(false);
//--- Выйти, если строка не выделена
   if(m_symb_table1.SelectedItem()==WRONG_VALUE)
     {
      //--- Показать полное описание символа в статусной строке
      m_status_bar.SetValue(0,"Не выбран символ для анализа");
      m_status_bar.GetItemPointer(0).Update(true);
      return(false);
     }
//--- Получим выбранный символ
   string symbol=m_symb_table1.GetValue(0,m_symb_table1.SelectedItem());
//--- Показать полное описание символа в статусной строке
   string val=(m_lang_index==0)?"Выбранный символ: ":"Selected symbol: ";
   m_status_bar.SetValue(0,val+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
   m_status_bar.GetItemPointer(0).Update(true);
//---
   GetResult(symbol);
   return(true);
  }

Суть его работы состоит в определении выбранного торгового инструмента из таблицы символов и передача его значения в статусную строку и метод GetResult(). Рассмотрим его более подробно, потому как вся основная работа происходит именно в этом методе.

//+------------------------------------------------------------------+
//| Обработка результатов поиска паттернов                           |
//+------------------------------------------------------------------+
bool CProgram::GetResult(const string symbol)
  {
//--- Структура для оценки эффективности фигур
   RATING_SET m_coef[];
//--- Типы фигур
   PATTERN_TYPE pattern_types[];
//---
   ArrayResize(pattern_types,33);
   for(int i=0;i<33;i++)
     {
      if(i==16)
         pattern_types[i]=-1;
      if(i<16)
         pattern_types[i]=PATTERN_TYPE(i);
      if(i>16)
         pattern_types[i]=PATTERN_TYPE(i-1);
     }
//--- Определяем выбранные таймфреймы
   GetTimeframes(m_timeframes,m_cur_timeframes);
   int total=ArraySize(m_cur_timeframes);
//--- Проверка на хотя бы один выбранный таймфрейм
   if(total<1)
     {
      if(m_lang_index==0)
         MessageBox("Вы не выбрали рабочий таймфрейм!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("You have not selected a working timeframe!","Error",MB_OK);
      return(false);
     }
   int count=0;
   m_total_row=0;
//--- Удалить все строки
   m_table1.DeleteAllRows();
//--- Получение диапазона дат
   datetime start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00");
   datetime end=StringToTime(TimeToString(m_calendar2.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit2.GetHours()+":"+(string)m_time_edit2.GetMinutes()+":00");
//--- Проверка правильности установленных дат
   if(start>end || end>TimeCurrent())
     {
      if(m_lang_index==0)
         MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("Incorrect date range selected!","Error",MB_OK);
      return(false);
     }
//--- 
   for(int k=0;k<33;k++)
     {
      if(k==16)
         continue;
      //--- Получение выбранных фигур для анализа
      if(m_patterns[k].IsPressed())
        {
         ArrayResize(m_m_total,total);
         ArrayResize(m_coef,total);
         ZeroMemory(m_m_total);
         ZeroMemory(m_coef);
         count++;
         //--- Расчет по таймфреймам
         for(int j=0;j<total;j++)
           {
            double arr[];
            //--- Получение данных для анализа
            int copied=GetData(m_buttons_group1.SelectedButtonIndex(),symbol,m_cur_timeframes[j],start,end,arr);
            //---
            if(copied<9)
               MessageBox("Недостаточно данных для анализа","Ошибка",MB_OK);
            for(int i=0;i<copied;i++)
              {
               if(i>copied-9)
                  continue;
               //--- Условие поиска паттерна
               double A=arr[i];
               double B=arr[i+1];
               double C=arr[i+2];
               double D=arr[i+3];
               double E=arr[i+4];
               if(GetPatternType(A,B,C,D,E)==pattern_types[k])
                 {
                  m_m_total[j]++;
                  GetCategory(symbol,i+5,m_coef[j],m_cur_timeframes[j],m_threshold_value1);
                 }
              }
            //--- Добавление результат в таблицу
            AddRow(m_table1,m_patterns[k].LabelText(),m_coef[j],m_m_total[j],m_cur_timeframes[j]);
           }
        }
     }
//---
   if(count>0)
     {
      //---
      m_table1.DeleteRow(m_total_row);
      //--- Обновить таблицу
      m_table1.Update(true);
      m_table1.GetScrollVPointer().Update(true);
     }
   else
     {
      if(m_lang_index==0)
         MessageBox("Вы не выбрали паттерн!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("You have not chosen a pattern!","Error",MB_OK);
     }
   return(true);
  }

Для начала следует объяснить типы вводимых переменных в самом начале метода. Первая из них это структура RATING_SET.

struct RATING_SET
  {
   int               a_uptrend;
   int               b_uptrend;
   int               c_uptrend;
   int               a_dntrend;
   int               b_dntrend;
   int               c_dntrend;
  };

Она содержит 6 переменных типа int и необходима для записи в них результатов о том, как часто после идентификации фигуры цена шла в заданном направлении и как быстро ее достигала. Например для восходящего тренда и установленного порогового значение тренда в пунктах в 100 пукнтов на 5-знаке при нахождении фигуры и преодолении цены этого значения за одну свечу в переменную a_uptrend запишется единица, если цена достигла 100 пунктов за 2 свечи, то в переменную b_uptrend. В нашем метода мы будем использовать массив таких структур m_coef[].

Второй тип переменной — PATTERN_TYPE. Это перечисление, в котором собраны все типы технических фигур Меррилла.

//+------------------------------------------------------------------+
//| Тип фигуры                                                       |
//+------------------------------------------------------------------+
enum PATTERN_TYPE
  {
   M1,M2,M3,M4,M5,M6,M7,M8,
   M9,M10,M11,M12,M13,M14,M15,M16,
   W1,W2,W3,W4,W5,W6,W7,W8,
   W9,W10,W11,W12,W13,W14,W15,W16
  };

В методе применятся массив перечислений pattern_types[]. Далее идет проверка — какие таймфреймы для работы были выбраны в приложении. Эту информацию обрабатывает метод GetTimeframes().

//+------------------------------------------------------------------+
//| Получение массива выбранных таймфреймов                          |
//+------------------------------------------------------------------+
void  CProgram::GetTimeframes(CButton &buttons[],ENUM_TIMEFRAMES &timeframe[])
  {
   string tf[22]=
     {
      "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30",
      "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
     };
   int j=0;
   ArrayResize(timeframe,22);
   for(int i=0;i<22;i++)
     {
      if(buttons[i].IsPressed())
        {
         timeframe[j]=StringToTimeframe(tf[i]);
         j++;
        }
     }
   ArrayResize(timeframe,j);
  }

И записывает это в заранее заданный для этого массив таймфреймов m_cur_timeframes[]. Далее получаем временной диапазон для работы.

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

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CProgram::GetData(int index,string symb,ENUM_TIMEFRAMES tf,datetime start,datetime end,double &arr[])
  {
//---
   int Handle=INVALID_HANDLE,copied;
//--- Цена закрытия
   if(index==0)
     {
      MqlRates rt[];
      ZeroMemory(rt);
      copied=CopyRates(symb,tf,start,end,rt);
      ArrayResize(arr,copied);
      for(int i=0;i<copied;i++)
        {
         arr[i]=rt[i].close;
         if(m_applied_price==PRICE_OPEN)
            arr[i]=rt[i].open;
         else if(m_applied_price==PRICE_CLOSE)
            arr[i]=rt[i].close;
         else if(m_applied_price==PRICE_HIGH)
            arr[i]=rt[i].high;
         else if(m_applied_price==PRICE_LOW)
            arr[i]=rt[i].low;
        }
      return(copied);
     }
//--- ATR
   if(index==1)
      Handle=iRSI(symb,tf,m_atr_period,m_applied_price);
//--- CCI
   if(index==2)
      Handle=iCCI(symb,tf,m_cci_period,m_applied_price);
//--- DeMarker
   if(index==3)
      Handle=iDeMarker(symb,tf,m_dem_period);
//--- Force Index
   if(index==4)
      Handle=iDeMarker(symb,tf,m_dem_period);
//--- WPR
   if(index==5)
      Handle=iWPR(symb,tf,m_wpr_period);
//--- RSI
   if(index==6)
      Handle=iRSI(symb,tf,m_rsi_period,m_applied_price);
//---
   if(Handle==INVALID_HANDLE)
     {
      Print("Не удалось получить хендл индикатора");
      return(-1);
     }
   copied=CopyBuffer(Handle,0,start,end,arr);
   return(copied);
  }

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

//+------------------------------------------------------------------+
//| Распознавание паттернов                                          |
//+------------------------------------------------------------------+
PATTERN_TYPE CProgram::GetPatternType(double A,double B,double C,double D,double E)
  {
//--- M1
   if(B>A && A>D && D>C && C>E)
      return(M1);
//--- M2
   if(B>A && A>D && D>E && E>C)
      return(M2);
//--- M3
   if(B>D && D>A && A>C && C>E)
      return(M3);
//--- M4
   if(B>D && D>A && A>E && E>C)
      return(M4);
//--- M5
   if(D>B && B>A && A>C && C>E)
      return(M5);
//--- M6
   if(D>B && B>A && A>E && E>C)
      return(M6);
//--- M7
   if(B>D && D>C && C>A && A>E)
      return(M7);
//--- M8
   if(B>D && D>E && E>A && A>C)
      return(M8);
//--- M9
   if(D>B && B>C && C>A && A>E)
      return(M9);
//--- M10
   if(D>B && B>E && E>A && A>C)
      return(M10);
//--- M11
   if(D>E && E>B && B>A && A>C)
      return(M11);
//--- M12
   if(B>D && D>C && C>E && E>A)
      return(M12);
//--- M13
   if(B>D && D>E && E>C && C>A)
      return(M13);
//--- M14
   if(D>B && B>C && C>E && E>A)
      return(M14);
//--- M15
   if(D>B && B>E && E>C && C>A)
      return(M15);
//--- M16
   if(D>E && E>B && B>C && C>A)
      return(M16);
//--- W1
   if(A>C && C>B && B>E && E>D)
      return(W1);
//--- W2
   if(A>C && C>E && E>B && B>D)
      return(W2);
//--- W3
   if(A>E && E>C && C>B && B>D)
      return(W3);
//--- W4
   if(A>C && C>E && E>D && D>B)
      return(W4);
//--- W5
   if(A>E && E>C && C>D && D>B)
      return(W5);
//--- W6
   if(C>A && A>B && B>E && E>D)
      return(W6);
//--- W7
   if(C>A && A>E && E>B && B>D)
      return(W7);
//--- W8
   if(E>A && A>C && C>B && B>D)
      return(W8);
//--- W9
   if(C>A && A>E && E>D && D>B)
      return(W9);
//--- W10
   if(E>A && A>C && C>D && D>B)
      return(W10);
//--- W11
   if(C>E && E>A && A>B && B>D)
      return(W11);
//--- W12
   if(E>C && C>A && A>B && B>D)
      return(W12);
//--- W13
   if(C>E && E>A && A>D && D>B)
      return(W13);
//--- W14
   if(E>C && C>A && A>D && D>B)
      return(W14);
//--- W15
   if(C>E && E>D && D>A && A>B)
      return(W15);
//--- W16
   if(E>C && C>D && D>A && A>B)
      return(W16);
   return(-1);
  }

При нахождении технической фигуры происходит ее оценка посредством метода GetCategory(). Здесь-то и используется определенный ранее массив структур типа RATING_SET.

//+------------------------------------------------------------------+
//| Определение категорий прибыли                                    |
//+------------------------------------------------------------------+
bool CProgram::GetCategory(const string symbol,const int shift,RATING_SET &rate,ENUM_TIMEFRAMES timeframe,int threshold)
  {
   MqlRates rt[];
   datetime start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00");
   start+=PeriodSeconds(timeframe)*shift;
   int copied=CopyRates(symbol,timeframe,start,4,rt);
//--- Получаем данные предыдущих свечей
   if(copied<4)
      return(false);
   double high1,high2,high3,low1,low2,low3,close0,point;
   close0=rt[0].close;
   high1=rt[1].high;
   high2=rt[2].high;
   high3=rt[3].high;
   low1=rt[1].low;
   low2=rt[2].low;
   low3=rt[3].low;
   if(!SymbolInfoDouble(symbol,SYMBOL_POINT,point))
      return(false);

//--- Проверка на Uptrend
   if((int)((high1-close0)/point)>=threshold)
     {
      rate.a_uptrend++;
     }
   else if((int)((high2-close0)/point)>=threshold)
     {
      rate.b_uptrend++;
     }
   else if((int)((high3-close0)/point)>=threshold)
     {
      rate.c_uptrend++;
     }

//--- Проверка на Downtrend
   if((int)((close0-low1)/point)>=threshold)
     {
      rate.a_dntrend++;
     }
   else if((int)((close0-low2)/point)>=threshold)
     {
      rate.b_dntrend++;
     }
   else if((int)((close0-low3)/point)>=threshold)
     {
      rate.c_dntrend++;
     }
   return(true);
  }

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

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AddRow(CTable &table,string pattern_name,RATING_SET &rate,int found,ENUM_TIMEFRAMES timeframe)
  {
   int row=m_total_row;
   double p1,p2,k1,k2;
   int sum1=0,sum2=0;
   sum1=rate.a_uptrend+rate.b_uptrend+rate.c_uptrend;
   sum2=rate.a_dntrend+rate.b_dntrend+rate.c_dntrend;
//---
   p1=(found>0)?(double)sum1/found*100:0;
   p2=(found>0)?(double)sum2/found*100:0;
   k1=(found>0)?(m_k1*rate.a_uptrend+m_k2*rate.b_uptrend+m_k3*rate.c_uptrend)/found:0;
   k2=(found>0)?(m_k1*rate.a_dntrend+m_k2*rate.b_dntrend+m_k3*rate.c_dntrend)/found:0;
//---
   table.AddRow(row);
   table.SetValue(0,row,pattern_name);
   table.SetValue(1,row,(string)found);
   table.SetValue(2,row,TimeframeToString(timeframe));
   table.SetValue(3,row,DoubleToString(p1,2),2);
   table.SetValue(4,row,DoubleToString(p2,2),2);
   table.SetValue(5,row,DoubleToString(k1,2),2);
   table.SetValue(6,row,DoubleToString(k2,2),2);
   ZeroMemory(rate);
   m_total_row++;
  }

Для снятие возможных вопросов в использовании приложения в видео ниже приведены примеры расчета с различными настройками.


Рекомендации при тестировании технических фигур Меррилла:

  • Для корректной работы приложения необходимо, чтобы исторические данные для тестирования по заданному вами торговому инструменту были загружены.
  • Не рекомендуется загружать все паттерны и все таймфреймы одновременно, так как обработка результатов может занять длительное время.
  • Самые распространенные сценарии, вызывающие трудности в работе, были оснащены подсказками. Такие как — неустановка таймфрема, паттерна или неправильная дата.
  • Будьте внимательны с настройками в свойствах эксперта(рис.6), и если установки непонятны, то перечитайте статью.
  • В статье дважды была освещена тема метода расчета эффективности паттернов и приведена ссылка на статью, в которой очень подробно всё описано. Потому как устанавливая весовые коэффициенты во вкладке настройки, нужно иметь четкое понимание их влияния на оценку паттернов.

Заключение

В конце статьи приложен архив со всеми перечисленными файлами, отсортированными по папкам. Поэтому для корректной работы достаточно положить папку  MQL5 в корень терминала. Для того чтобы найти корень терминала, в котором находится папка MQL5, нужно в MetaTrader 5 нажать комбинацию клавиш  Ctrl+Shift+D или воспользоваться контекстным меню, как показано на рис.14 ниже.


Рис.7 Поиск папки MQL5 в корне терминала MetaTrader 5


Прикрепленные файлы |
MQL5.zip (8048.26 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (12)
Alexander Fedosov
Alexander Fedosov | 19 июл 2019 в 17:10
Aleksandr Masterskikh:

Отличная работа - как в части постановки задачи, и его программного решения, так и в части реализации графического интерфейса.

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

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

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

В следующей статье я постараюсь расширить возможности приложения.

Igor Makanu
Igor Makanu | 19 июл 2019 в 17:42
Alexander Fedosov:

Можно конечно было загнать всё это в структуру или массив. Но так пока проще понять с нуля в виде точек ломаной.

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

//+------------------------------------------------------------------+
//| Распознавание паттернов                                          |
//+------------------------------------------------------------------+
PATTERN_TYPE GetPatternType(double A,double B,double C,double D,double E)
  {
   if(A>B && C>A && B>E && E>D) return(W6);
   if(A>B && C>E && E>A && B>D) return(W11);
   if(A>B && E>C && C>A && B>D)return(W12);
   if(A>B && C>E && E>D && D>A)return(W15);
   if(A>B && E>C && C>D && D>A) return(W16);

//   A>C
   if(A>C && B>D && D>A && C>E) return(M3);
   if(A>C && B>D && D>E && E>A) return(M8);
   if(A>C && D>B && B>E && E>A) return(M10);
   if(A>C && C>B && B>E && E>D) return(W1);
   if(A>C && C>E && E>B && B>D) return(W2);
   if(A>C && C>E && E>D && D>B) return(W4);
   if(A>C && E>A && C>B && B>D) return(W8);
   if(A>C && E>A &&  C>D && D>B)return(W10);
//  C>A else ? 
   if(C>A && B>D && D>C && A>E) return(M7);
   if(C>A && D>B && B>C && A>E) return(M9);
   if(C>A && B>D && D>E && E>C) return(M13);
   if(C>A && D>B && B>E && E>C) return(M15);
   if(C>A && D>E && E>B && B>C) return(M16);
   if(C>A && A>E && E>B && B>D) return(W7);
   if(C>A && A>E && E>D && D>B) return(W9);
   if(C>A && E>C && A>D && D>B) return(W14);
   
   
   if(B>A && A>D && D>C && C>E) return(M1);
   if(B>A && A>D && D>E && E>C) return(M2);
   if(B>A && D>B && A>C && C>E) return(M5);
   if(B>A && D>B && A>E && E>C) return(M6);
   if(B>A && D>E && E>B && A>C) return(M11);
   

   if(B>D && D>A && A>E && E>C) return(M4);
   if(B>D && D>C && C>E && E>A) return(M12);
   if(B>D && A>E && E>C && C>B) return(W3);
   if(D>B && B>C && C>E && E>A) return(M14);
   if(D>B && A>E && E>C && C>D) return(W5);
   if(D>B && C>E && E>A && A>D) return(W13);
   
   
   return(-1);
  }

все равно должно быть компактное и универсальное решение!... но я пока его еще не вижу

Alexander Fedosov
Alexander Fedosov | 19 июл 2019 в 18:33
Igor Makanu:

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

все равно должно быть компактное и универсальное решение!... но я пока его еще не вижу

Так хорошее решение выше Дмитрий предложил. Его можно обыграть так:

В аргументах метода GetPatternType передавать размерность паттерна и всё. Он сам из этой цифры составляет все возможные комбинации и на основе этих данных ищет заданный в структуре или массиве.

Aleksandr Masterskikh
Aleksandr Masterskikh | 19 июл 2019 в 20:11
Alexander Fedosov:

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

В следующей статье я постараюсь расширить возможности приложения.

Спасибо за интересные работы!

Denis Kirichenko
Denis Kirichenko | 25 июл 2019 в 11:59

Спасибо автору за статью, интересный материал и подход.

Пару мыслей выскажу. Поиск фигуры можно производить по битовой маске. Например, для М-паттерна = 0101, а для W-паттерна = ~М-паттерн = 1010, где 0 - снижение от первой точки до второй, а 1 - рост. 

Если фигура найдена, то с самими паттернами уже будет сложнее (если использовать битовые операции). В текущем варианте паттерна у нас 5 точек. Для одной точки нужно 5 бит. Если точка в паттерне самая нижняя, то она = 00001, если самая верхняя, то она = 10000.

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

Библиотека для простого и быстрого создания программ для MetaTrader (Часть XVI): События коллекции символов Библиотека для простого и быстрого создания программ для MetaTrader (Часть XVI): События коллекции символов

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

Создаем кроссплатформенный советник-сеточник (Часть III): сетка на коррекциях с мартингейлом Создаем кроссплатформенный советник-сеточник (Часть III): сетка на коррекциях с мартингейлом

В этой статье мы попробуем создать лучший из возможных советников, работающих по принципу сеточника. Как обычно, это будет кроссплатформенный советник, способный работать как в MetaTrader 4, так и в MetaTrader 5. Первый советник был хорош всем, кроме того, что не мог принести прибыль на длительном промежутке времени. Второй советник мог работать на интервалах более нескольких лет. Но принести более 50% прибыли в год при максимальной просадке менее 50% он был не способен.

Выцарапываем профит до последнего пипса Выцарапываем профит до последнего пипса

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

Библиотека для простого и быстрого создания программ для MetaTrader (Часть XVII): Интерактивность объектов библиотеки Библиотека для простого и быстрого создания программ для MetaTrader (Часть XVII): Интерактивность объектов библиотеки

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