English 中文 Español Deutsch 日本語 Português
Исследование методов свечного анализа (Часть II): Автопоиск новых паттернов

Исследование методов свечного анализа (Часть II): Автопоиск новых паттернов

MetaTrader 5Тестер | 1 марта 2019, 13:50
5 100 12
Alexander Fedosov
Alexander Fedosov

Содержание

Введение

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

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

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

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


Постановка задачи

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

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

На рис.1 представлена общая схема создания нового паттерна.


Рис.1 Алгоритм создания нового паттерна.

Таким образом получается определенный набор(пул) свечей, из которых будут формироваться новые паттерны наборами по 1-3 свечи с повторениями или без. Общее количество в пуле будет равно 11 базовых свечей. Сформированные свечные модели буду анализироваться по тому же принципу, что и в первой статье.


Обновление прототипа интерфейса

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

Рис.2 Вкладка для работы со сгенерированными паттернами.

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

Рис.3 Обновленная вкладка Настройки.

  1. Добавлена нумерация простых  типов свечей и их названия. Для того чтобы лучше соотносить визуальное отображение свечей со списком Используемые свечи.
  2. Добавлен выбор языка интерфейса. Русский или английский.
  3. Список Используемых свечей для генерации паттернов. Чекбоксами можно выбирать нужные для тестирования.
  4. Группа переключаемых кнопок. Можно выбрать только одну из позиций. Сделано для того, чтобы не перегружать данными список сгенерированные паттернов в таблице вкладки Автопоиск.
  5. Две переключаемые кнопки. С повторами означает, что в паттерне может присутствовать только один тип свечи. Пример — паттерн из трех медвежьих Марибозу. При этом паттерн Волчок - Марибозу - Марибозу имеет место быть.

Реализация инструментария

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

//+------------------------------------------------------------------+
//| Создаёт группу с вкладками                                       |
//+------------------------------------------------------------------+
bool CProgram::CreateTabs(const int x_gap,const int y_gap)
  {
#define TABS1_TOTAL 3
//--- Сохраним указатель на главный элемент
   m_tabs1.MainPointer(m_window1);
//--- Свойства
   m_tabs1.IsCenterText(true);
   m_tabs1.PositionMode(TABS_TOP);
   m_tabs1.AutoXResizeMode(true);
   m_tabs1.AutoYResizeMode(true);
   m_tabs1.AutoXResizeRightOffset(3);
   m_tabs1.AutoYResizeBottomOffset(25);

//--- Добавим вкладки с указанными свойствами
   string tabs_names[TABS1_TOTAL]={"Анализ","Автопоиск","Настройки"};
   for(int i=0; i<TABS1_TOTAL; i++)
      m_tabs1.AddTab(tabs_names[i],150);
//--- Создадим элемент управления
   if(!m_tabs1.CreateTabs(x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_tabs1);
   return(true);
  }

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

//+------------------------------------------------------------------+
//| Создаёт форму для элементов управления                           |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_window1);
//--- Свойства
   m_window1.XSize(750);
   m_window1.YSize(500);
   m_window1.FontSize(9);
   m_window1.IsMovable(true);
   m_window1.CloseButtonIsUsed(true);
   m_window1.CollapseButtonIsUsed(true);
   m_window1.FullscreenButtonIsUsed(true);
   m_window1.TooltipsButtonIsUsed(true);
//--- Создание формы
   if(!m_window1.CreateWindow(m_chart_id,m_subwin,caption_text,5,5))
      return(false);
//--- Вкладки
   if(!CreateTabs(3,43))
      return(false);
//--- Вкладка Analyze
//--- Поля ввода
   if(!CreateSymbolsFilter(m_symb_filter1,10,10,"Символы",0))
      return(false);
   if(!CreateRequest(m_request1,250,10,"Поиск",0))
      return(false);
   if(!CreateRange(m_range1,485,10,"Диапазон",0))
      return(false);
//--- Комбо-боксы
   if(!CreateComboBoxTF(m_timeframes1,350,10,"Таймфрейм",0))
      return(false);
//--- Создание таблицы символов
   if(!CreateSymbTable(m_symb_table1,10,50,0))
      return(false);
//--- Создание таблицы результатов
   if(!CreateTable1(m_table1,120,50,0))
      return(false);

//--- Вкладка AutoSearch
//--- Поля ввода
   if(!CreateSymbolsFilter(m_symb_filter2,10,10,"Символы",1))
      return(false);
   if(!CreateRequest(m_request2,250,10,"Поиск",1))
      return(false);
   if(!CreateRange(m_range2,485,10,"Диапазон",1))
      return(false);
//--- Комбо-боксы
   if(!CreateComboBoxTF(m_timeframes2,350,10,"Таймфрейм",1))
      return(false);
//--- Создание таблицы символов
   if(!CreateSymbTable(m_symb_table2,10,50,1))
      return(false);
//--- Создание таблицы результатов
   if(!CreateTable2(m_table2,120,50,1))
      return(false);

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

//+------------------------------------------------------------------+
//| Создаёт элемент настройки свечи                                  |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_dark.bmp"
#resource "\\Images\\EasyAndFastGUI\\Candles\\long.bmp"
#resource "\\Images\\EasyAndFastGUI\\Candles\\short.bmp"
#resource "\\Images\\EasyAndFastGUI\\Candles\\doji.bmp"
#resource "\\Images\\EasyAndFastGUI\\Candles\\spin.bmp"
#resource "\\Images\\EasyAndFastGUI\\Candles\\maribozu.bmp"
#resource "\\Images\\EasyAndFastGUI\\Candles\\hammer.bmp"
//---
bool CProgram::CreateCandle(CPicture &pic,CButton &button,CTextLabel &candlelabel,const string candlename,const int x_gap,const int y_gap,string path)
  {
//--- Сохраним указатель на главный элемент
   pic.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(2,pic);
//--- Свойства
   pic.XSize(64);
   pic.YSize(64);
   pic.IconFile(path);
//--- Создание кнопки
   if(!pic.CreatePicture(x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,pic);
   CreateButtonPic(pic,button,"Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_dark.bmp");
   CreateNameCandle(candlelabel,x_gap,y_gap+pic.YSize(),candlename);
   return(true);
  }

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

Рис.4 Смена языка интерфейса.

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

//--- Событие выбора пункта в комбо-боксе
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      //--- Изменение таймфрейма
      if(ChangePeriod1(lparam))
         Update(true);
      //--- Изменение языка интерфейса
      if(ChangeLanguage(lparam))
         Update(true);
     }

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

//+------------------------------------------------------------------+
//| Изменение языка интерфейса                                       |
//+------------------------------------------------------------------+
bool CProgram::ChangeLanguage(const long id)
  {
//--- Проверка идентификатора элемента
   if(id!=m_lang_setting.Id())
      return(false);
   m_lang_index=m_lang_setting.GetListViewPointer().SelectedItemIndex();
//---
   if(m_lang_index==0)
     ....

Следующий раздел, который мы рассмотрим, отвечает за выбор типов простых свечей, из которых будут генерироваться паттерны для тестирования. Всего их 11 типов. Элемент управления сделан в виде списка названий используемых типов свечей и чекбоксов, являющихся признаком выбора, и реализован посредством метода CreateListView():

//+------------------------------------------------------------------+
//| Создаёт список                                                   |
//+------------------------------------------------------------------+
bool CProgram::CreateListView(const int x_gap,const int y_gap)
  {
//--- Размер списка
#define CANDLE_TOTAL 11
//--- Сохраним указатель на главный элемент
   m_listview1.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(2,m_listview1);
//--- Свойства
   m_listview1.XSize(175);
   m_listview1.YSize(250);
   m_listview1.ItemYSize(19);
   m_listview1.LabelXGap(25);
   m_listview1.LightsHover(true);
   m_listview1.CheckBoxMode(true);
   m_listview1.ListSize(CANDLE_TOTAL);
   m_listview1.AutoYResizeMode(true);
   m_listview1.AutoYResizeBottomOffset(10);
   m_listview1.FontSize(10);
//--- Заполнение списка данными
   string cand_name[CANDLE_TOTAL]=
     {
      "Длинная — бычья",
      "Длинная — медвежья",
      "Короткая — бычья",
      "Короткая — медвежья",
      "Волчок — бычья",
      "Волчок — медвежья",
      "Доджи",
      "Марибозу — бычья",
      "Марибозу — медвежья",
      "Молот — бычья",
      "Молот — медвежья"
     };
   for(int r=0; r<CANDLE_TOTAL; r++)
     {
      m_listview1.SetValue(r,(string)(r+1)+". "+cand_name[r]);
     }
//--- Создадим список
   if(!m_listview1.CreateListView(x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_listview1);
   return(true);
  }

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

Рис.5 Выбор исследуемых типов свечей и режима повтора.

Метод, отвечающий за создание переключателя Повтора, называется CreateDualButton():

//+------------------------------------------------------------------+
//| Создание переключателя Повтора                                   |
//+------------------------------------------------------------------+
bool CProgram::CreateDualButton(CButton &lbutton,CButton &rbutton,const int x_gap,const int y_gap,const string ltext,const string rtext)
  {
   CreateButton(lbutton,x_gap,y_gap,ltext);
   CreateButton(rbutton,x_gap+99,y_gap,rtext);
   return(true);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateButton(CButton &button,const int x_gap,const int y_gap,const string text)
  {
//--- Сохранить указатель на главный элемент
   button.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(2,button);
//--- Свойства
   button.XSize(100);
   button.YSize(30);
   button.Font("Trebuchet");
   button.FontSize(10);
   button.IsCenterText(true);
   button.BorderColor(C'0,100,255');
   button.BackColor(clrAliceBlue);
   button.BackColorLocked(C'50,180,75');
   button.BorderColorLocked(C'50,180,75');
   button.LabelColorLocked(clrWhite);
//--- Создадим элемент управления
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,button);
   return(true);
  }

А настройка работы элемента управления отслеживается в событии нажатия левой кнопкой мыши:

   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ....
      //--- Если нажали на кнопку
      if(lparam==m_button7.Id())
        {
         m_button7.IsLocked(true);
         m_button8.IsLocked(false);
        }
      else if(lparam==m_button8.Id())
        {
         m_button7.IsLocked(false);
         m_button8.IsLocked(true);
        }

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

Шаг 1. Установка вводных данных.

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

Рис.6 Вывод ошибки при выборе одного типа свечи и размерности два.

Шаг 2. Работа с вкладкой Автопоиск.

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

  • Выбор и поиск исследуемых валютных инструментов. Здесь в поле ввода можно ввести часть названия инструмента, либо ввести нужные через запятую и нажать кнопку Поиск. Также есть предустановленная фраза Major, которая выводит основные валютные пары. Чтобы увидеть полностью все представленные инструменты из Обзора рынка, нужно снять галочку в верхнем левом углу. Тем самым отключится любая фильтрация из окна поиска.
  • Далее выбираем из выпадающего списка нужный таймфрейм и диапазон выборки из количества свечей выбранного таймфрейма.
  • После того как вы выбрали нужный вам инструмент или список, щелчком мыши выбираете текущий для начала запуска анализа. После этого будут сформированы паттерны, настроенные вами на шаге 1, произведены расчеты и выведены в таблице. Стоит сказать, что после получения данных в таблице можно изменить таймфрейм и данные в реальном времени пересчитаются с учетом вашего изменения.

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


Рис.7 Пример расчета и получения данных на валютной паре EURUSD.  

Как видно на рис.7 в таблице Символ выбрана строчка с исследуемой валютной парой. Вверху справа установлен таймфрейм М15 и диапазон выборки в 8000 15-минутных свечей. В таблице результатов видим шесть столбцов. Здесь мы разберем только первый, а про остальные можно прочесть из первой статьи в разделе Разработка прототипа интерфейса.

Первый столбец называется Набор. В строчках мы видим запись вида [1,1,2]. Три цифры в квадратных скобках означает, что используется паттерн размерностью три. При этом в нем используются простые свечи под номерами 1 и 2 именно в том порядке, что указаны в квадратных скобках. Номера используемых свечей можно узнать во вкладке Настройки в разделе Используемые свечи.

Рис.8 Список и номера используемых свечей, при построении паттерна.  

Теперь, понимая как настраивать и запускать приложение, можно приступить к рассмотрению внутренней логики его работы. Итак, после установки вводных данных происходит нажатие на исследуемый валютный инструмент из таблицы Символ. Обработка события по нажатию на пункте списка таблицы вызывает метод ChangeSymbol2():

//--- Событие нажатия на пункте списка или таблицы
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      //--- Изменение символа
      if(ChangeSymbol1(lparam))
         Update(true);
      if(ChangeSymbol2(lparam))
         m_table2.Update(true);
     }

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

//+------------------------------------------------------------------+
//| Изменение символа                                                |
//+------------------------------------------------------------------+
bool CProgram::ChangeSymbol2(const long id)
  {
//--- Проверка идентификатора элемента
   if(id!=m_symb_table2.Id())
      return(false);
//--- Выйти, если строка не выделена
   if(m_symb_table2.SelectedItem()==WRONG_VALUE)
     {
      //--- Показать полное описание символа в статусной строке
      m_status_bar.SetValue(0,"Не выбран символ для анализа");
      m_status_bar.GetItemPointer(0).Update(true);
      return(false);
     }
//--- Получим символ
   string symbol=m_symb_table2.GetValue(0,m_symb_table2.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);
   if(!BuildingAutoSearchTable())
      return(false);
   GetPatternType(symbol,m_total_combination);
   return(true);
  }

Первый метод это BuildingAutoSearchTable(). Он отвечает за создание паттернов из списка выбранных простых типов свечей вкладки Настройки раздела Используемые свечи(рис.8) и выводит их в таблицу результатов в первый столбец.

//+------------------------------------------------------------------+
//| Перестраивает таблицу автопоиска паттернов                       |
//+------------------------------------------------------------------+
bool CProgram::BuildingAutoSearchTable(void)
  {
//---
   if(!GetCandleCombitaion())
     {
      if(m_lang_index==0)
         MessageBox("Число выбранных свечей меньше размера исследуемого паттерна!","Ошибка");
      else if(m_lang_index==1)
         MessageBox("The number of selected candles is less than the size of the studied pattern!","Error");
      return(false);
     }
//--- Удалить все строки
   m_table2.DeleteAllRows();
//--- Установим количество строк по количеству символов
   for(int i=0; i<ArraySize(m_total_combination); i++)
     {
      m_table2.AddRow(i);
      m_table2.SetValue(0,i,m_total_combination[i]);
     }
   m_table2.DeleteRow(ArraySize(m_total_combination));
//--- Обновить таблицу
   m_table2.Update(true);
   m_table2.GetScrollVPointer().Update(true);
   m_table2.GetScrollHPointer().Update(true);
   return(true);
  }
//+------------------------------------------------------------------+
//| Генерация паттернов из простых свечей                            |
//+------------------------------------------------------------------+
bool CProgram::GetCandleCombitaion(void)
  {
   string candlenumber[];
   int selected_candles=0,n;
   ArrayResize(candlenumber,m_total_candles);
//---
   for(int i=0;i<m_total_candles;i++)
     {
      if(m_listview1.GetState(i))
        {
         candlenumber[selected_candles]=(string)(i+1);
         selected_candles++;
        }
     }

   if((m_pattern_size==2 && selected_candles<2) || (m_pattern_size==3 && selected_candles<2) || selected_candles<1)
      return(false);
//--- Расчет количества комбинаций
   if(m_pattern_size>1)
      n=(m_button7.IsLocked())?(int)MathPow(selected_candles,m_pattern_size):(int)MathPow(selected_candles,m_pattern_size)-selected_candles;
   else
      n=selected_candles;
   ArrayResize(m_total_combination,n);

   n=0;
//--- Набор из одной свечи
   if(m_pattern_size==1)
     {
      for(int i=0;i<selected_candles;i++)
         m_total_combination[i]="["+candlenumber[i]+"]";
     }
//--- Набор из двух свечей
   else if(m_pattern_size==2)
     {
      //--- С повторами
      if(m_button7.IsLocked())
        {
         for(int i=0;i<selected_candles;i++)
           {
            for(int j=0;j<selected_candles;j++)
              {
               m_total_combination[n]="["+candlenumber[i]+","+candlenumber[j]+"]";
               n++;
              }
           }
        }
      //--- Без повторов
      else if(m_button8.IsLocked())
        {
         for(int i=0;i<selected_candles;i++)
           {
            for(int j=0;j<selected_candles;j++)
              {
               if(j!=i)
                 {
                  m_total_combination[n]="["+candlenumber[i]+","+candlenumber[j]+"]";
                  n++;
                 }
              }
           }
        }
     }
//--- Набор из трех свечей
   else if(m_pattern_size==3)
     {
      //--- С повторами
      if(m_button7.IsLocked())
        {
         for(int i=0;i<selected_candles;i++)
           {
            for(int j=0;j<selected_candles;j++)
              {
               for(int k=0;k<selected_candles;k++)
                 {
                  m_total_combination[n]="["+candlenumber[i]+","+candlenumber[j]+","+candlenumber[k]+"]";
                  n++;
                 }
              }
           }
        }
      //--- Без повторов
      else if(m_button8.IsLocked())
        {
         for(int i=0;i<selected_candles;i++)
           {
            for(int j=0;j<selected_candles;j++)
               for(int k=0;k<selected_candles;k++)
                 {
                  if(i==j && i==k)
                     continue;
                  m_total_combination[n]="["+candlenumber[i]+","+candlenumber[j]+","+candlenumber[k]+"]";
                  n++;
                 }
           }
        }
     }
   return(true);
  }

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

Все комбинации записываются в строковый массив m_total_combitaion[], и уже в методе BuildingAutoSearchTable() идет добавление в таблицу результатов.

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

   //--- Распознавание паттернов
   bool              GetPatternType(const string symbol);
   bool              GetPatternType(const string symbol,string &total_combination[]);

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

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

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

 Для нашей задачи нам необходим такой набор оценок для каждого сгенерированного паттерна. Поэтому в самом начале метода был объявлен массив структур RATING_SET. А размерность массива соответствует количеству сгенерированных паттернов или размеру строкового массива m_total_combitaion[].

   RATING_SET ratings[];

//---
   total_patterns=ArraySize(total_combination);
...
   ArrayResize(ratings,total_patterns);

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

struct CANDLE_STRUCTURE
  {
   double            open,high,low,close;       // OHLC
   TYPE_TREND        trend;                     //Тренд
   bool              bull;                      //Бычья свеча
   double            bodysize;                  //Размер тела
   TYPE_CANDLESTICK  type;                      //Тип свечи
  };

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

//---
   for(int i=0;i<total_patterns;i++)
     {
      StringReplace(total_combination[i],"[","");
      StringReplace(total_combination[i],"]","");
      if(m_pattern_size>1)
        {
         ushort sep=StringGetCharacter(",",0);
         StringSplit(total_combination[i],sep,elements);
        }
      ZeroMemory(ratings[i]);
      m_pattern_total[i]=0;
      if(m_pattern_size==1)
         IndexToPatternType(cand1[i],(int)total_combination[i]);
      else if(m_pattern_size==2)
        {
         IndexToPatternType(cand1[i],(int)elements[0]);
         IndexToPatternType(cand2[i],(int)elements[1]);
        }
      else if(m_pattern_size==3)
        {
         IndexToPatternType(cand1[i],(int)elements[0]);
         IndexToPatternType(cand2[i],(int)elements[1]);
         IndexToPatternType(cand3[i],(int)elements[2]);
        }
     }

В цикле перебираем каждое строковое значение из сгенерированных паттернов, убираем квадратные скобки по краям и с помощью StringSplit() по разделителю "," заносим в массив elements[]. При этом стоит обратить внимание, что эта процедура актуальна только для паттернов размерностью больше одного, так как при размерности один вид строки будет без запятой и достаточно ее очистить от квадратных скобок. Теперь рассмотрим метод IndexToPatternType(), который на вход получает CANDLE_STRUCTURE для заполнения и обработанные данные из массива сгенерированных паттернов.

void CProgram::IndexToPatternType(CANDLE_STRUCTURE &res,const int index)
  {
//--- Длинная - бычья
   if(index==1)
     {
      res.bull=true;
      res.type=CAND_LONG;
     }
//--- Длинная - медвежья
   else if(index==2)
     {
      res.bull=false;
      res.type=CAND_LONG;
     }
//--- Короткая - бычья
   else if(index==3)
     {
      res.bull=true;
      res.type=CAND_SHORT;
     }
//--- Короткая - медвежья
   else if(index==4)
     {
      res.bull=false;
      res.type=CAND_SHORT;
     }
//--- Волчок - бычья
   else if(index==5)
     {
      res.bull=true;
      res.type=CAND_SPIN_TOP;
     }
//--- Волчок - медвежья
   else if(index==6)
     {
      res.bull=false;
      res.type=CAND_SPIN_TOP;
     }
//--- Доджи
   else if(index==7)
     {
      res.bull=true;
      res.type=CAND_DOJI;
     }
//--- Марибозу - бычья
   else if(index==8)
     {
      res.bull=true;
      res.type=CAND_MARIBOZU;
     }
//--- Марибозу - медвежья
   else if(index==9)
     {
      res.bull=false;
      res.type=CAND_MARIBOZU;
     }
//--- Молот - бычья
   else if(index==10)
     {
      res.bull=true;
      res.type=CAND_HAMMER;
     }
//--- Молот - медвежья
   else if(index==11)
     {
      res.bull=false;
      res.type=CAND_HAMMER;
     }
  }

В зависимости от размерности паттерна заполняются одна, две или три структуры CANDLE_STRUCTURE, это cand1, cand2, cand3. Это видно из кода:

if(m_pattern_size==1)
         IndexToPatternType(cand1[i],(int)total_combination[i]);
      else if(m_pattern_size==2)
        {
         IndexToPatternType(cand1[i],(int)elements[0]);
         IndexToPatternType(cand2[i],(int)elements[1]);
        }
      else if(m_pattern_size==3)
        {
         IndexToPatternType(cand1[i],(int)elements[0]);
         IndexToPatternType(cand2[i],(int)elements[1]);
         IndexToPatternType(cand3[i],(int)elements[2]);
        }

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

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

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

//---
   for(int i=m_range_total2;i>5;i--)
     {
      if(m_pattern_size==1)
        {
         //--- Получаем тип текущей свечи
         GetCandleType(symbol,cur_cand,m_timeframe2,i);                                         // текущая свеча
         //---
         for(int j=0;j<total_patterns;j++)
           {
            if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull)
              {
               m_pattern_total[j]++;
               GetCategory(symbol,i-3,ratings[j],m_timeframe2);
              }
           }
        }
      else if(m_pattern_size==2)
        {
         //--- Получаем тип текущей свечи
         GetCandleType(symbol,prev_cand,m_timeframe2,i);                                        // предыдущая свеча
         GetCandleType(symbol,cur_cand,m_timeframe2,i-1);                                       // текущая свеча
         //---
         for(int j=0;j<total_patterns;j++)
           {
            if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull && 
               prev_cand.type==cand2[j].type && prev_cand.bull==cand2[j].bull)
              {
               m_pattern_total[j]++;
               GetCategory(symbol,i-4,ratings[j],m_timeframe2);
              }
           }
        }
      else if(m_pattern_size==3)
        {
         //--- Получаем тип текущей свечи
         GetCandleType(symbol,prev_cand2,m_timeframe2,i);                                       // предыдущая свеча
         GetCandleType(symbol,prev_cand,m_timeframe2,i-1);                                      // предыдущая свеча
         GetCandleType(symbol,cur_cand,m_timeframe2,i-2);                                       // текущая свеча
         //---
         for(int j=0;j<total_patterns;j++)
           {
            if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull && 
               prev_cand.type==cand2[j].type && prev_cand.bull==cand2[j].bull && 
               prev_cand2.type==cand3[j].type && prev_cand2.bull==cand3[j].bull)
              {
               m_pattern_total[j]++;
               GetCategory(symbol,i-5,ratings[j],m_timeframe2);
              }
           }
        }
     }

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

Рис.9 Расчет оценки эффективности паттерна. 

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

Итак, после поиска паттернов, подсчета их количества, а также набором данных оценка по категориям A, B, C, предстоит обработка полученных данных и внесение результатов в таблицу. Эти расчеты проводятся в методе CoefCalculation().

//---
   for(int i=0;i<total_patterns;i++)
      CoefCalculation(m_table2,i,ratings[i],m_pattern_total[i]);

 Как видно, что аргументами для данного метода являются:

  • m_table2 — ссылка на таблицу результатов, куда будут занесены итоги расчета для каждого сгенерированного паттерна.
  • i — строка таблицы.
  • ratings[i] — массив структур RATING_SET, содержащий в себе набор оценок по категориям для каждого паттерна.
  • m_pattern_total[i] — массив, содержащий в себе количество найденных паттернов каждого типа.

Рассмотрим метод чуть подробнее.

//+------------------------------------------------------------------+
//| Расчет коэффициентов оценки эффективности                        |
//+------------------------------------------------------------------+
bool CProgram::CoefCalculation(CTable &table,const int row,RATING_SET &rate,int found)
  {
   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)?NormalizeDouble((double)sum1/found*100,2):0;
   p2=(found>0)?NormalizeDouble((double)sum2/found*100,2):0;
   k1=(found>0)?NormalizeDouble((m_k1*rate.a_uptrend+m_k2*rate.b_uptrend+m_k3*rate.c_uptrend)/found,3):0;
   k2=(found>0)?NormalizeDouble((m_k1*rate.a_dntrend+m_k2*rate.b_dntrend+m_k3*rate.c_dntrend)/found,3):0;

   table.SetValue(1,row,(string)found);
   table.SetValue(2,row,(string)((double)found/m_range_total2*100),2);
   table.SetValue(3,row,(string)p1,2);
   table.SetValue(4,row,(string)p2,2);
   table.SetValue(5,row,(string)k1,2);
   table.SetValue(6,row,(string)k2,2);
//--- Обновить таблицу
   table.Update(true);
   table.GetScrollVPointer().Update(true);
   table.GetScrollHPointer().Update(true);
   return(true);
  }

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

Демонстрация работы приложения

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

Этап 1. Отбор простых моделей свечей.

Для начала выберем все возможные простые модели свечей в разделе Используемые свечи, выставим параметр "С повторами" и "Число свечей в паттерне" равным единице. А также "Пороговое значение тренда (пункты)" установим на 200. Должно получится так:

Рис.10 Первый этап настройки анализа сгенерированных паттернов.

Теперь перейдем во вкладку Автопоиск, наберем в строке поиска Major и нажмем Поиск. Далее выставим текущий таймфрейм на Н1, и выберем валютную пару GBPUSD. Результат тестирования получился следующим.

Рис.11 Результат тестирования сгенерированных паттернов с размерностью один.

Отберем шесть самые часто встречающиеся типы свечей. Это 1,2,5,6,3,4 — от большей к меньшей.

Этап 2. Тестирование паттернов с размерностью два.

Из этих простых типов свечей теперь составим паттерны с размерностью два. Для этого перейдем во вкладку Настройки и уберем галочки с 7-й по 11-ю. На этот раз выберем режим "Без Повторов", а также выставим "Число свечей в паттерне" на два. Должно получиться как рис.12 чуть ниже.

Рис.12 Настройка тестирования сгенерированных паттернов размерностью два.  

Теперь снова перейдем во вкладку Автопоиск и нажмем на GBPUSD. Мы получим различные комбинации из выбранных нами свечей и результаты их оценки. Но при низком значении "Порогового значения тренда", особенно на старших таймфреймах, очень часто результаты движения цены будут примерно одинаковы. Постепенным повышением значения вы задаете более жесткие условия отбора, и тем можно сузить круг поиска интересных свечных моделей. Например, подняв значение до 400 у меня получились такие результаты:

Рис.13 Результат тестирования при увеличение Порогового значения тренда.

В полученных результатах я искал высокое значение движения цены в одну сторону и в разы меньшую в другую. Как видно на рис.13, таких вариантов тут два — [1,4] и [2,6]. Это соответствует паттернам Длинная(бычья)—Короткая(медвежья) и Длинная(медвежья)—Волчок(медвежья). Второй вариант паттерна предпочтительней, так как его Встречаемость почти в 2 раза выше.

Последнее, что мы не протестировали, это свечные модели, состоящие из трех свечей. Так как при выборе всех возможных вариантов простых моделей свечей получится слишком большой объем данных для изучения, поэтому выберем 4 самых встречаемых типа из предыдущих выборок — 1,2,5,6. Если вы всё верно настроили, то вкладка Настройки будет выглядеть так:

Рис.14 Настройка тестирования сгенерированных паттернов размерностью три.

Как видите, "Пороговое значение тренда" было оставлено на 400. Затем переходим во вкладку Автопоиск и снова нажимаем на GBPUSD. Отбор перспективных свечных моделей ведется по тому же принципу что и ранее — высокое значение движения цены в одну сторону и в разы меньшую в другую. Это отлично видно по значению коэффициента эффективности. Например, мне попали два подряд интересных результат с выделяющимися на фоне других коэффициентами и вероятностью движения.

Рис.15 Результаты тестирования сгенерированных паттернов с размерностью три.

Это паттерны [2,5,2] и следующим за ним [2,5,5], что соответствует паттернами Длинная(медвежья)—Волчок(бычья)—Длинная(медвежья) и Длинная(медвежья)—Волчок(бычья)—Волчок(бычья). У первой свечной модели высокое значение вероятности восходящего тренда при большом, относительно других, коэффициенте эффективности. У второй однонаправленная неплохая вероятность, но эффективность уже ниже. 

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

Заключение

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

Программы, используемые в статье:

#
 Имя
Тип
Описание
1
PatternAnalyzer.mq5 Графический интерфейс
 Панель инструментов для анализа свечной модели.
2 MainWindow.mqh Библиотека  Библиотека построения графического интерфейса
3 Program.mqh Библиотека  Библиотека методов создания элементов интерфейса и расчетной части
Прикрепленные файлы |
MQL5.zip (449.32 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (12)
Aleksandr Slavskii
Aleksandr Slavskii | 17 мар. 2019 в 06:26
Alexander Fedosov:

Учту в будущем обновлении.

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

Руками делать долго, паттернов довольно таки много получается. Только на покупку выборка перспективных на М5 у меня вышла такая

         if(paternDn(4,3,2,i,close[i]))  
         if(paternDn(8,2,9,i,close[i]))
         if(paternDn(2,2,5,i,close[i]))
         if(paternDn(9,5,3,i,close[i]))
         if(paternDn(2,2,9,i,close[i]))
         if(paternDn(9,8,1,i,close[i]))
         if(paternDn(2,9,8,i,close[i]))
         if(paternDn(9,8,2,i,close[i]))
         if(paternDn(6,9,8,i,close[i]))
         if(paternDn(8,1,3,i,close[i]))
         if(paternDn(9,8,5,i,close[i]))
         if(paternDn(2,8,8,i,close[i]))
         if(paternDn(5,1,2,i,close[i]))
         if(paternDn(9,2,2,i,close[i]))
         if(paternDn(5,2,2,i,close[i]))
         if(paternDn(4,2,2,i,close[i]))
         if(paternDn(3,4,8,i,close[i]))
         if(paternDn(8,8,6,i,close[i]))
         if(paternDn(1,1,2,i,close[i]))
         if(paternDn(4,8,5,i,close[i]))
         if(paternDn(9,2,1,i,close[i]))
         if(paternDn(8,4,9,i,close[i]))
         if(paternDn(9,8,9,i,close[i]))
         if(paternDn(9,2,2,i,close[i]))
         if(paternDn(8,2,8,i,close[i]))
         if(paternDn(4,9,1,i,close[i]))
         if(paternDn(3,2,9,i,close[i]))
         if(paternDn(1,6,9,i,close[i]))
         if(paternDn(8,5,8,i,close[i]))
         if(paternDn(8,9,9,i,close[i]))
         if(paternDn(9,2,9,i,close[i]))
         if(paternDn(3,3,8,i,close[i]))
         if(paternDn(1,8,1,i,close[i]))
         if(paternDn(8,8,1,i,close[i]))
         if(paternDn(8,10,11,i,close[i]))
         if(paternDn(8,8,10,i,close[i]))
         BuffDn[i]=EMPTY_VALUE;
Alexander Fedosov
Alexander Fedosov | 17 мар. 2019 в 06:49
s22aa:

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

Руками делать долго, паттернов довольно таки много получается. Только на покупку выборка перспективных на М5 у меня вышла такая

Пару дней назад я отправил на проверку третью часть. Там библиотека для работы с паттернами. С помощью неё можно создавать индикаторы, советники. Да там и будут примеры как это можно делать.
На данный момент мне поступают пожелания по развитию Анализатора. Как соберу достаточное количество, тогда постараюсь их реализовать. 
shazulya Zaripova
shazulya Zaripova | 18 мар. 2019 в 20:22
очень познавательная информация. 
Aleksey Vyazmikin
Aleksey Vyazmikin | 19 мар. 2019 в 00:40

Оформление интерфейса понравилось.

Метод поиска надо реализовывать через дерево решений, т.е. от обратного по фактическим данным, а не гаданием с подбором "коэффициентов".

Aleksey Mavrin
Aleksey Mavrin | 1 апр. 2019 в 22:02

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

К слову о большем количестве паттернов и удобстве работы с ними есть мысли: 1. использовать для определения комплекс нескольких битовых масок и результирующего на их основе числового значения. Т.е. чтобы при работе программы не прогонять сравнение со всеми паттернами а использовать switch.  Каждый паттерн в итоге задаётся одним уникальным числом по правилам, на основе которых строятся маски. 2.  естественно ввод из файла.

Использование вычислительных возможностей MATLAB 2018 в MetaTrader 5 Использование вычислительных возможностей MATLAB 2018 в MetaTrader 5
После модернизации пакета MATLAB в 2015 году необходимо рассмотреть современный способ создания DLL-библиотек. На примере прогнозирующего индикатора в статье иллюстрируются особенности связывания MetaTrader 5 и MATLAB с использованием современных 64-х разрядных версий платформ, применяемых в настоящее время. Рассмотрение всей последовательности подключения MATLAB позволит разработчику на MQL5 быстрее создавать приложения с расширенными вычислительными возможностями, избегая «подводных камней».
Библиотека для простого и быстрого создания программ для MetaTrader (Часть I): Концепция, организация данных, первые результаты Библиотека для простого и быстрого создания программ для MetaTrader (Часть I): Концепция, организация данных, первые результаты
Разбирая огромное количество торговых стратегий, множество заказов на изготовление программ для терминалов MT5 и MT4, просматривая различные сайты по MetaTrader, я пришёл к выводу, что всё это многообразие в подавляющем своём большинстве строится на фактически одних и тех же элементарных функциях, действиях и значениях, повторяющихся от программы к программе. Результатом моей работы стала кроссплатформенная библиотека "DoEasy" для быстрого и лёгкого создания программ для МetaТrader 5 и МetaТrader 4
Библиотека для простого и быстрого создания программ для MetaTrader (Часть II): Коллекция исторических ордеров и сделок Библиотека для простого и быстрого создания программ для MetaTrader (Часть II): Коллекция исторических ордеров и сделок
В первой статье мы начали создавать большую кроссплатформенную библиотеку, целью которой является облегчение создания программ для платформ MetaTrader 5 и MetaTrader 4. Создали абстрактный объект COrder, который является базовым объектом для хранения данных исторических ордеров и сделок, а также рыночных ордеров и позиций. Теперь мы создадим все необходимые объекты для хранения данных истории счёта в коллекциях.
Синтаксический анализ MQL средствами MQL Синтаксический анализ MQL средствами MQL
Статья описывает препроцессор, сканер и парсер для синтаксического анализа исходных кодов на MQL. Реализация на MQL прилагается.