Конструктор стратегий на основе технических фигур Меррилла

Alexander Fedosov | 24 октября, 2019

Содержание

Введение

В предыдущей статье была рассмотрена модель применения технических фигур Меррилла к различным данным, таким как ценовое значение на графике валютного инструмента и значениям различных индикаторов из стандартного набора терминала MetaTrader 5: ATR, WPR, CCI, RSI и других. Был разработан графический интерфейс для исследования этой идеи. Однако он носит исследовательский характер с целью проверить и найти эффективные способы применения технических фигур. В свою очередь, способы использования найденных удачных конфигураций нужно выстраивать в торговые стратегии и тестировать. В этой статье разработаем базовый инструментарий для работы с построением торговых стратегий и их тестированием.


Формулировка задачи и прототип приложения

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

Во вкладке Конструктор будет находится основной набор элементов интерфейса необходимых для формирования торговой стратегии. Рассмотрим все основные элементы:

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

Рассмотрим теперь из чего состоит Раздел для формирования сигнала:

  1. Выбранный паттерн из набор технических фигур Меррилла.
  2. Набор из трех сигналов, к которым могут быть применимы технические фигуры. Для каждого из сигналов будет существовать возможность отключения. То есть при выборе только одного будет вход в рынок только по нему. При выборе более одного сигнала, вход осуществляется по одному из сигналов. Применение технических фигур возможно будет к списку различных индикаторов, а также к самой цене. 
  3. Установка Тейк-профит и Стоп-лосс.

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

На основе заданных выше желаемых возможностях приложения создадим прототип будущего приложения. Ниже на рис.1 представлена схема и набор элементов интерфейса для первой вкладки Конструктор:


Рис.1 Прототип и Элементы интерфейса для вкладки Конструктор.

Также для вкладки Настройки создадим прототип и расставим элементы интерфейса. 


Рис.2 Прототип и элементы интерфейса вкладки Настройки

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

name

[in]  Имя пользовательского индикатора, содержащее путь относительно корневой директории индикаторов (MQL5/Indicators/). Если индикатор находится в поддиректории, например, в MQL5/Indicators/Examples, то имя должно выглядеть соответственно, а именно – "Examples\\имя_индикатора" (обязательно указание двойного обратного слеша вместо одиночного в качестве разделителя).

Для применения стороннего индикатора к одному из сигналов необходимо к любому из сигналов в выпадающем списке применить пункт Custom как показано на рис.3 ниже:

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

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

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

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

В приложении, как видно на рис.1, присутствуют два окна: первое — это основное окно приложения и второе — это диалоговое окно Настройки диапазона дат. При этом первое имеет две вкладки Конструктор и настройки. Поэтому главный метод создания интерфейса  CreateGUI() будет сочетать в себе два метода создания окон: 

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

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

Рис.4 Диалоговое окно Настройки диапазона дат.

Окно состоит из самого диалогового окна, а также из 2 элементов календаря и двух элементов настройки начального и конечно времени. Четко определив из чего состоит этот элемент, реализуем его внутри метода CreateDate Setting().

//+------------------------------------------------------------------+
//| Создаёт диалоговое окно выбора диапазона дат                     |
//+------------------------------------------------------------------+
bool CProgram::CreateDateSetting(void)
{
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_window[1]);
//--- Координаты
   int x=m_date_range.X();
   int y=m_date_range.Y()+m_date_range.YSize();
//--- Свойства
   m_window[1].XSize(372);
   m_window[1].YSize(230);
   m_window[1].WindowType(W_DIALOG);
   m_window[1].IsMovable(true);
//--- Создание формы
   if(!m_window[1].CreateWindow(m_chart_id,m_subwin,"",x,y))
      return(false);
//---
   if(!CreateCalendar(m_calendar1,m_window[1],10,25,D'01.01.2019',1))
      return(false);
   if(!CreateCalendar(m_calendar2,m_window[1],201,25,m_calendar2.Today(),1))
      return(false);
//---
   if(!CreateTimeEdit(m_time_edit1,m_window[1],10,200,"Время",1))
      return(false);
   if(!CreateTimeEdit(m_time_edit2,m_window[1],200,200,"Время",1))
      return(false);
//---
   return(true);
}

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

//+------------------------------------------------------------------+
//| Создаёт форму для элементов управления                           |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
{
#define VERSION " 1.0"
   color caption=C'0,130,255';
   int ygap=30;
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_window[0]);
//--- Свойства
   m_window[0].XSize(900);
   m_window[0].YSize(600);
   m_window[0].FontSize(9);
   m_window[0].CloseButtonIsUsed(true);
   m_window[0].CollapseButtonIsUsed(true);
   m_window[0].CaptionColor(caption);
   m_window[0].CaptionColorHover(caption);
   m_window[0].CaptionColorLocked(caption);
//--- Создание формы
   if(!m_window[0].CreateWindow(m_chart_id,m_subwin,caption_text+VERSION,10,20))
      return(false);
//--- Вкладки
   if(!CreateTabs(150,20))
      return(false);

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

//+------------------------------------------------------------------+
//| Создаёт группу с вкладками                                       |
//+------------------------------------------------------------------+
bool CProgram::CreateTabs(const int x_gap,const int y_gap)
{
//--- Сохраним указатель на главный элемент
   m_tabs1.MainPointer(m_window[0]);
//--- Свойства
   m_tabs1.IsCenterText(true);
   m_tabs1.PositionMode(TABS_LEFT);
   m_tabs1.AutoXResizeMode(true);
   m_tabs1.AutoYResizeMode(true);
   m_tabs1.AutoYResizeBottomOffset(25);
   m_tabs1.TabsYSize(40);   
//--- Добавим вкладки с указанными свойствами
   for(int i=0; i<ArraySize(m_tabs_names); i++)
      m_tabs1.AddTab(m_tabs_names[i],150);
//--- Создадим элемент управления
   if(!m_tabs1.CreateTabs(x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_tabs1);
   return(true);
}

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

//---- Вкладка Конструктор
//--- Фильтр инструментов
   if(!CreateSymbolsFilter(10,10))
      return(false);
   if(!CreateSymbolsTable(10,45))
      return(false);
//--- Рабочий таймфрейм
   if(!CreateTextLabel(m_text_labels1[2],290,10,"Timeframe",0))
      return(false);
   if(!CreateTimeframe1(440,10))
      return(false);
//--- Диапазон дат
   if(!CreateButton(m_date_range,240,10))
      return(false);
//--- Текстовые метки
   if(!CreateTextLabel(m_text_labels1[0],int(0.35*(m_window[0].XSize()-150)-100),10+ygap,"BUY—Signal",0))
      return(false);
   if(!CreateTextLabel(m_text_labels1[1],int(0.75*(m_window[0].XSize()-150)-100),10+ygap,"SELL—Signal",0))
      return(false);
//--- Выбор паттерна
   if(!PatternType1(int(0.35*(m_window[0].XSize()-150)-100),40+ygap,0))
      return(false);
   if(!CreateCheckBox(m_checkbox[0],int(0.35*(m_window[0].XSize()-150)-120),45+ygap,"Паттерн"))
      return(false);
   if(!PatternType2(int(0.75*(m_window[0].XSize()-150)-100),40+ygap,0))
      return(false);
   if(!CreateCheckBox(m_checkbox[1],int(0.75*(m_window[0].XSize()-150)-120),45+ygap,"Паттерн"))
      return(false);
//--- Выбор применения паттернов
   if(!AppliedType1(int(0.35*(m_window[0].XSize()-150)-100),80+ygap))
      return(false);
   if(!AppliedType2(int(0.35*(m_window[0].XSize()-150)-100),50+33*2+ygap))
      return(false);
   if(!AppliedType3(int(0.35*(m_window[0].XSize()-150)-100),50+33*3+ygap))
      return(false);
   if(!AppliedType4(int(0.75*(m_window[0].XSize()-150)-100),80+ygap))
      return(false);
   if(!AppliedType5(int(0.75*(m_window[0].XSize()-150)-100),50+33*2+ygap))
      return(false);
   if(!AppliedType6(int(0.75*(m_window[0].XSize()-150)-100),50+33*3+ygap))
      return(false);
//--- Чекбоксы сигналов
   for(int i=2; i<8; i++)
   {
      if(i<5)
         if(!CreateCheckBox(m_checkbox[i],int(0.35*(m_window[0].XSize()-150)-120),50+35*(i-1)+ygap,"Сигнал "+string(i-1)))
            return(false);
      if(i>=5)
         if(!CreateCheckBox(m_checkbox[i],int(0.75*(m_window[0].XSize()-150)-120),50+35*(i-4)+ygap,"Сигнал "+string(i-4)))
            return(false);
   }
//--- Настройки установки Тейк-профит и Стоп-лосс
   if(!CreateEditValue(m_takeprofit1,int(0.35*(m_window[0].XSize()-150)-120),50+35*4+ygap,"Тейк Профит",500,0))
      return(false);
   if(!CreateEditValue(m_stoploss1,int(0.35*(m_window[0].XSize()-150)-120),50+35*5+ygap,"Стоп Лосс",500,0))
      return(false);
   if(!CreateEditValue(m_takeprofit2,int(0.75*(m_window[0].XSize()-150)-120),50+35*4+ygap,"Тейк Профит",500,0))
      return(false);
   if(!CreateEditValue(m_stoploss2,int(0.75*(m_window[0].XSize()-150)-120),50+35*5+ygap,"Стоп Лосс",500,0))
      return(false);
//--- Отчет
   if(!CreateReportFrame(m_frame[2],"",int(0.35*(m_window[0].XSize()-150)-120),60+35*6+ygap))
      return(false);
   for(int i=0; i<6; i++)
   {
      if(i<3)
         if(!CreateTextLabel(m_report_text[i],int(0.4*(m_window[0].XSize()-150)-120),60+35*(7+i)+ygap,"",0))
            return(false);
      if(i>=3)
         if(!CreateTextLabel(m_report_text[i],int(0.75*(m_window[0].XSize()-150)-120),60+35*(7+i-3)+ygap,"",0))
            return(false);
      m_report_text[i].IsCenterText(false);
   }

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

1. Фильтр инструментов.

Состоит из методов CreateSymbolsFilter() и CreateSymbolsTable(). Реализуют они следующий элемент:

Рис.5 Фильтр инструментов.

CreateSymbolsFilter()  реализует поле ввода с чекбоксом и кнопкой Поиска.

//+------------------------------------------------------------------+
//| Создаёт чекбокс с полем ввода "Symbols filter"                   |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsFilter(const int x_gap,const int y_gap)
{
//--- Сохраним указатель на главный элемент
   m_symb_filter.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_symb_filter);
//--- Свойства
   m_symb_filter.CheckBoxMode(true);
   m_symb_filter.YSize(25);
   m_symb_filter.FontSize(11);
   m_symb_filter.XSize(200);
   m_symb_filter.GetTextBoxPointer().XGap(20);
   m_symb_filter.GetTextBoxPointer().XSize(100);
   m_symb_filter.GetTextBoxPointer().YSize(25);
   m_symb_filter.GetTextBoxPointer().AutoSelectionMode(true);
   m_symb_filter.SetValue("USD"); // "EUR,USD" "EURUSD,GBPUSD" "EURUSD,GBPUSD,AUDUSD,NZDUSD,USDCHF"
//--- Создадим элемент управления
   if(!m_symb_filter.CreateTextEdit("",x_gap,y_gap))
      return(false);
//--- Включим чек-бокс
   m_symb_filter.IsPressed(true);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_symb_filter);
//---
   if(!CreateRequest(x_gap+125,y_gap))
      return(false);
   return(true);
}

CreateSymbolsTable() реализует таблицу вывода отфильтрованных валютных инструментов из Обзора рынка.

//+------------------------------------------------------------------+
//| Создаёт таблицу символов                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsTable(const int x_gap,const int y_gap)
{
#define ROWS1_TOTAL    1
//--- Сохраним указатель на главный элемент
   m_table_symb.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_table_symb);
//--- Массив ширины столбцов
   int width[1]= {119};
//--- Массив выравнивания текста в столбцах
   ENUM_ALIGN_MODE align[1]= {ALIGN_CENTER};
//--- Массив отступа текста в столбцах по оси X
   int text_x_offset[1]= {5};
//--- Свойства
   m_table_symb.XSize(120);
   m_table_symb.TableSize(1,ROWS1_TOTAL);
   m_table_symb.ColumnsWidth(width);
   m_table_symb.TextAlign(align);
   m_table_symb.FontSize(10);
   m_table_symb.TextXOffset(text_x_offset);
   m_table_symb.ShowHeaders(true);
   m_table_symb.SelectableRow(true);
   m_table_symb.IsWithoutDeselect(true);
   m_table_symb.IsZebraFormatRows(clrWhiteSmoke);
   m_table_symb.AutoYResizeMode(true);
   m_table_symb.AutoYResizeBottomOffset(3);
   m_table_symb.HeadersColor(C'0,130,255');
   m_table_symb.HeadersColorHover(clrCornflowerBlue);
   m_table_symb.HeadersTextColor(clrWhite);
   m_table_symb.BorderColor(C'0,100,255');
//--- Создадим элемент управления
   if(!m_table_symb.CreateTable(x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_table_symb);
   return(true);
}

2. Рабочий таймфрейм и кнопка Диапазон дат.

Эти элементы реализуют выбор рабочего таймфрейма для тестирования, а роль кнопки Диапазон дат проста — она открывает одноименное диалоговое окно, уже рассмотренное выше. Метод CreateButton() реализует кнопку, а методы CreateTextLabel() и CreateTimeframe1() — надпись и выпадающий список соответственно. Стоит добавить, что методы CreateButton() и CreateTextLabel() универсальные методы, которые будут использовать далее, поэтому их листинг будет рассмотрен лишь однажды здесь. На рис.6 представлены эти элементы отдельно:

Рис.6 Кнопка Диапазон дат и выбор рабочего таймфрейма.

//+------------------------------------------------------------------+
//| Создаёт текстовую метку на первой вкладке                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTextLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text,int tab)
{
//--- Сохраним указатель на окно
   text_label.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(tab,text_label);
//---
   text_label.Font("Trebuchet");
   text_label.FontSize(11);
   text_label.XSize(200);
   text_label.LabelColor(C'0,100,255');
   text_label.IsCenterText(true);
//--- Создание кнопки
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}
//+------------------------------------------------------------------+
//| Создаёт кнопку для отображения окна выборки диапазона дат        |
//+------------------------------------------------------------------+
bool CProgram::CreateButton(CButton &button,const int x_gap,const int y_gap)
{
//--- Сохранить указатель на главный элемент
   button.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,button);
//--- Свойства
   button.XSize(100);
   button.YSize(25);
   button.FontSize(11);
   button.IsHighlighted(false);
   button.IsCenterText(true);
   button.BorderColor(C'0,100,255');
   button.BackColor(clrAliceBlue);
//--- Создадим элемент управления
   if(!button.CreateButton("Диапазон дат",x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

Метод CreateTimeframe1() представляет собой выпадающий список со всеми доступными таймфреймами.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTimeframe1(const int x_gap,const int y_gap)
{
//--- Передать объект панели
   m_timeframe1.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_timeframe1);
//--- Массив значений пунктов в списке
   string timeframe_names[21]=
   {
      "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30",
      "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
   };
//--- Установим свойства перед созданием
   m_timeframe1.XSize(50);
   m_timeframe1.YSize(25);
   m_timeframe1.ItemsTotal(21);
   m_timeframe1.FontSize(12);
   m_timeframe1.LabelColor(C'0,100,255');
   CButton *but=m_timeframe1.GetButtonPointer();
   but.FontSize(10);
   but.XSize(50);
   but.BackColor(clrAliceBlue);
   but.XGap(1);
   m_timeframe1.GetListViewPointer().FontSize(10);
//--- Сохраним значения пунктов в список комбо-бокса
   for(int i=0; i<21; i++)
      m_timeframe1.SetValue(i,timeframe_names[i]);
//--- Получим указатель списка
   CListView *lv=m_timeframe1.GetListViewPointer();
//--- Установим свойства списка
   lv.LightsHover(true);
   m_timeframe1.SelectItem(0);
//--- Создадим элемент управления
   if(!m_timeframe1.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_timeframe1);
   return(true);
}

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

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

Рис.7 Текстовые метки разделов и выбор технической фигуры.

Метод CreateCheckBox() создает чекбоксы с заголовком Паттерн.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCheckBox(CCheckBox &checkbox,const int x_gap,const int y_gap,const string text)
{
//--- Сохраним указатель на главный элемент
   checkbox.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,checkbox);
//--- Свойства
   checkbox.YSize(25);
   checkbox.GreenCheckBox(true);
   checkbox.IsPressed(true);
   checkbox.FontSize(12);
   checkbox.LabelColor(C'0,100,255');
   checkbox.LabelColorPressed(C'0,100,255');
//--- Создадим элемент управления
   if(!checkbox.CreateCheckBox(text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,checkbox);
   return(true);
}

Методы PatternType1() и PatternType2() одинаковы.

//+------------------------------------------------------------------+
//| Создаёт комбо-бокс 1                                             |
//+------------------------------------------------------------------+
bool CProgram::PatternType1(const int x_gap,const int y_gap,const int tab)
{
//--- Общее количество пунктов в списке
#define ITEMS_TOTAL1 32
//--- Передать объект панели
   m_combobox1.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(tab,m_combobox1);
//--- Массив значений пунктов в списке
   string pattern_names[ITEMS_TOTAL1]=
   {
      "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"
   };
//--- Установим свойства перед созданием
   m_combobox1.XSize(200);
   m_combobox1.YSize(25);
   m_combobox1.ItemsTotal(ITEMS_TOTAL1);
   m_combobox1.GetButtonPointer().FontSize(10);
   m_combobox1.GetButtonPointer().BackColor(clrAliceBlue);
   m_combobox1.GetListViewPointer().FontSize(10);
//--- Сохраним значения пунктов в список комбо-бокса
   for(int i=0; i<ITEMS_TOTAL1; i++)
      m_combobox1.SetValue(i,pattern_names[i]);
//--- Получим указатель списка
   CListView *lv=m_combobox1.GetListViewPointer();
//--- Установим свойства списка
   lv.LightsHover(true);
   m_combobox1.SelectItem(0);
//--- Создадим элемент управления
   if(!m_combobox1.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_combobox1);
   return(true);
}

4. Выбор применения паттернов и чекбоксы сигналов.

Это блок интерфейса состоит из набора сигналов для настройки сигналов на Покупку и Продажу. Каждый блок состоит из опционального выбора от одного до трех сигналов, которые могут быть условиями входа в сделку. Состоит из методов CreateCheckBox() и AppliedTypeN().


Рис.8 Применение паттернов и чекбоксы сигналов. 

Методы AppliedType1()—AppliedType6() одинаковы по своей структуре и представляют собой выпадающий список с выбором массива данных для поиска на нем сигналов в виде технических фигур.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::AppliedType1(const int x_gap,const int y_gap)
{
//--- Передать объект панели
   m_applied1.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_applied1);
//--- Массив значений пунктов в списке
   string pattern_names[9]=
   {
      "Price","ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum","Custom"
   };
//--- Установим свойства перед созданием
   m_applied1.XSize(200);
   m_applied1.YSize(25);
   m_applied1.ItemsTotal(9);
   m_applied1.GetButtonPointer().FontSize(10);
   m_applied1.GetButtonPointer().BackColor(clrAliceBlue);
   m_applied1.GetListViewPointer().FontSize(10);
//--- Сохраним значения пунктов в список комбо-бокса
   for(int i=0; i<9; i++)
      m_applied1.SetValue(i,pattern_names[i]);
//--- Получим указатель списка
   CListView *lv=m_applied1.GetListViewPointer();
//--- Установим свойства списка
   lv.LightsHover(true);
   m_applied1.SelectItem(0);
//--- Создадим элемент управления
   if(!m_applied1.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_applied1);
   return(true);
}

5. Настройки установки Тейк-профит и Стоп-лосс.

Раздел интерфейса, позволяющий настраивать Тейк-профит и Стоп-лосс как для сигнала на покупку, так и для сигнала на продажу отдельно. Настройка осуществляется в пунктах. 

Рис.9 Поля ввода для Тейк-профит и Стоп-лосс.

Для реализации данных полей ввода используется универсальный метод CreateEditValue().

//+------------------------------------------------------------------+
//| Создаёт поле ввода                                               |
//+------------------------------------------------------------------+
bool CProgram::CreateEditValue(CTextEdit &text_edit,const int x_gap,const int y_gap,const string label_text,const int value,const int tab)
{
//--- Сохраним указатель на главный элемент
   text_edit.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(tab,text_edit);
//--- Свойства
   text_edit.XSize(210);
   text_edit.YSize(24);
   text_edit.LabelColor(C'0,100,255');
   text_edit.FontSize(12);
   text_edit.MaxValue(1000);
   text_edit.MinValue(10);
   text_edit.SpinEditMode(true);
   text_edit.SetValue((string)value);
   text_edit.GetTextBoxPointer().AutoSelectionMode(true);
   text_edit.GetTextBoxPointer().XGap(100);
//--- Создадим элемент управления
   if(!text_edit.CreateTextEdit(label_text,x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,text_edit);
   return(true);
}

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

Этот блок состоит из результатов тестирования. Для его реализации используется уже ранее рассмотренный метод CreateTextLabel()

Рис.10 Блок отчета.

На этом описание реализации вкладки Конструктор заканчивается и далее рассмотрим вкладку Настройки.

1. Настройки параметров стандартных индикаторов.

В данный раздел входят все настройки индикаторов, которые предлагаются для тестирования и анализа.

Рис.11 Блок настроек стандартных индикаторов.

Для реализации данного блока используются три метода CreateFrame() для создания визуального раздела с обводкой. А также универсальный метод поля ввода для создания параметров индикаторов CreateIndSetting() и набор методов IndicatorSetting1()IndicatorSetting4() для выпадающих списков параметров Ma Method, Volumes, Price.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateFrame(CFrame &frame,const string text,const int x_gap,const int y_gap)
{
//--- Сохраним указатель на главный элемент
   frame.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(1,frame);
//---
   frame.XSize(350);
   frame.YSize(500);
   frame.LabelColor(C'0,100,255');
   frame.BorderColor(C'0,100,255');
   frame.FontSize(11);
   frame.AutoYResizeMode(true);
   frame.AutoYResizeBottomOffset(100);
   frame.GetTextLabelPointer().XSize(250);
//--- Создадим элемент управления
   if(!frame.CreateFrame(text,x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,frame);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::IndicatorSetting1(const int x_gap,const int y_gap,const string text)
{
//--- Передать объект панели
   m_ind_set1.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(1,m_ind_set1);
//--- Массив значений пунктов в списке
   string pattern_names[4]=
   {
      "Simple","Exponential","Smoothed","Linear weighted"
   };
//--- Установим свойства перед созданием
   m_ind_set1.XSize(200);
   m_ind_set1.YSize(25);
   m_ind_set1.ItemsTotal(4);
   m_ind_set1.FontSize(12);
   m_ind_set1.LabelColor(C'0,100,255');
   CButton *but=m_ind_set1.GetButtonPointer();
   but.FontSize(10);
   but.XSize(100);
   but.BackColor(clrAliceBlue);
   m_ind_set1.GetListViewPointer().FontSize(10);
//--- Сохраним значения пунктов в список комбо-бокса
   for(int i=0; i<4; i++)
      m_ind_set1.SetValue(i,pattern_names[i]);
//--- Получим указатель списка
   CListView *lv=m_ind_set1.GetListViewPointer();
//--- Установим свойства списка
   lv.LightsHover(true);
   m_ind_set1.SelectItem(0);
//--- Создадим элемент управления
   if(!m_ind_set1.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_ind_set1);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::IndicatorSetting3(const int x_gap,const int y_gap,const string text)
{
//--- Передать объект панели
   m_ind_set3.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(1,m_ind_set3);
//--- Массив значений пунктов в списке
   string pattern_names[2]=
   {
      "Tick volume","Real Volume"
   };
//--- Установим свойства перед созданием
   m_ind_set3.XSize(200);
   m_ind_set3.YSize(25);
   m_ind_set3.ItemsTotal(2);
   m_ind_set3.FontSize(12);
   m_ind_set3.LabelColor(C'0,100,255');
   CButton *but=m_ind_set3.GetButtonPointer();
   but.FontSize(10);
   but.XSize(100);
   but.BackColor(clrAliceBlue);
   m_ind_set3.GetListViewPointer().FontSize(10);
//--- Сохраним значения пунктов в список комбо-бокса
   for(int i=0; i<2; i++)
      m_ind_set3.SetValue(i,pattern_names[i]);
//--- Получим указатель списка
   CListView *lv=m_ind_set3.GetListViewPointer();
//--- Установим свойства списка
   lv.LightsHover(true);
   lv.ItemYSize(20);
   lv.YSize(42);
   m_ind_set3.SelectItem(0);
//--- Создадим элемент управления
   if(!m_ind_set3.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_ind_set3);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::IndicatorSetting4(const int x_gap,const int y_gap,const string text)
{
//--- Передать объект панели
   m_ind_set4.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(1,m_ind_set4);
//--- Массив значений пунктов в списке
   string pattern_names[4]=
   {
      "Open","Close","High","Low"
   };
//--- Установим свойства перед созданием
   m_ind_set4.XSize(200);
   m_ind_set4.YSize(25);
   m_ind_set4.ItemsTotal(4);
   m_ind_set4.FontSize(12);
   m_ind_set4.LabelColor(C'0,100,255');
   CButton *but=m_ind_set4.GetButtonPointer();
   but.FontSize(10);
   but.XSize(100);
   but.BackColor(clrAliceBlue);
   m_ind_set4.GetListViewPointer().FontSize(10);
//--- Сохраним значения пунктов в список комбо-бокса
   for(int i=0; i<4; i++)
      m_ind_set4.SetValue(i,pattern_names[i]);
//--- Получим указатель списка
   CListView *lv=m_ind_set4.GetListViewPointer();
//--- Установим свойства списка
   lv.LightsHover(true);
   lv.ItemYSize(20);
   lv.YSize(82);
   m_ind_set4.SelectItem(1);
//--- Создадим элемент управления
   if(!m_ind_set4.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_ind_set4);
   return(true);
}

 2. Язык интерфейса.

Элемент интерфейса Язык интерфейса в виде выпадающего списка с выбором Английского или Русского языка интерфейса. За создание этого элемента отвечает  метод  LanguageSetting():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LanguageSetting(const int x_gap,const int y_gap,const string text)
{
//--- Передать объект панели
   m_language_set.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(1,m_language_set);
//--- Массив значений пунктов в списке
   string pattern_names[2]=
   {
      "Русский","English"
   };
//--- Установим свойства перед созданием
   m_language_set.XSize(200);
   m_language_set.YSize(25);
   m_language_set.ItemsTotal(2);
   m_language_set.FontSize(12);
   m_language_set.LabelColor(C'0,100,255');
   CButton *but=m_language_set.GetButtonPointer();
   but.FontSize(10);
   but.XSize(100);
   but.BackColor(clrAliceBlue);
   but.XGap(140);
   m_language_set.GetListViewPointer().FontSize(10);
//--- Сохраним значения пунктов в список комбо-бокса
   for(int i=0; i<2; i++)
      m_language_set.SetValue(i,pattern_names[i]);
//--- Получим указатель списка
   CListView *lv=m_language_set.GetListViewPointer();
//--- Установим свойства списка
   lv.LightsHover(true);
   lv.ItemYSize(20);
   lv.YSize(42);
   m_language_set.SelectItem(1);
//--- Создадим элемент управления
   if(!m_language_set.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_language_set);
   return(true);
}

3. Настройка параметров кастомных индикаторов.

Состоит из визуального раздела с заголовком и обводкой, создаваемого с помощью уже рассмотренного выше CreateFrame(), а также поле ввода для буфера индикатора посредством CreateIndSetting() и новым методом CreateCustomEdit() для ввода имени индикатора и его параметров через запятую.

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

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text)
{
//--- Сохраним указатель на главный элемент
   text_edit.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(1,text_edit);
//--- Свойства
   text_edit.XSize(100);
   text_edit.YSize(24);
   text_edit.LabelColor(C'0,100,255');
   CTextBox *box=text_edit.GetTextBoxPointer();
   box.AutoSelectionMode(true);
   box.XSize(325);
   box.XGap(1);
   box.DefaultTextColor(clrSilver);
   box.DefaultText(default_text);
//--- Создадим элемент управления
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,text_edit);
   return(true);
}

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

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

Шаг 1. Выбор языка интерфейса

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

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

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

//+------------------------------------------------------------------+
//| Изменение языка интерфейса                                       |
//+------------------------------------------------------------------+
bool CProgram::ChangeLanguage(const long id)
{
//--- Проверка идентификатора элемента
   if(id!=m_language_set.Id())
      return(false);
   m_lang_index=m_language_set.GetListViewPointer().SelectedItemIndex();
//---
   if(m_lang_index==0)
   {
      //--- Вкладка Конструктор
      m_tabs1.Text(0,"Конструктор");
      m_tabs1.Text(1,"Настройки");
      m_table_symb.SetHeaderText(0,"Символ");
      m_request.LabelText("Поиск");
      m_date_range.LabelText("Диапазон дат");
      m_timeframe1.LabelText("Таймфрейм");
      for(int i=0; i<8; i++)
      {
         if(i<2)
            m_checkbox[i].LabelText("Паттерн");
         else if(i>=2 && i<5)
            m_checkbox[i].LabelText("Сигнал "+string(i-1));
         else if(i>=5)
            m_checkbox[i].LabelText("Сигнал "+string(i-4));
      }
      m_takeprofit1.LabelText("Тейк Профит");
      m_takeprofit2.LabelText("Тейк Профит");
      m_stoploss1.LabelText("Стоп Лосс");
      m_stoploss2.LabelText("Стоп Лосс");
      m_frame[2].GetTextLabelPointer().LabelText("Отчёт");
      string report_label[6]=
      {
         "Всего трейдов: ","Короткие трейды: ","Прибыльные трейды: ",
         "Прибыль в пунктах: ","Длинные трейды: ","Убыточные трейды: "
      };
      for(int i=0; i<6; i++)
         m_report_text[i].LabelText(report_label[i]+"-");
      //--- Вкладка Настройки
      m_frame[0].GetTextLabelPointer().LabelText("Настройки стандартных индикаторов");
      m_frame[1].GetTextLabelPointer().LabelText("Настройки кастомных индикаторов");
      m_custom_buffer.LabelText("Номер буфера");
      m_custom_path.GetTextBoxPointer().DefaultText("Введите адрес индикатора");
      m_custom_param.GetTextBoxPointer().DefaultText("Введите параметры индикатора через запятую");
      m_language_set.LabelText("Язык интерфейса");
      //--- Окно Диапазон дат
      m_window[1].LabelText("Настройки диапазона дат");
      m_time_edit1.LabelText("Время");
      m_time_edit2.LabelText("Время");
      m_time_edit3.LabelText("Время");
      m_time_edit4.LabelText("Время");
      m_status_bar.SetValue(0,"Не выбран символ для анализа");
   }
   else
   {
      //--- Вкладка Конструктор
      m_tabs1.Text(0,"Constructor");
      m_tabs1.Text(1,"Settings");
      m_table_symb.SetHeaderText(0,"Symbol");
      m_request.LabelText("Search");
      m_date_range.LabelText("Date range");
      m_timeframe1.LabelText("Timeframe");
      for(int i=0; i<8; i++)
      {
         if(i<2)
            m_checkbox[i].LabelText("Pattern");
         else if(i>=2 && i<5)
            m_checkbox[i].LabelText("Signal "+string(i-1));
         else if(i>=5)
            m_checkbox[i].LabelText("Signal "+string(i-4));
      }
      m_takeprofit1.LabelText("Take Profit");
      m_takeprofit2.LabelText("Take Profit");
      m_stoploss1.LabelText("Stop Loss");
      m_stoploss2.LabelText("Stop Loss");
      m_frame[2].GetTextLabelPointer().LabelText("Report");
      string report_label[6]=
      {
         "Total trades: ","Short Trades: ","Profit Trades: ",
         "Profit in points: ","Long Trades: ","Loss Trades: "
      };
      for(int i=0; i<6; i++)
         m_report_text[i].LabelText(report_label[i]+"-");
      //--- Вкладка Настройки
      m_frame[0].GetTextLabelPointer().LabelText("Standard Indicator Settings");
      m_frame[1].GetTextLabelPointer().LabelText("Custom Indicator Settings");
      m_custom_buffer.LabelText("Buffer number");
      m_custom_path.GetTextBoxPointer().DefaultText("Enter the indicator path");
      m_custom_param.GetTextBoxPointer().DefaultText("Enter indicator parameters separated by commas");
      m_language_set.LabelText("Interface language");
      //--- Окно Диапазон дат
      m_window[1].LabelText("Date Range Settings");
      m_time_edit1.LabelText("Time");
      m_time_edit2.LabelText("Time");
      m_time_edit3.LabelText("Time");
      m_time_edit4.LabelText("Time");
      m_status_bar.SetValue(0,"No symbol selected for analysis");
   }
   return(true);
}

Шаг 2. Настройка параметров индикаторов

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

Шаг 3. Настройка таблицы символов.

Переходим во вкладку "Конструктор" и в верхней левой части настраиваем нужные инструменты, доступные из Обзора рынка. За это отвечает метод RequestData(), а событие вызова этого метода это нажатие на кнопку "Поиск".

   //--- СОбытие нажатия кнопки
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
   {
      //--- Запрос данных
      RequestData(lparam);
....
//+------------------------------------------------------------------+
//| Вывод инструментов в таблицу символов                            |
//+------------------------------------------------------------------+
bool CProgram::RequestData(const long id)
{
//--- Проверка идентификатора элемента
//---
   if(id==m_request.Id())
   {
      //--- Скрыть таблицу
      m_table_symb.Hide();
      //--- Инициализация таблицы
      GetSymbols(m_symb_filter);
      RebuildingTables(m_table_symb);
      //--- Показать таблицу
      m_table_symb.Show();
   }
   return(true);
}

Шаг 4. Выбор временного диапазона тестирования

Это также событие по нажатию на кнопку "Диапазон дат". Логика проста: открывается диалоговое окно Настройки диапазона дат.

//--- Событие нажатия кнопки
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
   {
...
      //---
      if(lparam==m_date_range.Id())
      {
         int x=m_date_range.X();
         int y=m_date_range.Y()+m_date_range.YSize();
         m_window[1].X(x);
         m_window[1].Y(y);
         m_window[1].OpenWindow();
         string val=(m_lang_index==0)?"Настройки диапазона дат":"Date Range Settings";
         m_window[1].LabelText(val);
      }
...

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

Шаг 5. Установка рабочего таймфрейма.

Рабочий таймфрем применим ко всем шести сигналам, которые можно настраивать в конструкторе.

Шаг 6. Включение сигналов на продажу/покупку и выбор технической фигуры для тестирования.

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

Рис.13 Отключение сигналов на покупку или продажу.

Также слева от текстовой метки "Паттерн" выбираем тип технической фигуры Меррилла для дальнейшего тестирования. Более подробно о том как они выглядят я уже описывал в предыдущей статье на эту тему.

Шаг 7. Выбор сигналов для тестирования и выставление Тейк-профит со Стоп-лоссом

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

Шаг 8. Запуск тестирования

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

//--- Событие нажатия на пункте списка или таблицы
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
   {
      //--- Выбор символа для дальнейшей работы
      //--- Проверка идентификатора элемента
      if(lparam==m_table_symb.Id())
      {
         //--- Выйти, если строка не выделена
         if(m_table_symb.SelectedItem()==WRONG_VALUE)
         {
            //--- Показать полное описание символа в статусной строке
            m_status_bar.SetValue(0,"Не выбран символ для анализа");
            m_status_bar.GetItemPointer(0).Update(true);
         }
         //--- Получим выбранный символ
         string symbol=m_table_symb.GetValue(0,m_table_symb.SelectedItem());
         //--- Показать полное описание символа в статусной строке
         m_status_bar.SetValue(0,"Selected symbol: "+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
         m_status_bar.GetItemPointer(0).Update(true);
         GetResult(symbol);
      }
   }

За тестирование отвечает метод GetResult(). Рассмотрим его более подробно.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::GetResult(const string symbol)
{
//--- Получение диапазона дат
   m_start_date=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00");
   m_end_date=StringToTime(TimeToString(m_calendar2.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit2.GetHours()+":"+(string)m_time_edit2.GetMinutes()+":00");
//--- Проверка правильности установленных дат
   if(m_start_date>m_end_date || m_end_date>TimeCurrent())
   {
      if(m_lang_index==0)
         MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("Incorrect date range selected!","Error",MB_OK);
      return;
   }
//--- Проверка выбора паттернов
   int buy_pat=m_combobox1.GetListViewPointer().SelectedItemIndex();
   int sell_pat=m_combobox2.GetListViewPointer().SelectedItemIndex();
   if(buy_pat==sell_pat)
   {
      if(m_lang_index==0)
         MessageBox("Паттерн на покупку и продажу не может быть одинаков!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("The pattern for buying and selling cannot be the same!","Error",MB_OK);
      return;
   }
//---
   ZeroMemory(m_report);
   datetime cur_date=m_start_date;
   string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
   int applied1=m_applied1.GetListViewPointer().SelectedItemIndex();
   int applied2=m_applied2.GetListViewPointer().SelectedItemIndex();
   int applied3=m_applied3.GetListViewPointer().SelectedItemIndex();
   int applied4=m_applied4.GetListViewPointer().SelectedItemIndex();
   int applied5=m_applied5.GetListViewPointer().SelectedItemIndex();
   int applied6=m_applied6.GetListViewPointer().SelectedItemIndex();
//---
   while(cur_date<m_end_date)
   {
      if(
         BuySignal(symbol,m_start_date,applied1,1) ||
         BuySignal(symbol,m_start_date,applied2,2) ||
         BuySignal(symbol,m_start_date,applied3,3))
      {
         CalculateBuyDeals(symbol,m_start_date);
         cur_date=m_start_date;
         continue;
      }
      if(
         SellSignal(symbol,m_start_date,applied4,1) ||
         SellSignal(symbol,m_start_date,applied5,2) ||
         SellSignal(symbol,m_start_date,applied6,3))
      {

         CalculateSellDeals(symbol,m_start_date);
         cur_date=m_start_date;
         continue;
      }
      m_start_date+=PeriodSeconds(StringToTimeframe(tf));
      cur_date=m_start_date;
   }
//--- Вывод отчета
   PrintReport();
}

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

1. Методы поиска сигналов BuySignal() и SellSignal(). Они похожи, потому рассмотрим только один.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::BuySignal(const string symbol,datetime start,int applied,int signal)
{
//--- Выходим, если сигнал на покупку выключен
   if(!m_checkbox[0].IsPressed())
      return(false);
//---
   int Handle=INVALID_HANDLE;
   string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
//--- Подготовка данных
   if(m_checkbox[signal+1].IsPressed())
   {
      //--- Цена
      if(applied==0)
      {
         MqlRates rt[];
         int sl=0,tp=0;
         POINTS pat;
         double arr[];
         int copied=CopyRates(symbol,StringToTimeframe(tf),m_start_date,5,rt);
         int app_price=m_ind_set4.GetListViewPointer().SelectedItemIndex();
         ArrayResize(arr,copied);
         //Print(m_start_date+": "+copied);
         if(copied<5)
            return(false);
         //---
         for(int i=0; i<copied; i++)
         {
            if(app_price==0)
               arr[i]=rt[i].open;
            else if(app_price==1)
               arr[i]=rt[i].close;
            else if(app_price==2)
               arr[i]=rt[i].high;
            else if(app_price==3)
               arr[i]=rt[i].low;
         }
         //--- Поиск паттерна
         pat.A=arr[0];
         pat.B=arr[1];
         pat.C=arr[2];
         pat.D=arr[3];
         pat.E=arr[4];
         //--- Если паттерн найден, то проверяем сигнал
         if(GetPatternType(pat)==m_combobox1.GetListViewPointer().SelectedItemIndex())
         {
            m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),5);
            return(true);
         }
         return(false);
      }
      //--- ATR
      if(applied==1)
         Handle=iATR(symbol,StringToTimeframe(tf),int(m_ind_setting[0].GetValue()));
      //--- CCI
      if(applied==2)
      {
         int app_price;
         switch(m_ind_set4.GetListViewPointer().SelectedItemIndex())
         {
         case  0:
            app_price=PRICE_OPEN;
            break;
         case  1:
            app_price=PRICE_CLOSE;
            break;
         case  2:
            app_price=PRICE_HIGH;
            break;
         case  3:
            app_price=PRICE_LOW;
            break;
         default:
            app_price=PRICE_CLOSE;
            break;
         }
         Handle=iCCI(symbol,StringToTimeframe(tf),int(m_ind_setting[1].GetValue()),app_price);
      }
      //--- DeMarker
      if(applied==3)
         Handle=iDeMarker(symbol,StringToTimeframe(tf),int(m_ind_setting[2].GetValue()));
      //--- Force Index
      if(applied==4)
      {
         int force_period=int(m_ind_setting[3].GetValue());
         ENUM_MA_METHOD force_ma_method;
         ENUM_APPLIED_VOLUME force_applied_volume;
         switch(m_ind_set1.GetListViewPointer().SelectedItemIndex())
         {
         case  0:
            force_ma_method=MODE_SMA;
            break;
         case  1:
            force_ma_method=MODE_EMA;
            break;
         case  2:
            force_ma_method=MODE_SMMA;
            break;
         case  3:
            force_ma_method=MODE_LWMA;
            break;
         default:
            force_ma_method=MODE_SMA;
            break;
         }
         switch(m_ind_set3.GetListViewPointer().SelectedItemIndex())
         {
         case  0:
            force_applied_volume=VOLUME_TICK;
            break;
         case  1:
            force_applied_volume=VOLUME_REAL;
            break;
         default:
            force_applied_volume=VOLUME_TICK;
            break;
         }
         Handle=iForce(symbol,StringToTimeframe(tf),force_period,force_ma_method,force_applied_volume);
      }
      //--- WPR
      if(applied==5)
         Handle=iWPR(symbol,StringToTimeframe(tf),int(m_ind_setting[5].GetValue()));
      //--- RSI
      if(applied==6)
         Handle=iRSI(symbol,StringToTimeframe(tf),int(m_ind_setting[4].GetValue()),PRICE_CLOSE);
      //--- Momentum
      if(applied==7)
         Handle=iMomentum(symbol,StringToTimeframe(tf),int(m_ind_setting[6].GetValue()),PRICE_CLOSE);
      //--- Custom
      if(applied==8)
      {
         string str[];
         double arr[];
         string parameters=m_custom_param.GetValue();
         StringSplit(parameters,',',str);
         if(ArraySize(str)>20)
         {
            if(m_lang_index==0)
               MessageBox("Количество параметров не должно быть больше 20!","Ошибка",MB_OK);
            else if(m_lang_index==1)
               MessageBox("The number of parameters should not be more than 20!","Error",MB_OK);
         }
         ArrayResize(arr,ArraySize(str));
         for(int i=0; i<ArraySize(str); i++)
            arr[i]=StringToDouble(str[i]);
         string name=m_custom_path.GetValue();
         Handle=GetCustomValue(StringToTimeframe(tf),name,arr);
      }
      //---
      if(applied>0)
      {
         if(Handle==INVALID_HANDLE)
         {
            if(m_lang_index==0)
               MessageBox("Не удалось получить хендл индикатора!","Ошибка",MB_OK);
            else if(m_lang_index==1)
               MessageBox("Failed to get handle indicator!","Error",MB_OK);
         }
         double arr[];
         int buffer=(applied==8)?int(m_custom_buffer.GetValue()):0;
         int copied=CopyBuffer(Handle,buffer,m_start_date,5,arr);
         //---
         int sl=0,tp=0;
         POINTS pat;
         if(copied<5)
            return(false);
         //--- Условие поиска паттерна
         pat.A=arr[0];
         pat.B=arr[1];
         pat.C=arr[2];
         pat.D=arr[3];
         pat.E=arr[4];
         //--- Если паттерн найден, то проверяем сигнал
         if(GetPatternType(pat)==m_combobox1.GetListViewPointer().SelectedItemIndex())
         {
            m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),5);
            return(true);
         }
         return(false);
      }
      return(false);
   }
   return(false);
}

Суть данного метода в заданной последовательности действий:

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

2. Методы обработки найденного сигнала CalculateBuyDeals() и CalculateSellDeals().

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::CalculateBuyDeals(const string symbol,datetime start)
{
   MqlRates rt[];
   int TP=int(m_takeprofit1.GetValue());
   int SL=int(m_stoploss1.GetValue());
   string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
   int copied=CopyRates(symbol,StringToTimeframe(tf),m_start_date,m_end_date,rt);
   double deal_price=iOpen(symbol,StringToTimeframe(tf),copied);
   for(int j=0; j<copied; j++)
   {
      if((iHigh(symbol,StringToTimeframe(tf),copied-j)-deal_price)/SymbolInfoDouble(symbol,SYMBOL_POINT)>=TP)
      {
         m_report.profit_trades++;
         m_report.profit+=TP;
         m_report.long_trades++;
         m_report.total_trades++;
         m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),j);
         return;
      }
      else if((deal_price-iLow(symbol,StringToTimeframe(tf),copied-j))/SymbolInfoDouble(symbol,SYMBOL_POINT)>=SL)
      {
         m_report.loss_trades++;
         m_report.profit-=SL;
         m_report.long_trades++;
         m_report.total_trades++;
         m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),j);
         return;
      }
   }
   m_start_date=m_end_date;
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::CalculateSellDeals(const string symbol,datetime start)
{
   MqlRates rt[];
   int TP=int(m_takeprofit2.GetValue());
   int SL=int(m_stoploss2.GetValue());
   string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
   int copied=CopyRates(symbol,StringToTimeframe(tf),m_start_date,m_end_date,rt);
   double deal_price=iOpen(symbol,StringToTimeframe(tf),copied);
   for(int j=0; j<copied; j++)
   {
      if((deal_price-iLow(symbol,StringToTimeframe(tf),copied-j))/SymbolInfoDouble(symbol,SYMBOL_POINT)>=TP)
      {
         m_report.profit_trades++;
         m_report.profit+=TP;
         m_report.short_trades++;
         m_report.total_trades++;
         m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),j);
         return;
      }
      else if((iHigh(symbol,StringToTimeframe(tf),copied-j)-deal_price)/SymbolInfoDouble(symbol,SYMBOL_POINT)>=SL)
      {
         m_report.loss_trades++;
         m_report.profit-=SL;
         m_report.short_trades++;
         m_report.total_trades++;
         m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),j);
         return;
      }
   }
   m_start_date=m_end_date;
}

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

3. Метод вывода результатов тестирования PrintReport().

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::PrintReport(void)
{
   if(m_lang_index==0)
   {
      string report_label[6]=
      {
         "Всего трейдов: ","Короткие трейды: ","Прибыльные трейды: ",
         "Прибыль в пунктах: ","Длинные трейды: ","Убыточные трейды: "
      };
      //---
      m_report_text[0].LabelText(report_label[0]+string(m_report.total_trades));
      m_report_text[1].LabelText(report_label[1]+string(m_report.short_trades));
      m_report_text[2].LabelText(report_label[2]+string(m_report.profit_trades));
      m_report_text[3].LabelText(report_label[3]+string(m_report.profit));
      m_report_text[4].LabelText(report_label[4]+string(m_report.long_trades));
      m_report_text[5].LabelText(report_label[5]+string(m_report.loss_trades));
   }
   else
   {
      string report_label[6]=
      {
         "Total trades: ","Short Trades: ","Profit Trades: ",
         "Profit in points: ","Long Trades: ","Loss Trades: "
      };
      //---
      m_report_text[0].LabelText(report_label[0]+string(m_report.total_trades));
      m_report_text[1].LabelText(report_label[1]+string(m_report.short_trades));
      m_report_text[2].LabelText(report_label[2]+string(m_report.profit_trades));
      m_report_text[3].LabelText(report_label[3]+string(m_report.profit));
      m_report_text[4].LabelText(report_label[4]+string(m_report.long_trades));
      m_report_text[5].LabelText(report_label[5]+string(m_report.loss_trades));
   }
   Update(true);
}

Выводит данные о тестировании в приложение. На этом алгоритм работы приложения завершается.

Демонстрация и пример работы с конструктором

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


Заключение

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


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