Extending Strategy Builder Functionality

23 January 2020, 13:24
Alexander Fedosov
0
6 970

Table of Contents

Introduction

In the first part of the article series, we analyzed Merrill patterns and applied them to different data arrays, such as the price and price-based oscillators ATR, CCI and WPR, among others. The purpose of the article was to explore and evaluate the prospects of using the specified patterns on forex and other markets. The second part was devoted to the creation of a Strategy Builder to assemble simple strategies using the previously discussed patterns. In the third part, we will expand the strategy creation and testing functionality. We will add the possibility to work with lots in addition to points, as well the functionality for viewing testing results.

Overview of Additions

Before considering the new features, let us recall the previous part. Results of all tests were displayed in a brief summary report, while the report was presented as profit/loss on points of the specific considered financial instrument. However, this did not allow the complete evaluation of all potential capabilities of a strategy. Therefore, the major goal is to expand tester features and then to expand the trading report parameters.

We will stick to the following plan when implementing the improvements:

  • Initial deposit in the account currency.
  • Profit calculation options: "In points" or "In deposit currency".
  • If "In deposit currency" is selected, two other input fields appear: "Lot Type" and "Lot Size".
  • Lot Type can be Constant or based on Balance.
  • Lot Size. Available for the Constant lot type.

The following changes will be implemented in the trading report:

  • Total Net Profit. The parameter is only available for the profit type "In deposit currency".
  • Balance Drawdown Absolute. The parameter is only available for the profit type "In deposit currency".
  • Balance Drawdown Max. The parameter is only available for the profit type "In deposit currency".
  • In addition to the number of short and long trades, the percentage of winning trades of both types is displayed.
  • Strategy profitability. The ratio of total profit to total loss.
  • Recovery factor. The parameter is only available for the profit type "In deposit currency".

You can read more about the new parameters in the Testing Report section of the MetaTrader 5 Help. The prototype of the above features is shown in fig.1.

Fig.1 Prototype of new testing tools.

Another new features is the possibility to visually view the testing result of any strategy. It means you can view the testing results graph. We will add the "Open graph" button (shown in fig.1) to the Report section of our app.

Fig.2 Graph appearance.

As seen in fig.2, the deposit movement nature and trade results can be visually evaluated on the chart. For convenience, the title displays the tested symbol, its timeframe, and the time interval in which testing is performed.

New Feature Implementation Stages

Let us define the main elements and implementing methods, similarly to what we did when developing the Strategy Builder from scratch. Two methods have been added to the main method creating the CreateGUI() interface. Let's view these methods and the additions to previously existing method.

//+------------------------------------------------------------------+
//| 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);
}

Let's view the changes in the main window creation method CreateWindow(): new interface elements have been added to implement new testing instruments shown in fig.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);
   }
....

Visual changes were implemented only in the Constructor tab. The full tab implementation code is available in the attachments and in the previous article, while here only new methods are shown. Let's consider each of them. 

CreateProfitType(). The method crates a drop-down list with the profit type used in testing: Deposit currency or Points.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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(). The method creates a drop-down lost with the lot type: Constant or Balance dependent.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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(). The method creates the lot value input field.

/+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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(). Creates an input field for the initial deposit when testing in the Profit — Currency mode.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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, a click on which opens the chart window.

//+------------------------------------------------------------------+
//| 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);
}

The structure of output of Report characteristics was modified in CreateWindow(). It was supplemented and implemented in three columns, instead of two before. These are all the modifications in the main window creation method CreateWindow().

Next, let's move on to the method that implements the chart window and the report graph — 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);
}

The creation method is quite small. Please pay attention to the CreateGraph() method contained in it.

//+------------------------------------------------------------------+
//| 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);
}

It creates an empty graph and sets its visual characteristics. The graph data will be added later.

One more method in the main CreateGUI() is CreateLoading(), which creates the loading window. It serves as an indicator for application loading, changing of language settings, as well as for testing and working with data.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#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);
}

We have considered the visual elements of the Strategy Builder. Let's move on to the testing algorithm and see how the information is displayed in it using the new visual controls.

As you may remember from the previous article, strategy testing launch and processing is implemented by the GetResult() method. We have added new data and thus we need to revise this method.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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();
}

The following modifications are implemented:

  • A check is added to verify that patterns for buy and sell signals do not match.
  • Added use of the previously created Download window.
  • Added AddDeal() method, which is responsible for writing testing results to the data array that is used by the graph. The initial value for the graph is set here. With the Profit type "Currency" the initial value is "Currency". For "Points" it is zero.

Now let us view the AddDeal() data adding method:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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;
   }
}

It has two values in the arguments:

  1. points is used for the new value for the chart. The received value is the deal closing result in points. This is either Take Profit or Stop Loss.
  2. index is the index of the trade.

Depending on the selected Profit display mode, appropriate data is calculated in the method and is added to the data array. For the "Points" profit mode, each new element of the array is the sum of the previous value and the trade result in points. In the "Currency" mode, the received deal results in points is converted to the deposit currency. This operation takes into account the type of the tested pair: direct, reverse or cross pair.

The other two methods that have been modified are CalculateBuyDeals() and  CalculateSellDeals(). They process the found signal and virtually open a position if necessary. Let's view one of the methods (modifications in the second method are similar):

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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;
}

Changes concern Take Profit and Stop Loss triggering. Here, the above described AddDeal() method processes the fact of a deal closing. Also new parameters profit_pips and loss_pips have been added to the REPORT m_report structure. These parameters allow calculating new characteristics in the report.

And the last method which has been significantly modified is processing of received data and output of the results to the report. Again this is performed by the PrintReport() method:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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;
}

As it was mentioned in the overview, some parameters, for example Total profit or Recovery factor, are not available for the "Points" testing mode.


Strategy Builder Testing and Demonstration

Following the improvements and updates implemented in the application, there is a need to update the user manual.

Step 1. Set the user interface language. 

This step is optional and is only needed if you wish to change the language.

Step 2. Set indicator parameters.

The application already has default parameters, therefore there is no need to configure all indicators. Change only the required parameters. If necessary, you can always change the settings.

Step 3. Set the table of symbols.

By default, all symbols from the Market Watch are filtered by the presence of USD in the name. You can display all available symbols by simply unchecking the checkbox.

Step 4-5. Select the testing time interval and timeframe - these steps have not changed.

Step 6. Enable sell/buy signals and select a pattern for testing.

The signal setup procedure has not changed. However, changes apply to the visual display: if one of the signal types is disabled, all relevant settings will be hidden, as it is shown in fig.3.

Fig.3 Disabling buy or sell signals.

Step 7. Take Profit and Stop Loss settings have not changed.

Step 8. Select Profit type.

  • When Points is selected, testing results in the Report section will be shown in points of the selected currency symbol. 
  • If Currency is selected, additional settings appear: Lot Type, Lot Size and Initial Deposit. Lot Type affects the calculation of lot which will be applied in testing. It can be Constant or based on Balance.

Step 9. After steps 1-8, select the testing instrument by a left-click in the table. 

After testing is completed, the results will appear in the Report section. After that you can click Open graph.

Testing following the above algorithm is shown in the next video.



Recommendations for testing Merrill patterns:

  • To allow the application work correctly, we need historical data for testing on a specified trading symbol to be downloaded.
  • Be careful with custom indicator parameters: buffer number, name or parameters should be separated by commas. Note that only numeric values are supported. Custom indicator name is the path relative to the Indicators root directory (MQL5/Indicators/). If the indicator is located in a subdirectory, for example MQL5/Indicators/Examples, the name should look accordingly: "Examples\\indicator_name" (always use a double backslash instead of a single one as a separator).
  • The most common scenarios that may cause difficulties are accompanied by tips. These include the same patterns for both signals, attempt to open a graph without performing prior tests, incorrect testing beginning and ending dates.
  • At the beginning of the article, I mentioned a link to the descriptions of characteristics used in the Report. Use the link to read more about the parameter descriptions and calculation methods.

Conclusion

The archive attached below contains all described files properly arranged into folders. For correct operation, place the MQL5 folder to the terminal's root directory. To open the terminal root directory, in which the MQL5 folder is located, press the Ctrl+Shift+D key combination in the MetaTrader 5 terminal or use the context menu as shown in Fig. 5 below.


Fig. 5. Opening the MQL5 folder in the MetaTrader 5 terminal root


Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/7361

Attached files |
MQL5.zip (1936.58 KB)
Library for easy and quick development of MetaTrader programs (part XXIV): Base trading class - auto correction of invalid parameters Library for easy and quick development of MetaTrader programs (part XXIV): Base trading class - auto correction of invalid parameters

In this article, we will have a look at the handler of invalid trading order parameters and improve the trading event class. Now all trading events (both single ones and the ones occurred simultaneously within one tick) will be defined in programs correctly.

Continuous Walk-Forward Optimization (Part 1): Working with Optimization Reports Continuous Walk-Forward Optimization (Part 1): Working with Optimization Reports

The first article is devoted to the creation of a toolkit for working with optimization reports, for importing them from the terminal, as well as for filtering and sorting the obtained data. MetaTrader 5 allows downloading optimization results, however our purpose is to add our own data to the optimization report.

Library for easy and quick development of MetaTrader programs (part XXV): Handling errors returned by the trade server Library for easy and quick development of MetaTrader programs (part XXV): Handling errors returned by the trade server

After we send a trading order to the server, we need to check the error codes or the absence of errors. In this article, we will consider handling errors returned by the trade server and prepare for creating pending trading requests.

Exploring Seasonal Patterns of Financial Time Series with Boxplot Exploring Seasonal Patterns of Financial Time Series with Boxplot

In this article we will view seasonal characteristics of financial time series using Boxplot diagrams. Each separate boxplot (or box-and-whiskey diagram) provides a good visualization of how values are distributed along the dataset. Boxplots should not be confused with the candlestick charts, although they can be visually similar.