Расширяем функционал Конструктора стратегий

2 декабря 2019, 11:57
Alexander Fedosov
2
1 516

Содержание

Введение

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

Обзор нововведений и дополнений

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

В связи с этими целями план улучшений будет выглядеть следующим образом:

  • Начальный депозит в валюте счета.
  • Возможность выбора Профита: "В пунктах" или "В валюте депозита".
  • При выборе "В валюте депозита" открываются еще два поля ввода: "Тип Лота" и "Размер Лота".
  • Тип Лота, в свою очередь, может быть "Постоянный" или "От Баланса".
  • Размер лота. Доступен при Типе лота "Постоянный".

В список торгового отчета также буду внесены следующие изменения:

  • Чистая прибыль. Параметр, доступный только в режиме тип прибыли "В валюте депозита".
  • Абсолютная просадка по балансу. Параметр, доступный только в режиме тип прибыли "В валюте депозита".
  • Максимальная просадка баланса в процентах. Параметр, доступный только в режиме тип прибыли "В валюте депозита".
  • Количество коротких и длинных тредов дополнены параметрами процентом выигрышных того и другого типа.
  • Прибыльность стратегии. Отношение общей прибыли к общему убытку.
  • Фактор восстановления. Параметр, доступный только в режиме тип прибыли "В валюте депозита".

Более подробно о введенных параметров можно узнать в справке MetaTrader 5 в разделе Отчет о тестировании. На рис.1 показан прототип, описанных выше возможностей.

Рис.1 Прототип новых инструментов тестирования.

Еще одним нововведением является возможность визуально увидеть результат тестирования любой стратегии. А именно — График результатов тестирования. В нашем приложении он будет открываться по нажатию кнопки "Открыть график"(ее видно на рис.1) в разделе Отчет.

Рис.2 Внешний вид графика.

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

Этапы реализации нововведений

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

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

Рассмотрим изменения, которые коснулись метода создания главного окна CreateWindow(), а именно — дополнение элементов интерфейса, которые реализуют новые инструменты тестирования, показанные на рис.1 чуть выше.

//---- Вкладка КОНСТРУКТОР
....
//---
   if(!CreateProfitType(int(0.35*(m_window[0].XSize()-150)-120),50+35*6+ygap))
      return(false);
   if(!CreateLotType(int(0.6*(m_window[0].XSize()-150)-120),50+35*6+ygap))
      return(false);
   if(!CreateBaseLotValue(int(0.85*(m_window[0].XSize()-150)-120),50+35*6+ygap))
      return(false);
   if(!CreateInitialDeposit(int(0.35*(m_window[0].XSize()-150)-120),50+35*7+ygap))
      return(false);
//---
   if(!CreateReportFrame(m_frame[2],int(0.35*(m_window[0].XSize()-150)-120),110+35*7+ygap))
      return(false);
//--- Кнопка вызова Графика
   if(!CreateIconButton(int(0.35*(m_window[0].XSize()-150)-50),100+35*7+ygap))
      return(false);
//--- Строки отчета
   for(int i=0; i<11; i++)
   {
      if(i<5)
         if(!CreateTextLabel(m_report_text[i],int(0.37*(m_window[0].XSize()-150)-120),380+25*i+ygap,"",0))
            return(false);
      if(i>=5 && i<9)
         if(!CreateTextLabel(m_report_text[i],int(0.63*(m_window[0].XSize()-150)-120),380+25*(i-5)+ygap,"",0))
            return(false);
      if(i>=9)
         if(!CreateTextLabel(m_report_text[i],int(0.89*(m_window[0].XSize()-150)-120),380+25*(i-9)+ygap,"",0))
            return(false);
      m_report_text[i].IsCenterText(false);
      m_report_text[i].FontSize(10);
   }
....

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

Метод CreateProfitType(). Создает выпадающий список с типом профита при тестировании: Валюта депозита или Пункты.

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

Метод CreateLotType(). Создает выпадающий список с типом лота: Постоянный и От баланса.

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

Метод CreateBaseLotValue(). Создает поле ввода значения лота.

/+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateBaseLotValue(const int x_gap,const int y_gap)
{
//--- Сохраним указатель на главный элемент
   m_base_lot.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_base_lot);
//--- Свойства
   m_base_lot.XSize(210);
   m_base_lot.YSize(24);
   m_base_lot.LabelColor(C'0,100,255');
   m_base_lot.FontSize(12);
   m_base_lot.MaxValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX));
   m_base_lot.MinValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN));
   m_base_lot.StepValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP));
   m_base_lot.SetDigits(2);
   m_base_lot.SpinEditMode(true);
   m_base_lot.GetTextBoxPointer().AutoSelectionMode(true);
   m_base_lot.GetTextBoxPointer().XGap(100);
//--- Создадим элемент управления
   if(!m_base_lot.CreateTextEdit("Base Lot Size",x_gap,y_gap))
      return(false);
   m_base_lot.SetValue((string)SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN));
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_base_lot);
   return(true);
}

Метод CreateInitialDeposit(). Создает поле ввода стартового депозита при тестировании в режиме Профита — Валюта.

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

Метод CreateIconButton(). Создает кнопку, по нажатию которой будет открываться окно графика.

//+------------------------------------------------------------------+
//| Создаёт кнопку с картинкой                                       |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart.bmp"
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart_gray.bmp"
//---
bool CProgram::CreateIconButton(const int x_gap,const int y_gap)
{
//--- Сохраним указатель на главный элемент
   m_graph_button.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_graph_button);
//--- Свойства
   m_graph_button.XSize(150);
   m_graph_button.YSize(22);
   m_graph_button.FontSize(11);
   m_graph_button.IconXGap(3);
   m_graph_button.IconYGap(3);
   m_graph_button.IsHighlighted(false);
   m_graph_button.IsCenterText(true);
   m_graph_button.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart.bmp");
   m_graph_button.IconFileLocked("Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart_gray.bmp");
   m_graph_button.IconFilePressed("Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart.bmp");
   m_graph_button.IconFilePressedLocked("Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart_gray.bmp");
   m_graph_button.BorderColor(C'0,100,255');
   m_graph_button.BackColor(clrAliceBlue);
//--- Создадим элемент управления
   if(!m_graph_button.CreateButton("",x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_graph_button);
   return(true);
}

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

Далее перейдем к методу, реализующему окно графика и сам график — CreateGraphWindow().

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateGraphWindow(void)
{
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_window[2]);
//--- Свойства
   m_window[2].XSize(750);
   m_window[2].YSize(450);
   m_window[2].FontSize(9);
   m_window[2].WindowType(W_DIALOG);
   m_window[2].IsMovable(true);
//--- Создание формы
   if(!m_window[2].CreateWindow(m_chart_id,m_subwin,"",75,75))
      return(false);
   //--- Графики
   if(!CreateGraph(22,22))
      return(false);
//---
   return(true);
}

Сам метод создания окна достаточно небольшой, но в нем отдельно стоит остановиться на содержащемся в нем методе CreateGraph().

//+------------------------------------------------------------------+
//| Создаёт график                                                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGraph(const int x_gap,const int y_gap)
{
//--- Сохраним указатель на главный элемент
   m_graph1.MainPointer(m_window[2]);
//--- Свойства
   m_graph1.AutoXResizeMode(true);
   m_graph1.AutoYResizeMode(true);
   m_graph1.AutoXResizeRightOffset(10);
   m_graph1.AutoYResizeBottomOffset(10);
//--- Создание элемента
   if(!m_graph1.CreateGraph(x_gap,y_gap))
      return(false);
//--- Свойства графика
   CGraphic *graph=m_graph1.GetGraphicPointer();
   graph.BackgroundColor(::ColorToARGB(clrWhiteSmoke));
   graph.XAxis().Min(0);
   graph.BackgroundMainSize(20);
   graph.HistoryNameSize(0);
   graph.HistorySymbolSize(0);
   graph.HistoryNameWidth(0);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(2,m_graph1);
   return(true);
}

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

И последний метод, который находится в основном CreateGUI() — это метод CreateLoading() создания окна загрузки. Он введен как информационный ориентир при загрузке приложения, смене языковых настроек или же при тестировании и работе с данными.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\sandglass.bmp"
bool CProgram::CreateLoading(void)
{
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_window[3]);
//--- Свойства
   m_window[3].XSize(100);
   m_window[3].YSize(50);
   m_window[3].LabelYGap(50/2-16/2);
   m_window[3].IconYGap(50/2-16/2);
   m_window[3].FontSize(9);
   m_window[3].WindowType(W_DIALOG);
   m_window[3].IsMovable(false);
   m_window[3].CloseButtonIsUsed(false);
   m_window[3].CaptionColorLocked(C'0,130,225');
   m_window[3].LabelColor(clrWhite);
   m_window[3].LabelColorLocked(clrWhite);
   m_window[3].CaptionHeight(51);
   m_window[3].IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\sandglass.bmp");
   m_window[3].IconFileLocked("Images\\EasyAndFastGUI\\Icons\\bmp16\\sandglass.bmp");
   m_window[3].IconFilePressed("Images\\EasyAndFastGUI\\Icons\\bmp16\\sandglass.bmp");
   m_window[3].IconFilePressedLocked("Images\\EasyAndFastGUI\\Icons\\bmp16\\sandglass.bmp");
   int x=int(m_window[0].XSize()/2);
   int y=int(m_window[0].YSize()/2);
//--- Создание формы
   if(!m_window[3].CreateWindow(m_chart_id,m_subwin,"Working...",x,y))
      return(false);
   return(true);
}

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

Как мы помним из предыдущей статьи, за запуск  и обработку тестирования установленной стратегии отвечает метод 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;
   }
//--- Проверка правильности установки паттернов
   if(m_combobox1.GetListViewPointer().SelectedItemIndex()==m_combobox2.GetListViewPointer().SelectedItemIndex())
   {
      if(m_lang_index==0)
         MessageBox("Паттерны не могут быть одинаковыми!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("Patterns cannot be the same!","Error",MB_OK);
      return;
   }
//---
   m_window[3].OpenWindow();
//---
   m_counter=0;
   m_all_losses=0;
   m_all_profit=0;
   AddDeal(0,m_counter);
   ZeroMemory(m_report);
   MqlRates rt[];
   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(
         applied1>7 || applied2>7 || applied3>7 ||
         applied4>7 || applied5>7 || applied6>7)
      {
         if(m_custom_path.GetValue()=="")
         {
            if(m_lang_index==0)
               MessageBox("Не установлен путь к индикатору!","Ошибка",MB_OK);
            else if(m_lang_index==1)
               MessageBox("The indicator path is not set!","Error",MB_OK);
            break;
         }
         if(m_custom_param.GetValue()=="")
         {
            if(m_lang_index==0)
               MessageBox("Не установлены параметры индикатора!","Ошибка",MB_OK);
            else if(m_lang_index==1)
               MessageBox("Indicator parameters not set!","Error",MB_OK);
            break;
         }
      }
      //---
      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();
//---
   m_window[3].CloseDialogBox();
}

В измененной реализации остановимся на следующих моментах:

  • Была добавлена проверка, при которой паттерны, выбранные для сигнала на покупку и на продажу, не могут быть одинаковы.
  • Введено использование созданного ранее Окна загрузки.
  • Введен метод AddDeal(), который будет отвечать за запись результатов тестирования в массив данных, используемый графиком. В данном листинге он устанавливает начальное значение для графика. В режиме типа Профита "Валюта" это начальный депозит, в "Пунктах" это ноль.

Теперь рассмотрим сам метод добавления данных AddDeal():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AddDeal(int points,int index)
{
//--- В пунктах
   if(m_profit_type.GetListViewPointer().SelectedItemIndex()==0)
   {
      if(index==0)
      {
         ArrayResize(data,index+1);
         data[index]=0;
         return;
      }
      ArrayResize(data,index+1);
      data[index]=data[index-1]+points;
   }
//--- В валюте депозита
   else if(m_profit_type.GetListViewPointer().SelectedItemIndex()==1)
   {
      if(index==0)
      {
         ArrayResize(data,index+1);
         data[index]=StringToDouble(m_init_deposit.GetValue());
         return;
      }
      ArrayResize(data,index+1);
      //--- Получим выбранный символ
      string symbol=m_table_symb.GetValue(0,m_table_symb.SelectedItem());
      string basesymbol=AccountInfoString(ACCOUNT_CURRENCY);
      string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
      double lot=StringToDouble(m_base_lot.GetValue());
      if(m_lot_type.GetListViewPointer().SelectedItemIndex()>0)
      {
         lot*=data[index-1];
         lot=GetLotForOpeningPos(symbol,POSITION_TYPE_BUY,lot);
      }

      double pip_price=1;
      int shift=0;
      //--- Прямая пара
      if(StringSubstr(symbol,3,3)==basesymbol)
      {
         pip_price=NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(symbol,SYMBOL_POINT)*lot,2);
      }
      //--- Обратная пара
      else if(StringSubstr(symbol,0,3)==basesymbol)
      {
         shift=iBarShift(symbol,StringToTimeframe(tf),m_start_date,false);
         pip_price=NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(symbol,SYMBOL_POINT)*lot/iOpen(symbol,StringToTimeframe(tf),shift),2);
      }
      else
      {
         //--- Кросс
         StringConcatenate(symbol,StringSubstr(symbol,3,3),basesymbol);
         if(SymbolInfoDouble(symbol,SYMBOL_BID)!=0)
         {
            shift=iBarShift(symbol,StringToTimeframe(tf),m_start_date,false);
            pip_price=NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(symbol,SYMBOL_POINT)*lot/iOpen(symbol,StringToTimeframe(tf),shift),2);
         }
         //---
         StringConcatenate(symbol,basesymbol,StringSubstr(symbol,0,3));
         if(SymbolInfoDouble(symbol,SYMBOL_BID)!=0)
         {
            shift=iBarShift(symbol,StringToTimeframe(tf),m_start_date,false);
            pip_price=NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(symbol,SYMBOL_POINT)*lot*iOpen(symbol,StringToTimeframe(tf),shift),2);
         }
      }
      //---
      if(points>0)
         m_all_profit+=pip_price*points;
      else
         m_all_losses+=pip_price*-points;
      //---
      data[index]=data[index-1]+pip_price*points;
   }
}

В аргументах он имеет два значения:

  1. points — новое значение для графика. Здесь принимаемым значением является результат закрытия сделки в пунктах. Это либо значение Тейк-профита, либо Стоп-лосса.
  2. index — по сути, это порядковый номер сделки.

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

Следующие два метода претерпевшие изменения, это 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_counter++;
         AddDeal(TP,m_counter);
         m_report.profit_trades++;
         m_report.profit+=TP;
         m_report.profit_pips+=TP;
         m_report.long_trades++;
         m_report.profit_long++;
         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_counter++;
         AddDeal(-SL,m_counter);
         m_report.loss_trades++;
         m_report.profit-=SL;
         m_report.loss_pips+=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;
}

Изменения коснулись в местах срабатывания Тейк-профит и Стоп-лосс. Здесь, описанным выше методом AddDeal() происходит обработка факта закрытия сделки, а также структура REPORT m_report была расширена параметрами profit_pips и loss_pips. Они нужны для расчета новых характеристик в Отчете.

И последний метод, который был существенно изменен, это обработка полученных данных и вывод их в Отчет. За это, как и в предыдущей статье, отвечает метод PrintReport():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::PrintReport(void)
{
   string report_label[11];
   if(m_lang_index==0)
   {
      report_label[0]="Всего трейдов: ";
      report_label[1]="Чистая прибыль: ";
      report_label[2]="Прибыль в пунктах: ";
      report_label[3]="Абс.просадка баланса: ";
      report_label[4]="Макс.просадка баланса: ";
      report_label[5]="Корот.трейды/% выигр: ";
      report_label[6]="Приб.трейды/% от всех: ";
      report_label[7]="Прибыльность: ";
      report_label[8]="Фактор восстановления: ";
      report_label[9]="Длин.трейды/% выигр: ";
      report_label[10]="Убыт.трейды/% от всех: ";
   }
   else
   {
      report_label[0]="Total trades: ";
      report_label[1]="Total profit: ";
      report_label[2]="Total profit(pips): ";
      report_label[3]="Balance Drawdown Abs: ";
      report_label[4]="Balance Drawdown Max: ";
      report_label[5]="Short trades/won %: ";
      report_label[6]="Profit trades/% of all: ";
      report_label[7]="Profit Factor: ";
      report_label[8]="Recovery Factor: ";
      report_label[9]="Long trades/won %: ";
      report_label[10]="Loss trades/% of all: ";
   }
   //---
   m_report_text[0].LabelText(report_label[0]+string(m_report.total_trades));
   //---
   if(m_report.total_trades==0)
      return;
   //---
   if(m_profit_type.GetListViewPointer().SelectedItemIndex()==1)
   {
      double maxprofit=0.0,maxdd=0.0;
      for(int i=1; i<ArraySize(data); i++)
      {
         if(data[i]>maxprofit)
            maxprofit=data[i];
         if(maxdd<maxprofit-data[i])
            maxdd=maxprofit-data[i];
      }
      m_report_text[1].LabelText(report_label[1]+DoubleToString(data[ArraySize(data)-1],2));
      m_report_text[3].LabelText(report_label[3]+string(data[0]-data[ArrayMinimum(data)]));
      m_report_text[4].LabelText(report_label[4]+DoubleToString(maxdd/maxprofit*100,2)+"%");
      m_report_text[7].LabelText(report_label[7]+DoubleToString(m_all_profit/m_all_losses,2));
      m_report_text[8].LabelText(report_label[8]+DoubleToString((data[ArraySize(data)-1]-data[0])/maxdd,2));
   }
   else
   {
      m_report_text[1].LabelText(report_label[1]+"-");
      m_report_text[3].LabelText(report_label[3]+"-");
      m_report_text[4].LabelText(report_label[4]+"-");
      m_report_text[7].LabelText(report_label[7]+DoubleToString(m_report.profit_pips/(double)m_report.loss_pips,2));
   }
   m_report_text[2].LabelText(report_label[2]+string(m_report.profit));
   m_report_text[5].LabelText(report_label[5]+string(m_report.short_trades)+"/"+DoubleToString(m_report.profit_short/(double)m_report.short_trades*100,1)+"%");
   m_report_text[6].LabelText(report_label[6]+string(m_report.profit_trades)+"/"+DoubleToString(m_report.profit_trades/(double)m_report.total_trades*100,1)+"%");
   m_report_text[9].LabelText(report_label[9]+string(m_report.long_trades)+"/"+DoubleToString(m_report.profit_long/(double)m_report.long_trades*100,1)+"%");
   m_report_text[10].LabelText(report_label[10]+string(m_report.loss_trades)+"/"+DoubleToString(m_report.loss_trades/(double)m_report.total_trades*100,1)+"%");
//---
   for(int i=0; i<11; i++)
      m_report_text[i].Update(true);
   m_reported=true;
}

Как было сказано в обзоре Нововведений, часть параметров, например Чистая прибыль или Фактор восстановления, недоступны для режима тестирования "Пункты".


Порядок тестирования и демонстрация работы с Конструктором Стратегий

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

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

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

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

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

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

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

Шаг 4-5. Порядок выбора Временного диапазона тестирования и Таймфрейма осталось без изменений.

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

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

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

Шаг 7. Настройка Тейк-профита и Стоп-лосса также осталось неизменным.

Шаг 8. Выбор типа Профита.

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

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

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

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



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

  • Для корректной работы приложения необходимо, чтобы исторические данные для тестирования по заданному вами торговому инструменту были загружены.
  • При настройке кастомного индикатора будьте внимательны с его параметрами: номер буфера, имя и параметры вводятся через запятую. Следует добавить, что для кастомного индикатора поддерживаются только числовые значения. Имя пользовательского индикатора — это путь относительно корневой директории индикаторов (MQL5/Indicators/). Если индикатор находится в поддиректории, например, в MQL5/Indicators/Examples, то имя должно выглядеть соответственно, а именно — "Examples\\имя_индикатора" (обязательно указание двойного обратного слеша вместо одиночного в качестве разделителя).
  • Самые распространенные сценарии, вызывающие трудности в работе, были оснащены подсказками. Такие как одинаковые паттерны для обоих сигналов, попытка открыть график не проведя ни одного теста после запуска приложения, неправильная дата начала и окончания тестирования.
  • В начале статьи упоминалась ссылка на описания характеристик, используемых в Отчете. Перейдя по ней, можно получить дополнительную информацию об описании и методах расчета того или иного параметра.

Заключение

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


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


Прикрепленные файлы |
MQL5.zip (1957.75 KB)
Juer
Juer | 5 дек 2019 в 22:12
А торговый эксперт-то будет, чтоб на деле опробовать?
Alexander Fedosov
Alexander Fedosov | 6 дек 2019 в 05:55
Juer:
А торговый эксперт-то будет, чтоб на деле опробовать?

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

Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXVI): Работа с отложенными торговыми запросами - первая реализация (открытие позиций) Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXVI): Работа с отложенными торговыми запросами - первая реализация (открытие позиций)

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

Непрерывная скользящая оптимизация (Часть 1): Механизм работы с отчетами оптимизации Непрерывная скользящая оптимизация (Часть 1): Механизм работы с отчетами оптимизации

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

Исследование сезонных характеристик финансовых временных рядов при помощи диаграмм Boxplot Исследование сезонных характеристик финансовых временных рядов при помощи диаграмм Boxplot

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

Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXVII): Работа с торговыми запросами - выставление отложенных ордеров Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXVII): Работа с торговыми запросами - выставление отложенных ордеров

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