外汇EA编写教程:扩充策略构建器功能

内容目录

概述

在系列文章的第一部分中,我们分析了 Merrill 形态并将其应用于不同的数据数组,例如价格和基于价格的振荡器 ATR、CCI 和 WPR,等等。 本文目的在于探索和评估在外汇和其他市场里使用指定形态的前景。 第二部分则致力于策略构建器的创建,将先前讨论的形态汇总成简单策略。 在第三部分中,我们将扩充策略创建,并测试功能。 我们还将增加按点数操控手数的可能性,以及用于查看测试结果的功能。

附加概览

在研究新功能之前,我们先回顾一下之前的部分。 所有测试结果均显示在摘要报告中,而报告内的盈利/亏损值则按所研究的指定金融产品的点数表述。 然而,这无法针彻底评估策略的所有潜在功能。 所以,主要目标是扩充测试器功能,然后扩充交易报告参数。

实施改进时,我们将坚持以下计划:

  • 初始存款以帐户币种表示。
  • 盈利计算选项:“按点数”或“按存款币种”。
  • 如果选择了“按存款币种”,则会出现另外两个输入字段:“手数类型”和“手数大小”。
  • 手数类型可以是常数,也可以基于余额。
  • 手数大小。 可用于常量手数类型。

在交易报告中执行了以下修改:

  • 净利润总额。 该参数仅适用于利润类型“按存款币种”。
  • 余额绝对回撤。 该参数仅适用于利润类型“按存款币种”。
  • 余额最大回撤。 该参数仅适用于利润类型“按存款币种”。
  • 除了空头和多头交易的数量外,还显示两种类型的交易胜率百分比。
  • 策略盈利能力。 利润总额与亏损总额的比率。
  • 恢复因子。 该参数仅适用于利润类型“按存款币种”。

您可以在 MetaTrader 5 帮助的测试报告章节中了解有关新参数的更多信息。 上述功能的原型如图例 1 所示。

图例 1 新测试工具的原型。

另一个新功能是可以直观地查看任何策略的测试结果。 这意味着您可以查看测试结果图。 我们将在应用程序的“报告”部分添加“打开图表”按钮(如图例 1 所示)。

图例 2 图形外观。

如图例 2 所示,可以在图表上直观地评估存款的走势特征和交易结果。 为方便起见,标题显示出所测试品种、其时间帧,以及执行测试的时间段。

新功能实现阶段

我们来定义主要元素和实现方法,类似于开发策略构建器初期时所做的工作。 在创建界面的 CreateGUI() 主要方法中,添加了两个方法。 我们看一下这些方法,以及针对现有方法的补充。

//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Create a panel
   if(!CreateWindow("Merrill Constructor"))
      return(false);
//--- Create a dialog window
   if(!CreateDateSetting())
      return(false);
//--- Create a chart window
   if(!CreateGraphWindow())
      return(false);
//--- Create a load window
   if(!CreateLoading())
      return(false);
//--- Finish the creation of GUI
   CWndEvents::CompletedGUI();
   return(true);
}

我们观察主窗口创建方法 CreateWindow() 中的更改:为实现新的测试工具,添加了如图例 1 所示的新界面元素。

//---- CONSTRUCTOR tab
....
//---
   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);
//--- Graph opening button
   if(!CreateIconButton(int(0.35*(m_window[0].XSize()-150)-50),100+35*7+ygap))
      return(false);
//--- Report lines
   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)
{
//--- Pass the object to the panel
   m_profit_type.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(0,m_profit_type);
//--- Array of the item values in the list view
   string pattern_names[2]=
   {
      "Pips","Currency"
   };
//--- Set properties before creation
   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);
//--- Save the item values in the combobox list view
   for(int i=0; i<2; i++)
      m_profit_type.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_profit_type.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_profit_type.SelectItem(1);
//--- Create a control
   if(!m_profit_type.CreateComboBox("Profit Type",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_profit_type);
   return(true);
}

CreateLotType(). 该方法为手数类型创建一个下拉列表,常量,或依据余额。

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

CreateBaseLotValue(). 该方法为手数值创建输入字段。

/+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateBaseLotValue(const int x_gap,const int y_gap)
{
//--- Save the pointer to the main control
   m_base_lot.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(0,m_base_lot);
//--- Properties
   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);
//--- Create a control
   if(!m_base_lot.CreateTextEdit("Base Lot Size",x_gap,y_gap))
      return(false);
   m_base_lot.SetValue((string)SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN));
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_base_lot);
   return(true);
}

CreateInitialDeposit(). 在“利润 —币种”模式下进行测试时,为初始存款创建一个输入字段。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateInitialDeposit(const int x_gap,const int y_gap)
{
//--- Save the pointer to the main control
   m_init_deposit.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(0,m_init_deposit);
//--- Properties
   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);
//--- Create a control
   if(!m_init_deposit.CreateTextEdit("Initial Deposit",x_gap,y_gap))
      return(false);
   m_init_deposit.SetValue((string)1000);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_init_deposit);
   return(true);
}

CreateIconButton(). 创建一个按钮,点击后会打开图表窗口。

//+------------------------------------------------------------------+
//| Creates a button with an image                                   |
//+------------------------------------------------------------------+
#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)
{
//--- Save the pointer to the main control
   m_graph_button.MainPointer(m_tabs1);
//--- Attach to tab
   m_tabs1.AddToElementsArray(0,m_graph_button);
//--- Properties
   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);
//--- Create a control
   if(!m_graph_button.CreateButton("",x_gap,y_gap))
      return(false);
//--- Add the element pointer to the data base
   CWndContainer::AddToElementsArray(0,m_graph_button);
   return(true);
}

CreateWindow() 中修改了报告特征的输出结构。 它追加并实现为三列,取代了原先的两列。 所有这些就是在主窗口创建方法 CreateWindow() 中进行的修改。

接下来,我们继续实现图表窗口和报表图形的方法 — CreateGraphWindow()

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateGraphWindow(void)
{
//--- Add the pointer to the window array
   CWndContainer::AddWindow(m_window[2]);
//--- Properties
   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);
//--- Create the form
   if(!m_window[2].CreateWindow(m_chart_id,m_subwin,"",75,75))
      return(false);
   //--- Charts
   if(!CreateGraph(22,22))
      return(false);
//---
   return(true);
}

创建方法很小巧。 请注意其中包含的 CreateGraph() 方法

//+------------------------------------------------------------------+
//| Create a chart                                                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGraph(const int x_gap,const int y_gap)
{
//--- Save the pointer to the main control
   m_graph1.MainPointer(m_window[2]);
//--- Properties
   m_graph1.AutoXResizeMode(true);
   m_graph1.AutoYResizeMode(true);
   m_graph1.AutoXResizeRightOffset(10);
   m_graph1.AutoYResizeBottomOffset(10);
//--- Create element
   if(!m_graph1.CreateGraph(x_gap,y_gap))
      return(false);
//--- Chart properties
   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);
//--- Add the element pointer to the data base
   CWndContainer::AddToElementsArray(2,m_graph1);
   return(true);
}

它创建一个空图形,并设置其视觉特征。 图形数据将在以后添加。

在主要的 CreateGUI() 中的另一个方法是 CreateLoading(),其会创建加载窗口。 它以指标形式负责加载应用程序、更改语言设置、以及处理数据和测试。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#resource "//Images//EasyAndFastGUI//Icons//bmp16//sandglass.bmp"
bool CProgram::CreateLoading(void)
{
//--- Add the pointer to the window array
   CWndContainer::AddWindow(m_window[3]);
//--- Properties
   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);
//--- Create the form
   if(!m_window[3].CreateWindow(m_chart_id,m_subwin,"Working...",x,y))
      return(false);
   return(true);
}

我们已研究了策略构建器的视觉元素。 我们进入测试算法,并观察如何利用新的视觉控件在其中显示信息。

您也许还记得上一篇文章当中,策略测试的启动和处理是通过 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;
   }
//--- Check if patterns are specified correctly
   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;
   }
//--- Output the report
   PrintReport();
//---
   m_window[3].CloseDialogBox();
}

实现了以下修改:

  • 添加检查,验证形态与买入和卖出信号是否匹配。
  • 为先前创建的“下载”窗口添加具体操作。
  • 添加了 AddDeal() 方法,该方法负责将图形所使用的测试结果写入数据数组。 在此处设置图表的初始值。 对于“利润”类型为“货币”,初始值为“货币”。 对于 “点数” 则为零。

现在我们来查看 AddDeal() 数据添加方法:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AddDeal(int points,int index)
{
//--- In points
   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;
   }
//--- In deposit currency
   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);
      //--- Get a selected symbol
      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;
      // --- Direct pair
      if(StringSubstr(symbol,3,3)==basesymbol)
      {
         pip_price=NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(symbol,SYMBOL_POINT)*lot,2);
      }
      //--- Reverse pair
      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
      {
         //--- Cross pair
         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 是交易的索引。

根据所选的利润显示模式,将在方法中计算相应的数据,并添加到数据数组中。 对于“点数”利润模式,数组中的每个新元素都是以点数为单位的先前值与交易结果之和。 在“货币”模式下,收到的以点数为单位的成交结果将转换为存款币种。 此操作考虑了测试货币对的类型:直盘,颠倒盘或交叉盘。

其他两个修改过的方法是 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++)
   {
      //--- Take Profit trigger
      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;
      }
      //--- Stop Loss trigger
      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() 方法实际处理成交平仓。此外,新参数 profit_pips 和 loss_pips 已添加到 REPORT m_report 结构中。 这些参数允许计算报告中的新特征。

最后一个方法经过重大修改,处理接收到的数据,并将结果输出到报告中。 同样,这是通过 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 所示。

图例 3 禁用买入或卖出信号。

步骤 7、 止盈和止损设置未更改。

步骤 8、 选择利润类型。

  • 选择“点数”后,“报告”部分中的测试结果将以所选货币品种的点数显示。 
  • 如果选择了“货币”,则会出现其他设置:手数类型,手数和初始存款。 手数类型会在测试中影响手数的计算。 它可以是常量,也可以基于余额。

步骤 9、 在步骤 1-8 之后,通过在表中单击鼠标左键选择测试产品。 

测试完成后,结果将显示在“报告”部分中。 之后,您可以单击打开图形。

下一个视频显示了按照上述算法进行的测试。


测试美林形态的建议:

  • 为了令应用程序正常工作,我们需要下载指定交易品种的历史数据来进行测试。
  • 自定义指标参数时要小心:缓冲区编号,名称或参数应以逗号分隔。 请注意,仅支持数字值。 自定义指标名称是相对于指标根目录(MQL5/Indicators/)的路径。 如果指标位于子目录中,例如 MQL5/Indicators/Examples,则相应名称应看起来:“ Examples//indicator_name”(始终用双反斜杠替代单反斜杠作为分隔符)。
  • 可能会引起困难的最常见情况会带有提示。 这些包括同一形态触发两种信号,尝试在不执行先前测试的情况下打开图形,错误的测试开始日期和结束日期。
  • 在本文的开头,我提到的链接,指向该报告中用到的特征描述。 跟随链接阅读有关参数描述和计算方法的更多信息。

结束语

下面的文档包含所有描述过的文件,这些文件应排列在相应的文件夹中。 为了正确操作,请将 MQL5 文件夹放置在终端的根目录中。 若要打开 MQL5 文件夹所在的终端根目录,请按 MetaTrader 5 终端中的 Ctrl+Shift+D 组合键,或使用关联菜单,如下面的图例 5 中所示。


图例 5. 在 MetaTrader 5 终端根目录中打开 MQL5 文件夹

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/7361

附加的文件 |

MQL5.zip
(1936.58 KB)

 


 

MyFxtops迈投(www.myfxtops.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投