Ampliamos la funcionalidad del Constructor de estrategias

Alexander Fedosov | 23 enero, 2020

Contenido

Introducción

En el primer artículo de esta serie, conocimos las figuras técnicas de Merrill y las aplicamos a diferentes conjuntos de datos, como el precio, así como los indicadores del tipo oscilador derivados de él, como ATR, CCI, WPR y los demás. Nuestro objetivo fue crear una herramienta para buscar y estimar la perspectiva del uso de los patrones en los mercados de divisas y los demás. En el segundo artículo, desarrollamos un Constructor de estrategias capaz de modelar unas estrategias simples usando las figuras ya consideradas, y obtener los resultados. En el presente artículo, ampliaremos considerablemente la funcionalidad de creación y simulación de estrategias. Añadiremos la posibilidad de trabajar no sólo con los puntos, sino también con lotes, así como, la posibilidad de visualizar los resultados de la simulación.

Análisis de las novedades y adiciones

Antes de ver las adiciones, me gustaría recordar que en la versión anterior, los resultados de todas las pruebas fueron visualizados en un informe sumario, mientras que el propio resultado fue representado en forma del beneficio o la pérdida en puntos del instrumento investigado. No obstante, eso no nos permitía evaluar el potencial de una estrategia integralmente. Por tanto, el objetivo principal de las adiciones será el aumento de las posibilidades del Simulador de estrategias, y también la extensión de parámetros del informe comercial que se deriva de ello.

Con relación a estos objetivos, el plan de las mejoras será el siguiente:

La lista del informe comercial también incluirá las siguientes modificaciones:

Para más detalles sobre los parámetros introducidos, consulte el apartado Informe sobre la simulación de la Guía de ayuda de MetaTrader 5. En la fig. 1, se muestra un prototipo de las posibilidades descritas más arriba.

Fig. 1 Prototipo de nuevas herramientas de simulación.

Otra novedad consiste en poder ver visualmente el resultado de la simulación de cualquier estrategia. Se trata del gráfico de los resultados de la simulación. En nuestra aplicación, podemos abrir este gráfico al pulsar el botón «Abrir gráfico» (fíjese en la fig. 1) en el bloque Informe.

Fig. 2. La apariencia del gráfico.

Como podemos observar en la fig. 2, el gráfico permite evaluar visualmente el carácter del movimiento del depósito y los resultados de transacciones. Por motivos de conveniencia, el encabezado incluye el instrumento testeado, su marco temporal y el intervalo de tiempo de la simulación.

Etapas de la implementación de novedades

Igual como en caso de la implementación del Constructor de estrategias desde cero, vamos a definir los principales elementos y los métodos que los implementan. Así, fueron añadidos dos métodos nuevos al método principal de la creación de la interfaz CreateGUI(). Además, analizaremos las adiciones en los métodos ya existentes.

//+------------------------------------------------------------------+
//| Crea la interfaz gráfica del programa                          |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Creación del panel
   if(!CreateWindow("Merrill Constructor"))
      return(false);
//--- Creando la ventana de diálogo
   if(!CreateDateSetting())
      return(false);
//--- Creando la ventana del gráfico
   if(!CreateGraphWindow())
      return(false);
//--- Creando la ventana de descarga
   if(!CreateLoading())
      return(false);
//--- Terminar la creación de GUI
   CWndEvents::CompletedGUI();
   return(true);
}

Vamos a considerar las modificaciones respecto al método de la creación de la ventana principal CreateWindow(), a saber, la adición de los elementos de la interfaz que implementan nuevas herramientas de la simulación mostradas más arriba, en la fig. 1.

//---- Pestaña CONSTRUCTOR
....
//---
   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);
//--- Botón para abrir el Gráfico
   if(!CreateIconButton(int(0.35*(m_window[0].XSize()-150)-50),100+35*7+ygap))
      return(false);
//--- Líneas del informe
   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);
   }
....

Los cambios visuales se refieren sólo a la pestaña Constructor. Para ver la implementación completa de esta pestaña, por favor, consulte los códigos fuente, o bien diríjase al artículo anterior. Aquí, han sido elegidos los métodos que representan la adición. Vamos a examinar cada uno de ellos. 

Método CreateProfitType(). Crea una lista desplegable con el tipo del beneficio durante la simulación: Divisa del depósito o Puntos.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateProfitType(const int x_gap,const int y_gap)
{
//--- Transferir el objeto del panel
   m_profit_type.MainPointer(m_tabs1);
//--- Fijar en la pestaña
   m_tabs1.AddToElementsArray(0,m_profit_type);
//--- Array de valores de los puntos en la lista
   string pattern_names[2]=
   {
      "Pips","Currency"
   };
//--- Establecemos las propiedades antes de la creación
   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);
//--- Guardamos los valores de los puntos en la lista del cuadro combinado (combobox)
   for(int i=0; i<2; i++)
      m_profit_type.SetValue(i,pattern_names[i]);
//--- Obtenemos el puntero de la lista
   CListView *lv=m_profit_type.GetListViewPointer();
//--- Establecemos las propiedades de la lista
   lv.LightsHover(true);
   m_profit_type.SelectItem(1);
//--- Creamos el control
   if(!m_profit_type.CreateComboBox("Profit Type",x_gap,y_gap))
      return(false);
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(0,m_profit_type);
   return(true);
}

Método CreateLotType(). Crea una lista desplegable con el tipo del lote: Constante y Del balance.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateLotType(const int x_gap,const int y_gap)
{
//--- Transferir el objeto del panel
   m_lot_type.MainPointer(m_tabs1);
//--- Fijar en la pestaña
   m_tabs1.AddToElementsArray(0,m_lot_type);
//--- Array de valores de los puntos en la lista
   string pattern_names[2]=
   {
      "Balance","Constant"
   };
//--- Establecemos las propiedades antes de la creación
   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);
//--- Guardamos los valores de los puntos en la lista del cuadro combinado (combobox)
   for(int i=0; i<2; i++)
      m_lot_type.SetValue(i,pattern_names[i]);
//--- Obtenemos el puntero de la lista
   CListView *lv=m_lot_type.GetListViewPointer();
//--- Establecemos las propiedades de la lista
   lv.LightsHover(true);
   m_lot_type.SelectItem(1);
//--- Creamos el control
   if(!m_lot_type.CreateComboBox("Lot Type",x_gap,y_gap))
      return(false);
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(0,m_lot_type);
   return(true);
}

Метод CreateBaseLotValue(). Crea el campo de edición del valor del lote.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateBaseLotValue(const int x_gap,const int y_gap)
{
//--- Guardamos el puntero al control principal
   m_base_lot.MainPointer(m_tabs1);
//--- Fijar en la pestaña
   m_tabs1.AddToElementsArray(0,m_base_lot);
//--- Propiedades
   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);
//--- Creamos el 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));
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(0,m_base_lot);
   return(true);
}

Метод CreateInitialDeposit(). Crea el campo de edición del depósito inicial durante la simulación en el modo Beneficio — Divisa.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateInitialDeposit(const int x_gap,const int y_gap)
{
//--- Guardamos el puntero al control principal
   m_init_deposit.MainPointer(m_tabs1);
//--- Fijar en la pestaña
   m_tabs1.AddToElementsArray(0,m_init_deposit);
//--- Propiedades
   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);
//--- Creamos el control
   if(!m_init_deposit.CreateTextEdit("Initial Deposit",x_gap,y_gap))
      return(false);
   m_init_deposit.SetValue((string)1000);
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(0,m_init_deposit);
   return(true);
}

Метод CreateIconButton(). Crea el botón al pulsar el cual se abre la ventana del gráfico.

//+------------------------------------------------------------------+
//| Crea un botón con imagen                                       |
//+------------------------------------------------------------------+
#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)
{
//--- Guardamos el puntero al control principal
   m_graph_button.MainPointer(m_tabs1);
//--- Fijar en la pestaña
   m_tabs1.AddToElementsArray(0,m_graph_button);
//--- Propiedades
   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);
//--- Creamos el control
   if(!m_graph_button.CreateButton("",x_gap,y_gap))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,m_graph_button);
   return(true);
}

Además, en el método CreateWindow(), fue alterada la estructura de la visualización de las características del propio Informe. Fue completada e implementada como tres columnas, en vez de dos como antes. Pues, éstas son todas las adiciones en el método de la construcción de la ventana principal CreateWindow().

A continuación, pasamos al método que implementa la ventana del gráfico y el propio gráfico, es decir, CreateGraphWindow().

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateGraphWindow(void)
{
//--- Añadimos el puntero de la ventana al array de ventanas
   CWndContainer::AddWindow(m_window[2]);
//--- Propiedades
   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);
//--- Creando el formulario
   if(!m_window[2].CreateWindow(m_chart_id,m_subwin,"",75,75))
      return(false);
   //--- Gráficos
   if(!CreateGraph(22,22))
      return(false);
//---
   return(true);
}

El método de la creación de la ventana en sí no es demasiado grande, pero merece la pena detenernos en el método CreateGraph() incluido dentro de él.

//+------------------------------------------------------------------+
//| Crea el gráfico                                                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGraph(const int x_gap,const int y_gap)
{
//--- Guardamos el puntero al control principal
   m_graph1.MainPointer(m_window[2]);
//--- Propiedades
   m_graph1.AutoXResizeMode(true);
   m_graph1.AutoYResizeMode(true);
   m_graph1.AutoXResizeRightOffset(10);
   m_graph1.AutoYResizeBottomOffset(10);
//--- Creando el control
   if(!m_graph1.CreateGraph(x_gap,y_gap))
      return(false);
//--- Propiedades del gráfico
   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);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(2,m_graph1);
   return(true);
}

Por ahora, crea un gráfico en blanco, ajustando simplemente sus características visuales. Vamos a llenar el gráfico un poco más tarde.

El último método que forma parte del método principal CreateGUI(), es CreateLoading() que sirve para crear la ventana de descarga. Fue introducido como una referencia informativa durante la carga de la aplicación, cambio de los ajustes del idioma o el testeo y trabajo con datos.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\sandglass.bmp"
bool CProgram::CreateLoading(void)
{
//--- Añadimos el puntero de la ventana al array de ventanas
   CWndContainer::AddWindow(m_window[3]);
//--- Propiedades
   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);
//--- Creando el formulario
   if(!m_window[3].CreateWindow(m_chart_id,m_subwin,"Working...",x,y))
      return(false);
   return(true);
}

Hemos terminado el trabajo con los elementos visuales del Constructor de estrategias. Ahora hablaremos del propio algoritmo de simulación y participación de nuevos controles y visualización de la información.

Como podemos recordar del artículo anterior, el método GetResult() se encarga de iniciar y procesar la simulación de la estrategia establecida. No obstante, habrá que completarlo debido a la aparición de nuevos datos de entrada.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::GetResult(const string symbol)
{
//--- Obtener intervalo de fechas
   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");
//--- Verificando si las fechas establecidas son correctas
   if(m_start_date>m_end_date || m_end_date>TimeCurrent())
   {
      if(m_lang_index==0)
         MessageBox("Intervalo de fechas inválido","Error",MB_OK);
      else if(m_lang_index==1)
         MessageBox("Incorrect date range selected!","Error",MB_OK);
      return;
   }
//--- Verificando la corrección de patrones definidos
   if(m_combobox1.GetListViewPointer().SelectedItemIndex()==m_combobox2.GetListViewPointer().SelectedItemIndex())
   {
      if(m_lang_index==0)
         MessageBox("¡Los patrones no pueden ser iguales!","Error",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("¡La ruta hacia el indicador no está definida!","Error",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("¡Los parámetros del indicador no están definidos!","Error",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;
   }
//--- Visualización del informe
   PrintReport();
//---
   m_window[3].CloseDialogBox();
}

Hablando de la implementación modificada, nos fijaremos en los siguientes momentos:

Ahora analizaremos el propio método para añadir los datos AddDeal():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AddDeal(int points,int index)
{
//--- En puntos
   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;
   }
//--- En moneda del depósito
   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);
      //--- Obtenemos el símbolo seleccionado
      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;
      //--- Par directo
      if(StringSubstr(symbol,3,3)==basesymbol)
      {
         pip_price=NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(symbol,SYMBOL_POINT)*lot,2);
      }
      //--- Par inverso
      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
      {
         //--- Cruzamiento
         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;
   }
}

En los argumentos, tiene dos valores:

  1. points — un nuevo valor para el gráfico. Aquí, el valor recibido es el resultado del cierre de la transacción en puntos. Es el valor del Take Profit o de Stop Loss.
  2. index — en realidad, es el número de orden de la transacción.

Luego, dependiendo del modo de visualización del Beneficio seleccionado, se realiza el cálculo y la introducción de datos en el array data. Para el modo del Beneficio «Puntos», cada nuevo elemento del array es la suma del elemento anterior y del resultado de la transacción en puntos. En caso del segundo modo, «Divisa», el resultado de la transacción obtenido en puntos se convierte en la moneda del depósito. Además, se toma en cuenta el tipo del par de divisas testeado: par directo, par inverso y cruzamiento.

Los dos siguientes métodos modificados son CalculateBuyDeals()CalculateSellDeals(). Se encargan de procesar la señal encontrada o abren una posición virtualmente. Vamos a ver las modificaciones de uno de los métodos, ya que los cambios del segundo son los mismos:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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++)
   {
      //--- Disparo del Take Profit
      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;
      }
      //--- Disparo de Stop Loss
      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;
}

Los cambios han afectado los lugares del disparo de Take Profit y Stop Loss. Aquí, el método AddDeal() descrito anteriormente procesa el hecho del cierre de la transacción. Además, la estructura REPORT m_report ha sido extendida con los parámetros profit_pips y loss_pips. Son necesarios para calcular nuevas características en el Informe.

El último método que ha sido modificado considerablemente es el procesamiento de datos obtenidos y su visualización en el Informe. Igual como en el artículo anterior, el método PrintReport() se encarga de ello:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::PrintReport(void)
{
   string report_label[11];
   if(m_lang_index==0)
   {
      report_label[0]="Total trades: ";
      report_label[1]="Beneficio neto: ";
      report_label[2]="Beneficio en puntos: ";
      report_label[3]="Reducción absoluta del balance: ";
      report_label[4]="Reducción máxima del balance: ";
      report_label[5]="Trades corrtos/% rentables: ";
      report_label[6]="Trades rentables/% de todos: ";
      report_label[7]="Factor de rentabilidad: ";
      report_label[8]="Factor de recuperación: ";
      report_label[9]="Trades largos/% rentables: ";
      report_label[10]=" Trades no rentables/% de todos: ";
   }
   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;
}

Como ha sido mencionado en el análisis de Novedades, algunos de los parámetros (por ejemplo, Beneficio neto o Factor de recuperación) no están disponibles para el modo de la simulación «Puntos».


Orden de la simulación y demostración del trabajo con el Constructor de estrategias

Puesto que la propia aplicación ha sufrido modificaciones y actualizaciones, habrá que completar la guía de uso.

Paso 1. Definir el idioma de la interfaz. 

Este paso es opcional, y sólo es necesario si el idioma de la interfaz no le conviene.

Paso 2. Definir los parámetros de indicadores.

La aplicación ya dispone los ajustes predefinidos, por eso, no es necesario configurar absolutamente todo, sólo aquellos parámetros con los que se va a trabajar. Si hace falta, siempre podrá volver y modificar los parámetros.

Paso 3. Configurar la tabla de los símbolos.

Por defecto, se realiza la filtración de todos los símbolos disponibles en la pestaña Observación del mercado según la presencia de la parte USD. Para visualizar todos los símbolos disponibles, no es obligatorio quitarlos a través del campo de edición del filtro, basta con desmarcarlos.

Paso 5. El orden de selección del Intervalo temporal de la simulación y del Marco temporal ha quedado sin alterar.

Paso 6. Activación de las señales de venta/compra y selección de la figura técnica para la simulación.

El orden de la configuración de señales ha quedado sin alterar. Pero la visualización ha sufrido modificaciones, a saber, al desactivar un tipo de las señales, sus ajustes se ocultan, tal como se muestra en la figura 3.

Fig. 4. Desactivar de las señales de compra o venta.

Paso 7. La configuración de Take Profit y Stop Loss también ha quedado sin alterar.

Paso 8. Selección del tipo del Beneficio.

Paso 9. Una vez ejecutados los pasos 1-8, para iniciar la simulación, seleccione el instrumento a testear en la tabla de los símbolos usando el botón izquierdo del ratón. 

Después de la conclusión de la simulación, sus resultados aparecerán en el bloque Informe. Sólo después de eso, puede pulsar el botón Abrir Gráfico.

El siguiente vídeo muestra un ejemplo del funcionamiento según el orden de simulación expuesto.



Recomendaciones para el testeo de las figuras técnicas de Merrill:

Conclusión

Al final del articulo se adjunta el archivo comprimido con todos los ficheros mencionados, ordenados por carpetas. Por eso, para un trabajo correcto basta con colocar la carpeta MQL5  en la raíz del terminal. Para abrir el directorio raíz del terminal que contiene la carpeta MQL5, pulse en la combinación Ctrl+Shift+D o utilice el menú contextual, tal como se muestra en la imagen 5.


Fig. 5 Abrir la carpeta MQL5 en el directorio raíz del terminal MetaTrader 5