基于 Merill(梅里尔) 形态的策略构建器

Alexander Fedosov | 16 一月, 2020

内容目录

概述

前一篇文章中,我们研究了如何将 Merill(梅里尔)形态应用于各种数据,例如货币品种图表上的价格,以及标准 MetaTrader 5 指标值:ATR,WPR,CCI,RSI,等等。 为深入探索这一思路更开发了一款图形界面。 该界面的主要目的是测试和搜寻有效的形态运用方法。 进而,将找到的成功配置内置于交易策略之中,并进行测试。 在本文中,我们将开发用于构建交易策略,并对其进行测试的基本工具箱。


任务陈述和应用程序原型

在着手开发应用程序之前,我们要确定所需功能和界面元素的清单。 首先,我们定义两个分区:

构造器选项卡将包含形成交易策略所需的主要界面元素集合。 考虑所有基本要素:

  • 品种表格,它由终端里“市场观察”选项卡下所有品种的完整列表组成。
  • 过滤器选项 用于品种表格,可以方便地搜索所需的品种组。
  • 两个相同的分区,用于生成买入和卖出信号。 这些分区包含相同的元素,因此下面仅提供其中之一的描述。
  • 测试的日期范围。
  • 当前时间帧选择。
  • 在构造器选项卡下的最后一个分区是与交易策略设定有关的测试结果板块

我们来研究“信号生成”分区:

  1. 从 Merill 形态集合中选择的形态。
  2. 三个信号为一组,可以将其应用于形态。 每个信号可分别禁用。 即,如果仅选择一个信号,则仅基于该信号执行入场。 如果选择了多个信号,则其中任何一个信号触发均可执行入场。 Merill 形态可应用于指标以及价格。 
  3. 设置止盈和止损。

设置选项卡用于标准指标参数,并允许加载自定义指标。

基于上面指定的所需功能,我们来创建应用程序的原型。 图例 1 下面展示“构造器"选项卡的规划和配套界面元素:


图例1 "构造器"选项卡原型和界面元素。

我们还要为“设置”选项卡创建一个原型,并在此选项卡中布置元素。 


图例2 "设置"选项卡原型和界面元素。

请注意第二个自分区“自定义指标设置”和输入字段。 应遵照第三方指标操作相对应的 iCustom 函数语法正确输入数值。 首先,这涉及输入正确的指标路径:

名称

[输入]  自定义指标名称,其中包括相对于指标根目录 (MQL5/Indicators/) 的路径。 如果指示器位于子目录中,例如 MQL5/Indicators/Examples,则相应名称的样子:“Examples\\指标_名称”(需要双反斜杠,而不是单个反斜杠作为分隔符)。

若要应用第三方指标作为其中一个信号,请在其信号的下拉列表中选择“自定义”,如下图例 3 所示:

图例 3 选择使用自定义指标。

在应用程序的软件实现完毕后,将在本文中进一步阐述有关策略构造器的设置/用法的完整动作顺序。

实现测试用策略构建器

在继续创建应用程序图形界面之前,我们要确定其基本元素,并在此基础上构建和开发其余元素。

该应用程序有两个窗口:主应用程序窗口,和一个日期范围设置对话框。 主窗口有两个选项卡:“构造器”和“设置”。 为此,主界面创建方法 CreateGUI() 合并了两个窗口创建方法: 

  • CreateWindow() 创建主窗口。
  • CreateDateSetting() 创建日期范围设置窗口。
//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Creating a panel
   if(!CreateWindow("Merrill Constructor"))
      return(false);
//--- Creating a dialog window
   if(!CreateDateSetting())
      return(false);
//--- Finishing the creation of GUI
   CWndEvents::CompletedGUI();
   return(true);
}
//+------------------------------------------------------------------+

我们来研究每种方法的内容。 CreateDateSetting() 方法更易于实现,并且包含元素的很简单。 通过此方法实现的界面元素分别如图例 4 所示:

图例 4 日期范围设置对话框。

该窗口由对话框、两个日历元素和两个用于设置开始和结束时间的元素组成。 我们已经定义了元素内容。 现在,我们在 CreateDateSetting() 方法中实现它。

//+------------------------------------------------------------------+
//| Creates a date range selection dialog box                        |
//+------------------------------------------------------------------+
bool CProgram::CreateDateSetting(void)
{
//--- Add the pointer to the window array
   CWndContainer::AddWindow(m_window[1]);
//--- Coordinates
   int x=m_date_range.X();
   int y=m_date_range.Y()+m_date_range.YSize();
//--- Properties
   m_window[1].XSize(372);
   m_window[1].YSize(230);
   m_window[1].WindowType(W_DIALOG);
   m_window[1].IsMovable(true);
//--- Creating the form
   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,"Time",1))
      return(false);
   if(!CreateTimeEdit(m_time_edit2,m_window[1],200,200,"Time",1))
      return(false);
//---
   return(true);
}

现在我们进入实现主应用程序窗口的 CreateWindow() 方法。 该方法结构宽泛,这就是为什么我们将它们划分为单独的关键组件的原因。 首先是创建窗口本身及其基本结构,即"构造器"和"设置"两个选项卡。 

//+------------------------------------------------------------------+
//| Creates a form for controls                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
{
#define VERSION " 1.0"
   color caption=C'0,130,255';
   int ygap=30;
//--- Add the pointer to the window array
   CWndContainer::AddWindow(m_window[0]);
//--- Properties
   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);
//--- Creating the form
   if(!m_window[0].CreateWindow(m_chart_id,m_subwin,caption_text+VERSION,10,20))
      return(false);
//--- Tabs
   if(!CreateTabs(150,20))
      return(false);

这部分代码创建主窗口,而 CreateTabs() 方法负责添加上述两个选项卡:

//+------------------------------------------------------------------+
//| Create a group with tabs                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateTabs(const int x_gap,const int y_gap)
{
//--- Save the pointer to the main control
   m_tabs1.MainPointer(m_window[0]);
//--- Properties
   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);   
//--- Add tabs with the specified properties
   for(int i=0; i<ArraySize(m_tabs_names); i++)
      m_tabs1.AddTab(m_tabs_names[i],150);
//--- Creating a control
   if(!m_tabs1.CreateTabs(x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_tabs1);
   return(true);
}

在上面的原型中,我们为每个选项卡定义了元素的内容:构造器(图例 1)和设置(图例 2)。 现在我们研究选项卡中所包含元素的实现。 “构造器”选项卡包含许多重复的元素类型,因此,我们将仅研究主要子分区,以及用于实现这些元素的方法清单。

//---- Constructor tab
//--- Symbols filter
   if(!CreateSymbolsFilter(10,10))
      return(false);
   if(!CreateSymbolsTable(10,45))
      return(false);
//--- Working timeframe
   if(!CreateTextLabel(m_text_labels1[2],290,10,"Timeframe",0))
      return(false);
   if(!CreateTimeframe1(440,10))
      return(false);
//--- Date range
   if(!CreateButton(m_date_range,240,10))
      return(false);
//--- Text labels
   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);
//--- Pattern selection
   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,"Pattern"))
      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,"Pattern"))
      return(false);
//--- Selecting the application of patterns
   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);
//--- Signal checkboxes
   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,"Signal "+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,"Signal "+string(i-4)))
            return(false);
   }
//--- Take Profit and Stop Loss settings
   if(!CreateEditValue(m_takeprofit1,int(0.35*(m_window[0].XSize()-150)-120),50+35*4+ygap,"Take Profit",500,0))
      return(false);
   if(!CreateEditValue(m_stoploss1,int(0.35*(m_window[0].XSize()-150)-120),50+35*5+ygap,"Stop Loss",500,0))
      return(false);
   if(!CreateEditValue(m_takeprofit2,int(0.75*(m_window[0].XSize()-150)-120),50+35*4+ygap,"Take Profit",500,0))
      return(false);
   if(!CreateEditValue(m_stoploss2,int(0.75*(m_window[0].XSize()-150)-120),50+35*5+ygap,"Stop Loss",500,0))
      return(false);
//--- Report
   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() 实现含复选框的输入字段,和“搜索”按钮。

//+------------------------------------------------------------------+
//| Creates a checkbox with the "Symbols filter" input field         |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsFilter(const int x_gap,const int y_gap)
{
//--- Save the pointer to the main control
   m_symb_filter.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(0,m_symb_filter);
//--- Properties
   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"
//--- Creating a control
   if(!m_symb_filter.CreateTextEdit("",x_gap,y_gap))
      return(false);
//--- Enable the checkbox
   m_symb_filter.IsPressed(true);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_symb_filter);
//---
   if(!CreateRequest(x_gap+125,y_gap))
      return(false);
   return(true);
}

CreateSymbolsTable() 实现一个表格,该表格输出从“市场观察”窗口中经过滤的货币品种。

//+------------------------------------------------------------------+
//| Creates a symbol table                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsTable(const int x_gap,const int y_gap)
{
#define ROWS1_TOTAL    1
//--- Save the pointer to the main control
   m_table_symb.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(0,m_table_symb);
//--- Array of column widths
   int width[1]= {119};
//--- Array of text alignment in columns
   ENUM_ALIGN_MODE align[1]= {ALIGN_CENTER};
//--- Array of text offset along the X axis in the columns
   int text_x_offset[1]= {5};
//--- Properties
   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');
//--- Creating a control
   if(!m_table_symb.CreateTable(x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_table_symb);
   return(true);
}

2. 操作时间帧和“日期范围”按钮。

为了测试,所有元素均实现了操作时间帧的选择。 日期范围按钮将打开相应的对话框,如上所述。 CreateButton() 方法实现该按钮。 CreateTextLabel() 创建一个相应的标签,CreateTimeframe1() 实现时间范围选择。 CreateButton()CreateTextLabel() 是通用方法,以后会用到。 它们的代码仅在此处提供一次。 这些元素分别在图例 6 中展示:

图例 6 日期范围按钮和手工时间帧选择。

//+------------------------------------------------------------------+
//| Creates a text label in the first tab                            |
//+------------------------------------------------------------------+
bool CProgram::CreateTextLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text,int tab)
{
//--- Save the window pointer
   text_label.MainPointer(m_tabs1);
//--- Attach to tab
   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);
//--- Creation of a button
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Add the element pointer to the data base
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}
//+------------------------------------------------------------------+
//| Creates a button to show the date range selection window         |
//+------------------------------------------------------------------+
bool CProgram::CreateButton(CButton &button,const int x_gap,const int y_gap)
{
//--- Save the pointer to the main control
   button.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(0,button);
//--- Properties
   button.XSize(100);
   button.YSize(25);
   button.FontSize(11);
   button.IsHighlighted(false);
   button.IsCenterText(true);
   button.BorderColor(C'0,100,255');
   button.BackColor(clrAliceBlue);
//--- Creating a control
   if(!button.CreateButton("Date range",x_gap,y_gap))
      return(false);
//--- Add the element pointer to the data base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

CreateTimeframe1() 方法是一个包含所有可用时间帧的下拉列表。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTimeframe1(const int x_gap,const int y_gap)
{
//--- Pass the object to the panel
   m_timeframe1.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(0,m_timeframe1);
//--- Array of the item values in the list view
   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"
   };
//--- Set properties before creation
   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);
//--- Save the item values in the combobox list view
   for(int i=0; i<21; i++)
      m_timeframe1.SetValue(i,timeframe_names[i]);
//--- Get the list view pointer
   CListView *lv=m_timeframe1.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_timeframe1.SelectItem(0);
//--- Creating a control
   if(!m_timeframe1.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_timeframe1);
   return(true);
}

3.  分区中的文本标签,以及配合买入和卖出信号的形态选择元素。

文字标签是由 CreateTextLabel() 方法创建的,我们之前曾经研究过。 另两种方法实现了用于选择 Merill 形态进行测试的复选框和下拉菜单。

图例 7 分区中的文本标签和形态选择。

CreateCheckBox() 方法创建"形态"复选框。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCheckBox(CCheckBox &checkbox,const int x_gap,const int y_gap,const string text)
{
//--- Save the pointer to the main control
   checkbox.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(0,checkbox);
//--- Properties
   checkbox.YSize(25);
   checkbox.GreenCheckBox(true);
   checkbox.IsPressed(true);
   checkbox.FontSize(12);
   checkbox.LabelColor(C'0,100,255');
   checkbox.LabelColorPressed(C'0,100,255');
//--- Creating a control
   if(!checkbox.CreateCheckBox(text,x_gap,y_gap))
      return(false);
//--- Add the element pointer to the data base
   CWndContainer::AddToElementsArray(0,checkbox);
   return(true);
}

方法 PatternType1()PatternType2() 都是一样的

//+------------------------------------------------------------------+
//| Creates combobox 1                                               |
//+------------------------------------------------------------------+
bool CProgram::PatternType1(const int x_gap,const int y_gap,const int tab)
{
//--- Total number of the list items
#define ITEMS_TOTAL1 32
//--- Pass the object to the panel
   m_combobox1.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(tab,m_combobox1);
//--- Array of the item values in the list view
   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"
   };
//--- Set properties before creation
   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);
//--- Save the item values in the combobox list view
   for(int i=0; i<ITEMS_TOTAL1; i++)
      m_combobox1.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_combobox1.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_combobox1.SelectItem(0);
//--- Creating a control
   if(!m_combobox1.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_combobox1);
   return(true);
}

4. 选择所用形态和信号复选框。

该界面板块由配置“买入”和“卖出”信号的一组信号组成。 每个板块均包含一至三个信号的选项选择器,可用作入场条件。 由方法 CreateCheckBox() 和 AppliedTypeN() 组成。


图例 8 使用形态和信号复选框。 

方法 AppliedType1() — AppliedType6() 的结构相似:它们呈现一个下拉列表,从中选择一个数据数组来搜索基于形态的信号。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::AppliedType1(const int x_gap,const int y_gap)
{
//--- Pass the object to the panel
   m_applied1.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(0,m_applied1);
//--- Array of the item values in the list view
   string pattern_names[9]=
   {
      "Price","ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum","Custom"
   };
//--- Set properties before creation
   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);
//--- Save the item values in the combobox list view
   for(int i=0; i<9; i++)
      m_applied1.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_applied1.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_applied1.SelectItem(0);
//--- Creating a control
   if(!m_applied1.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_applied1);
   return(true);
}

5. 设置止盈和止损。

该界面分区允许分别配置买入和卖出信号的止盈和止损。 级别设置以点数为单位。 

图例 9 止盈和止损输入字段。

通用方法 CreateEditValue() 用于实现这些输入字段。

//+------------------------------------------------------------------+
//| Creates an input field                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateEditValue(CTextEdit &text_edit,const int x_gap,const int y_gap,const string label_text,const int value,const int tab)
{
//--- Save the pointer to the main control
   text_edit.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(tab,text_edit);
//--- Properties
   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);
//--- Creating a control
   if(!text_edit.CreateTextEdit(label_text,x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,text_edit);
   return(true);
}

6. 测试结果和报告。

该板块包含测试结果。 它是利用上述的 CreateTextLabel() 方法实现的。 

图例 10 报告板块。

我们已研究了构造器选项卡的实现。 现在我们进入“设置”。

1. 标准指标参数。

该分区包括用于测试和分析的所有指标设置。

图例 11 标准指标设置板块。

该板块由三个 CreateFrame() 方法实现,这些方法创建带有框架的可视分区。 我们还在此处用到通用 CreateIndSetting() 输入字段方法来创建指标参数,以及一组 IndicatorSetting1()IndicatorSetting4() 方法创建均线方法、交易量和价格参数的下拉列表。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateFrame(CFrame &frame,const string text,const int x_gap,const int y_gap)
{
//--- Save the pointer to the main control
   frame.MainPointer(m_tabs1);
//--- Attach to tab
   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);
//--- Creating a control
   if(!frame.CreateFrame(text,x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,frame);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::IndicatorSetting1(const int x_gap,const int y_gap,const string text)
{
//--- Pass the object to the panel
   m_ind_set1.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(1,m_ind_set1);
//--- Array of the item values in the list view
   string pattern_names[4]=
   {
      "Simple","Exponential","Smoothed","Linear weighted"
   };
//--- Set properties before creation
   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);
//--- Save the item values in the combobox list view
   for(int i=0; i<4; i++)
      m_ind_set1.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_ind_set1.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_ind_set1.SelectItem(0);
//--- Creating a control
   if(!m_ind_set1.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_ind_set1);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::IndicatorSetting3(const int x_gap,const int y_gap,const string text)
{
//--- Pass the object to the panel
   m_ind_set3.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(1,m_ind_set3);
//--- Array of the item values in the list view
   string pattern_names[2]=
   {
      "Tick volume","Real Volume"
   };
//--- Set properties before creation
   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);
//--- Save the item values in the combobox list view
   for(int i=0; i<2; i++)
      m_ind_set3.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_ind_set3.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   lv.ItemYSize(20);
   lv.YSize(42);
   m_ind_set3.SelectItem(0);
//--- Creating a control
   if(!m_ind_set3.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_ind_set3);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::IndicatorSetting4(const int x_gap,const int y_gap,const string text)
{
//--- Pass the object to the panel
   m_ind_set4.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(1,m_ind_set4);
//--- Array of the item values in the list view
   string pattern_names[4]=
   {
      "Open","Close","High","Low"
   };
//--- Set properties before creation
   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);
//--- Save the item values in the combobox list view
   for(int i=0; i<4; i++)
      m_ind_set4.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_ind_set4.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   lv.ItemYSize(20);
   lv.YSize(82);
   m_ind_set4.SelectItem(1);
//--- Creating a control
   if(!m_ind_set4.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_ind_set4);
   return(true);
}

 2. 界面语言。

“界面语言”控件以包含两个选项的下拉列表方式实现:英语和俄语。 该元素利用 LanguageSetting() 方法实现:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LanguageSetting(const int x_gap,const int y_gap,const string text)
{
//--- Pass the object to the panel
   m_language_set.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(1,m_language_set);
//--- Array of the item values in the list view
   string pattern_names[2]=
   {
      "Русский","English"
   };
//--- Set properties before creation
   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);
//--- Save the item values in the combobox list view
   for(int i=0; i<2; i++)
      m_language_set.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_language_set.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   lv.ItemYSize(20);
   lv.YSize(42);
   m_language_set.SelectItem(1);
//--- Creating a control
   if(!m_language_set.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   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)
{
//--- Save the pointer to the main control
   text_edit.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(1,text_edit);
//--- Properties
   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);
//--- Creating a control
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,text_edit);
   return(true);
}

我们已研究了视觉部分。 现在我们来分析配置交易策略的测试算法。

为了解释此应用程序进行测试所用的算法,我们需要确定动作顺序,以便令我们能够正确运行测试并接收结果。 精心设计的动作顺序可以明示应用程序界面每次交互的原理。

步骤 1. 界面语言选择

根据我们的实现,此选项在“设置”选项卡下可用,是一个下拉列表。 我们来说明如何切换界面语言。 这是通过自定义组合框项目选择的事件来完成的,该事件调用 ChangeLanguage() 方法。

//--- Selection of a combo box item
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
   {
      //--- Changing the interface language
      if(ChangeLanguage(lparam))
         Update(true);
   }

现在我们研究界面语言的变更方法。 尽管该方法有点冗长,但其思路却很简单。

//+------------------------------------------------------------------+
//| Changing the interface language                                  |
//+------------------------------------------------------------------+
bool CProgram::ChangeLanguage(const long id)
{
//--- Check the element ID
   if(id!=m_language_set.Id())
      return(false);
   m_lang_index=m_language_set.GetListViewPointer().SelectedItemIndex();
//---
   if(m_lang_index==0)
   {
      //--- Constructor tab
      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]+"-");
      //--- Settings tab
      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("Язык интерфейса");
      //--- Date Range window
      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
   {
      //--- Constructor tab
      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]+"-");
      //--- Settings tab
      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");
      //--- Date Range window
      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() 方法完成的。 该方法在“搜索”按钮按下事件里被调用。

   //--- Button click event
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
   {
      //--- Requesting data
      RequestData(lparam);
....
//+------------------------------------------------------------------+
//| Output of symbols to the symbols table                           |
//+------------------------------------------------------------------+
bool CProgram::RequestData(const long id)
{
//--- Check the element ID
//---
   if(id==m_request.Id())
   {
      //--- Hide the table
      m_table_symb.Hide();
      //--- Initialize the table
      GetSymbols(m_symb_filter);
      RebuildingTables(m_table_symb);
      //--- Show the table
      m_table_symb.Show();
   }
   return(true);
}

步骤 4. 测试时间范围选择

单击“日期范围”按钮会发生此事件。 逻辑很简单:它会打开一个用于设置日期范围的对话框。

//--- Button click event
   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 禁用买入或卖出信号。

可以在“形态”标签的左侧选择即将进行测试的 Merrill 形态。 上一篇文章 中已阐述了 Merill 形态的详细信息。

臂肘 7. 选择测试信号,并设置止盈和止损

图例 13 展示了每种入场类型最多可以同时设置三个信号。 信号触发根据逻辑“或"原理。 所以,如果在测试中为买入设置了所有三个信号,那么如果三个信号中的任何一个触发,则将入场。 同样适用于卖出信号。 在“信号”文本标签右边的下拉列表中,您可以为所选形态类型选择要应用的数据类型。

步骤 8. 运行测试

步骤 1-7 之后,在表格中单击鼠标左键选择测试的金融产品。 所单击列表或表项的自定义事件会启动测试算法。

//--- Event of pressing on a list or table item
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
   {
      //--- Select a symbol for further work
      //--- Check the element ID
      if(lparam==m_table_symb.Id())
      {
         //--- Exit if the line is not selected
         if(m_table_symb.SelectedItem()==WRONG_VALUE)
         {
            //--- Show the full symbol description in the status bar
            m_status_bar.SetValue(0,"Не выбран символ для анализа");
            m_status_bar.GetItemPointer(0).Update(true);
         }
         //--- Get a selected symbol
         string symbol=m_table_symb.GetValue(0,m_table_symb.SelectedItem());
         //--- Show the full symbol description in the status bar
         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)
{
//--- Get the date range
   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");
//--- Check specified dates
   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;
   }
//--- Output the report
   PrintReport();
}

此方法包括检查日期范围设置是否正确。 执行另一项检查以确保用户不会为测试买入和卖出信号设置相同的形态。 GetResult() 方法包括在设置中指定的三种处理数据的方法。

1. 信号搜索方法:BuySignal()SellSignal()。 它们是相似的。 我们来研究其一。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::BuySignal(const string symbol,datetime start,int applied,int signal)
{
//--- Exit if the buy signal is disabled
   if(!m_checkbox[0].IsPressed())
      return(false);
//---
   int Handle=INVALID_HANDLE;
   string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
//--- Preparing data
   if(m_checkbox[signal+1].IsPressed())
   {
      //--- Price
      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;
         }
         //--- Pattern search
         pat.A=arr[0];
         pat.B=arr[1];
         pat.C=arr[2];
         pat.D=arr[3];
         pat.E=arr[4];
         //--- If the pattern is found, check the signal
         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 indicator handle!","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);
         //--- Pattern search condition
         pat.A=arr[0];
         pat.B=arr[1];
         pat.C=arr[2];
         pat.D=arr[3];
         pat.E=arr[4];
         //--- If the pattern is found, check the signal
         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. 在 MetaTrader 5 终端根目录中打开 MQL5 文件夹