English Русский 中文 Deutsch 日本語 Português
Visualización de los resultados de la optimización según el criterio seleccionado

Visualización de los resultados de la optimización según el criterio seleccionado

MetaTrader 5Ejemplos | 21 mayo 2018, 08:45
1 899 0
Anatoli Kazharski
Anatoli Kazharski

Contenido

Introducción

Continuamos el desarrollo de la aplicación para el trabajo con los resultados de la optimización que hemos empezado en los artículos anteriores. Analizaremos el ejemplo cuando se puede formar la tabla de los mejores resultados después de optimizar los parámetros indicando otro criterio a través de la interfaz gráfica. En el artículo anterior, he demostrado cómo se trabaja con el conjunto de las categorías de los datos: guardarlos en el array del frame, y luego extraerlos desde él. Así, hemos trabajado con las indicaciones estadísticas, arrays de los balances de los símbolos y con las reducciones (drawdown) del depósito.

Después de la optimización, podemos observar estos datos en los gráficos diferentes, simplemente seleccionando las filas correspondientes de la tabla. Pero no hay límite para la perfección. Para ver los resultados seleccionados según uno u otro criterio, nos gustaría ver todos sus balances en un gráfico separado. La selección de las filas de la tabla va a formar la curva seleccionada del balance en este gráfico compuesto. Así, podremos estimar mejor los resultados de la optimización. 

Aparte de eso, demostraré cómo se puede manejar la selección de las filas de la tabla (a petición de algunos participantes de la comunidad). Para eso, fue necesario modificar la clase CTable de nuestra biblioteca.

Desarrollo de la interfaz gráfica

En nuestra versión anterior de la aplicación hay tres pestañas en la interfaz gráfica: Frames, Results y Balance.

En la pestaña Frames se encuentran los elementos para el trabajo y visualización de todos los resultados durante la optimización y después de ella.

Y ahora vamos a combinar la segunda pestaña (Results) con la tercera (Balance). Por tanto, después de seleccionar una fila de la tabla, en seguida veremos el resultado en el gráfico sin tener que pasar a otra pestaña.

En la pestaña Results, colocaremos otro grupo de pestañas: Balances y Favorites.La pestaña Balances va a contener los gráficos para ver los balances de multisímbolos y reducciones (drawdown) del depósito, así como la lista de los símbolos que participan en la prueba. En la pestaña Favorites colocaremos el gráfico de los mejores resultados de la tabla. Aparte de eso, añadiremos el control del tipo CComboBox (lista desplegable). Permitirá seleccionar un criterio para la selección de los mejores resultados desde la lista general de los frames.

La jerarquía completa de los controles de la interfaz gráfica ahora tiene el siguiente aspecto:

  • Formulario para los controles
  • Barra de estado para mostrar la información final adicional
  • Primer grupo de pestañas:
    • Frames:
      • Campo de introducción de la cantidad de los balances mostrados de los resultados durante el desplazamiento (scroll) repetido tras la optimización
      • Retardo en milisegundos durante el scroll de los resultados
      • Botón del inicio del scroll repetido de los resultados
      • Gráfico para visualizar la cantidad especificada de los balances de resultados
      • Gráfico que muestra todos los resultados
    • Results:
      • Tabla de los mejores resultados
      • Cuadro combinado (combobox) con lista desplegable para seleccionar el criterio según el cual va a formarse la tabla
      • Segundo grupo de pestañas:
        • Balance:
          • Gráfico para visualizar el balance de multisímbolos del resultado seleccionado
          • Gráfico para visualizar las reducciones (drawdown) del resultado seleccionado
          • Lista de balances de los símbolos que participan en la prueba
        • Favorites:
          • Gráfico con los mejores resultados desde la tabla
  • Indicador de la ejecución del proceso de la repetición de los frames

El código de los métodos para crear estos controles se ubica en un archivo separado y se incluye en el archivo con la clase del programa MQL:

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                   |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Ventana
   CWindow           m_window1;
   //--- Barra de estado
   CStatusBar        m_status_bar;
   //--- Pestañas
   CTabs             m_tabs1;
   CTabs             m_tabs2;
   //--- Campos de edición
   CTextEdit         m_curves_total;
   CTextEdit         m_sleep_ms;
   //--- Botones
   CButton           m_reply_frames;
   //--- Combobox
   CComboBox         m_criterion;
   //--- Gráficos
   CGraph            m_graph1;
   CGraph            m_graph2;
   CGraph            m_graph3;
   CGraph            m_graph4;
   CGraph            m_graph5;
   //--- Tablas
   CTable            m_table_main;
   CTable            m_table_symbols;
   //--- Indicador de progreso
   CProgressBar      m_progress_bar;
   //---
public:
   //--- Crea la interfaz gráfica
   bool              CreateGUI(void);
   //---
private:
   //--- Formulario
   bool              CreateWindow(const string text);
   //--- Barra de estado
   bool              CreateStatusBar(const int x_gap,const int y_gap);
   //--- Pestañas
   bool              CreateTabs1(const int x_gap,const int y_gap);
   bool              CreateTabs2(const int x_gap,const int y_gap);
   //--- Campos de edición
   bool              CreateCurvesTotal(const int x_gap,const int y_gap,const string text);
   bool              CreateSleep(const int x_gap,const int y_gap,const string text);
   //--- Botones
   bool              CreateReplyFrames(const int x_gap,const int y_gap,const string text);
   //--- Combobox
   bool              CreateCriterion(const int x_gap,const int y_gap,const string text);
   //--- Gráficos
   bool              CreateGraph1(const int x_gap,const int y_gap);
   bool              CreateGraph2(const int x_gap,const int y_gap);
   bool              CreateGraph3(const int x_gap,const int y_gap);
   bool              CreateGraph4(const int x_gap,const int y_gap);
   bool              CreateGraph5(const int x_gap,const int y_gap);
   //--- Botones
   bool              CreateUpdateGraph(const int x_gap,const int y_gap,const string text);
   //--- Tablas
   bool              CreateMainTable(const int x_gap,const int y_gap);
   bool              CreateSymbolsTable(const int x_gap,const int y_gap);
   //--- Indicador de progreso
   bool              CreateProgressBar(const int x_gap,const int y_gap,const string text);
  };
//+------------------------------------------------------------------+
//| Métodos para crear los controles                                 |
//+------------------------------------------------------------------+
#include "CreateGUI.mqh"
//+------------------------------------------------------------------+

Selección de los resultados de la optimización

Para visualizar todos los mejores resultados de la optimización en el mismo gráfico, necesitaremos el método que va a devolver true hasta que encuentre el número de los resultados especificado en los parámetros. Una vez encontrados, el método devolverá false. Se trata del método CFrameGenerator::UpdateBestResultsGraph() que se muestra a continuación. Por defecto, se muestran 100 mejores resultados de la optimización

En este método se utiliza el ciclo doble. El primer ciclo está limitado con la cantidad de los mejores resultados a mostrar y la cantidad de las filas de la tabla para evitar la salida fuera del rango en la estructura de los arrays de la tabla de datos. En cada iteración de este ciclo, el puntero de los frames se desplaza al principio de la lista de los frames.

En el segundo ciclo, repasando los frames, buscamos el número del recorrido que hemos guardado antes en la estructura de arrayas. La estructura de los arrays debe ser ordenada según el criterio especificado antes de la llamada al método CFrameGenerator::UpdateBestResultsGraph(). Luego, cuando tenemos encontrado el número del repaso, obtenemos los parámetros del EA en este repaso y su cantidad. Después de eso, obtenemos el balance del resultado del repaso actual desde su array de datos (m_data[]). Es necesario recordar que los datos del balance general se encuentran en el array del frame después de las indicaciones estadísticas, y el tamaño del array será igual al valor en el parámetro double del frame. Este array, como la serie de datos, se coloca en el gráfico de los balances de los mejores resultados. Si el resultado final de esta prueba supera el depósito inicial, la línea será verde, de lo contrario, es roja. El tamaño de la serie se guarda en un array separado para que tras la finalización del ciclo haya posibilidad de determinar la serie con la cantidad máxima de los elementos para definir los límites del eje X. Y finalmente, hay que aumentar el contador de los frames en uno, para que luego podamos continuar el ciclo evitando este repaso.

Si el ciclo ha sido completado, a continuación:

  • el contador de los frames se anula,
  • se determina la serie de los datos con el número máximo de los elementos,
  • para el gráfico de los mejores resultados se establecen los parámetros y él se actualiza.

Después de eso, el método CFrameGenerator::UpdateBestResultsGraph() devolverá false. Eso quiere decir que la selección de los resultados queda por terminada.

//+------------------------------------------------------------------+
//| Clase para trabajar con los resultados de la optimización        |
//+------------------------------------------------------------------+
class CFrameGenerator
  {
private:
   //--- Cantidad de los mejores resultados
   int               m_best_results_total;
   //---
public:
  //--- Actualizar el gráfico de los mejores resultados
   bool              UpdateBestResultsGraph(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CFrameGenerator::CFrameGenerator(void) : m_best_results_total(100)
  {
  }
//+------------------------------------------------------------------+
//| Actualizar el gráfico de los mejores resultados                  |
//+------------------------------------------------------------------+
bool CFrameGenerator::UpdateBestResultsGraph(void)
  {
   for(int i=(int)m_frames_counter; i<m_best_results_total && i<m_rows_total; i++)
     {
      //--- Mover el puntero de los frames al principio
      ::FrameFirst();
      //--- Extracción de datos
      while(::FrameNext(m_pass,m_name,m_id,m_value,m_data))
        {
         //--- Los números de los repasos no coinciden, pasar al siguiente
         if(m_pass!=(ulong)m_columns[0].m_rows[i])
            continue;
         //--- Obtenemos los parámetros y su cantidad
         GetParametersTotal();
         //--- Obtenemos el balance del resultados actual
         double serie[];
         ::ArrayCopy(serie,m_data,0,STAT_TOTAL,(int)m_value);
         //--- Enviamos el array para mostrar en el gráfico del balance
         CCurve *curve=m_graph_best.CurveGetByIndex(i);
         curve.Name((string)m_pass);
         curve.Color((m_data[m_profit_index]>=0)? ::ColorToARGB(clrLimeGreen) : ::ColorToARGB(clrRed));
         curve.Update(serie);
         //--- Obtenemos el tamaño de la serie
         m_curve_max[i]=::ArraySize(serie);
         //--- Aumentar el contador de los frames
         m_frames_counter++;
         return(true);
        }
     }
//--- Resetear el contador de los frames
   m_frames_counter=0;
//--- Determinar la serie con el número máximo de los elementos
   double x_max=m_curve_max[::ArrayMaximum(m_curve_max)];
//--- Propiedades del eje horizontal
   CAxis *x_axis=m_graph_best.XAxis();
   x_axis.Min(0);
   x_axis.Max(x_max);
   x_axis.DefaultStep((int)(x_max/8.0));
//--- Actualizar el gráfico
   m_graph_best.CalculateMaxMinValues();
   m_graph_best.CurvePlotAll();
   m_graph_best.Update();
   return(false);
  }

Para encontrar los resultados, hay que recorrer todos los frames de la lista general. Eso requiere tiempo. Por eso, monitorear el estado de la búsqueda, usamos el control «Barra de progreso» (CProgressBar). Para eso, en la clase de la aplicación (CProgram) ha sido implementado el método CProgram::GetBestOptimizationResults(). Aquí, en el ciclo while se invoca el método CFrameGenerator::UpdateBestResultsGraph() como condición. Antes de iniciar el ciclo, hagamos que la barra de progreso sea visible. Puesto que en el método CFrameGenerator::UpdateBestResultsGraph() se utiliza el contador de los frames, se puede obtener su valor actual. Después de la finalización del ciclo, hay que ocultar la barra de progreso.

class CProgram : public CWndEvents
  {
private:
   //--- Obtener los mejores resultados de la optimización
   void              GetBestOptimizationResults(void);
  };
//+------------------------------------------------------------------+
//| Obtener los mejores resultados de la optimización                |
//+------------------------------------------------------------------+
void CProgram::GetBestOptimizationResults(void)
  {
//--- Mostrar la barra de progreso
   m_progress_bar.Show(); 
//--- Visualizamos el proceso de la obtención de los mejores resultados
   int best_results_total=m_frame_gen.BestResultsTotal();
   while(m_frame_gen.UpdateBestResultsGraph() && !::IsStopped())
     {
      //--- Actualizar la barra de progreso
      m_progress_bar.LabelText("Selection of results: "+string(m_frame_gen.CurrentFrame())+"/"+string(best_results_total));
      m_progress_bar.Update((int)m_frame_gen.CurrentFrame(),best_results_total);
     }
//--- Ocultar la barra de progreso
   m_progress_bar.Hide();
  }

El método CProgram::GetBestOptimizationResults() debe invocarse en el método de la finalización de la optimización. De esta manera, el usuario va a comprender que el programa se ejecuta y «no se ha colgado». Los demás métodos han sido considerados en los artículos anteriores, por eso no vamos a mencionarlos aquí.

//+------------------------------------------------------------------+
//| Evento de la finalización del proceso de la optimización         |
//+------------------------------------------------------------------+
void CProgram::OnTesterDeinitEvent(void)
  {
//--- Finalización de la optimización
   m_frame_gen.OnTesterDeinitEvent();
//--- Visualizamos el proceso de la obtención de los mejores resultados
   GetBestOptimizationResults();
//--- Hacer que la interfaz esté disponible
   IsLockedGUI(true);
//--- Cálculo de la relación de los resultados positivos y negativos
   CalculateProfitsAndLosses();
//--- Obtenemos los datos en la tabla de los resultados de la optimización
   GetFrameDataToTable();
//--- Inicialización del núcleo de GUI
   CWndEvents::InitializeCore();
  }

Inmediatamente después de la finalización de la optimización o su detención forzosa, en la barra de estado aparece la barra de progreso. Muestra al usuario que el proceso de la selección de los resultados está en marcha:

 Fig. 1 - Visualización del proceso de la selección de los resultados

Fig. 1.  Visualización del proceso de la selección de los resultados

Para ver la visualización de los balances de todos los resultados seleccionados, hay que ir a la pestaña Results, y luego, a la pestaña Favorites. Por defecto, en la tabla se insertan 100 mejores resultados según el criterio Profit. En cualquier momento, se puede elegir otro criterio de la lista desplegable Criterion para seleccionar 100 mejores resultados. Ya volveremos a hablar de ello, mientras tanto vamos a considerar los métodos de la organización de este proceso.

 Fig. 2. Gráfico de los mejores resultados de la optimización.

Fig. 2. Gráfico de los mejores resultados de la optimización.

Selección de una fila de la tabla usando los métodos de la programación

Hasta ahora podíamos seleccionar la fila en la tabla sólo con el clic izquierdo. Pero a veces es necesario hacer eso usando los métodos de la programación: por ejemplo, seleccionar las filas usando las teclas Up, Down, Home y End. Para seleccionar una fila con los métodos de la programación, a la clase CTable ha sido añadido el método publico CTable::SelectRow(). Su código parece al método privado CTable::RedrawRow(). En su lugar, se utiliza para redibujar las filas según los eventos del clic del ratón y para mover el cursor sobre la tabla cuando está activado el modo del resalto de las filas al apuntar con el cursor.

La mayor parte del código se puede volver a usar en ambos métodos. Por eso la he insertado en el método separado CTable::DrawRow(). Tenemos que insertar lo siguiente en él:

  • array de los índices para determinar la secuencia del redibujado,
  • índice actual de la fila seleccionada,
  • índice de la fila seleccionada anterior,
  • modo del uso del método: de programación (false)  o de usuario (true).
En el propio método se determinan las coordenadas para redibujar las filas y se redibujan sus elementos de forma consecutiva: fondo, cuadrícula, imágenes y texto.
//+------------------------------------------------------------------+
//| Dibuja la fila indicada de la tabla según el modo especificado   |
//+------------------------------------------------------------------+
void CTable::DrawRow(int &indexes[],const int item_index,const int prev_item_index,const bool is_user=true)
  {
   int x1=0,x2=m_table_x_size-2;
   int y1[2]={0},y2[2]={0};
//--- Número de las filas y columnas para el dibujado
   uint rows_total    =0;
   uint columns_total =m_columns_total-1;
//--- Si es el método de programación para la selección de la fila
   if(!is_user)
      rows_total=(prev_item_index!=WRONG_VALUE && item_index!=prev_item_index)? 2 : 1;
   else
      rows_total=(item_index!=WRONG_VALUE && prev_item_index!=WRONG_VALUE && item_index!=prev_item_index)? 2 : 1;
//--- Dibujamos el fondo de las filas
   for(uint r=0; r<rows_total; r++)
     {
      //--- Cálculo de las coordenadas del borde superior e inferior de la fila
      y1[r] =m_rows[indexes[r]].m_y+1;
      y2[r] =m_rows[indexes[r]].m_y2-1;
      //--- Determinamos el foco en la fila respecto al modo del resalto
      bool is_item_focus=false;
      if(!m_lights_hover)
         is_item_focus=(indexes[r]==item_index && item_index!=WRONG_VALUE);
      else
         is_item_focus=(item_index==WRONG_VALUE)?(indexes[r]==prev_item_index) :(indexes[r]==item_index);
      //--- Dibujar el fondo de la fila
      m_table.FillRectangle(x1,y1[r],x2,y2[r],RowColorCurrent(indexes[r],(is_user)? is_item_focus : false));
     }
//--- Dibujamos los bordes
   for(uint r=0; r<rows_total; r++)
     {
      for(uint c=0; c<columns_total; c++)
         m_table.Line(m_columns[c].m_x2,y1[r],m_columns[c].m_x2,y2[r],::ColorToARGB(m_grid_color));
     } 
//--- Dibujamos imágenes
   for(uint r=0; r<rows_total; r++)
     {
      for(uint c=0; c<m_columns_total; c++)
        {
         //--- Dibujamos la imagen si (1) ella está presente en esta celda y (2) alineamos el texto de esta columna por la izquierda
         if(ImagesTotal(c,indexes[r])>0 && m_columns[c].m_text_align==ALIGN_LEFT)
            CTable::DrawImage(c,indexes[r]);
        }
     }
//--- Para el cálculo de las coordenadas
   int x=0,y=0;
//--- Modo de alineación del texto
   uint text_align=0;
//--- Dibujamos el texto
   for(uint c=0; c<m_columns_total; c++)
     {
      //--- Obtenemos (1) la coordenada X del texto y (2) el modo de alineación
      x          =TextX(c);
      text_align =TextAlign(c,TA_TOP);
      //---
      for(uint r=0; r<rows_total; r++)
        {
         //--- (1) Calcular la coordenada y (2) dibujar el texto
         y=m_rows[indexes[r]].m_y+m_label_y_gap;
         m_table.TextOut(x,y,m_columns[c].m_rows[indexes[r]].m_short_text,TextColor(c,indexes[r]),text_align);
        }
     }
  }

Al método CTable::SelectRow() hay que pasar sólo un argumento: el índice de la fila a seleccionar. Aquí primero comprobamos la salida fuera del rango de la tabla y si hay una fila seleccionada con este índice. Luego, determinamos el índice actual y anterior de la fila seleccionada y la secuencia del redibujado. Pasamos los valores recibidos en el método CTable::DrawRow(). Al obtener los índices en los bordes del área visible de la tabla, se puede determinar en qué posición es necesario mover el slider de la barra de desplazamiento.

//+------------------------------------------------------------------+
//| Selección de la fila especificada en la tabla                    |
//+------------------------------------------------------------------+
void CTable::SelectRow(const int row_index)
  {
//--- Comprobar la superación del rango
   if(!CheckOutOfRange(0,(uint)row_index))
      return;
//--- Si esta fila ya está seleccionada
   if(m_selected_item==row_index)
      return;
//--- El índice actual y anterior de las filas
   m_prev_selected_item =(m_selected_item==WRONG_VALUE)? row_index : m_selected_item;
   m_selected_item      =row_index;
//--- Array para los valores en una determinada secuencia
   int indexes[2];
//--- Si estamos aquí por primera vez
   if(m_prev_selected_item==WRONG_VALUE)
      indexes[0]=m_selected_item;
   else
     {
      indexes[0] =(m_selected_item>m_prev_selected_item)? m_prev_selected_item : m_selected_item;
      indexes[1] =(m_selected_item>m_prev_selected_item)? m_selected_item : m_prev_selected_item;
     }
// Dibuja la fila indicada de la tabla según el modo especificado
   DrawRow(indexes,m_selected_item,m_prev_selected_item,false);
//--- Obtener los índices en los bordes del área visible
   VisibleTableIndexes();
//--- Mover la barra de desplazamiento en la fila especificada
   if(row_index==0)
     {
      VerticalScrolling(0);
     }
   else if((uint)row_index>=m_rows_total-1)
     {
      VerticalScrolling(WRONG_VALUE);
     }
   else if(row_index<(int)m_visible_table_from_index)
     {
      VerticalScrolling(m_scrollv.CurrentPos()-1);
     }
   else if(row_index>=(int)m_visible_table_to_index-1)
     {
      VerticalScrolling(m_scrollv.CurrentPos()+1);
     }
  }

Puede descargar la versión renovada de la clase CTable al final del artículo. La versión nueva de la biblioteca EasyAndFast está disponible en la base de los códigos.

Métodos auxiliares para trabajar con los datos de los frames

En la versión de la aplicación del artículo anterior, al seleccionar una fila en la tabla de resultados, los balances de multisímbolos y reducciones del depósito se mostraban en los gráficos. Para comprender a qué símbolo pertenece una u otra curva en el gráfico de multisímbolos, los nombres de las curvas se muestran separadamente en la parte derecha del gráfico. En esta versión, el tamaño de este gráfico por el eje Y será fijo. Si la cantidad de los símbolos en las pruebas va a ser grande, todos ellos no caberán en la zona seleccionada. Por eso, a la derecha de los gráficos vamos a colocar una lista tipo CTable con la barra de desplazamiento que va a contener todos los nombres de los balances.

Para obtener los símbolos, se usa el método CProgram::GetFrameSymbolsToTable(). Una vez obtenidos los datos del frame, se abre la posibilidad de obtener los símbolos del resultado desde el parámetro string. Al pasar el array de cadenas, obtenemos la lista de los símbolos. Si en el resultado hay más de un símbolo, es necesario hacer que el número de los balances sea en un elemento más, reservando el primero para el balance general.

A continuación, establecemos la dimensionalidad de la tabla. Aquí necesitamos sólo una columna, mientras que el número de las filas será igual la cantidad de las curvas en el gráfico. Precisamos el ancho de la columna y establecemos el nombre para el encabezado de la tabla. Llenamos ciclicamente la tabla con los nombres de balances. Para comprender qué curva pertenece a cada nombre, los vinculamos con el componente de color. Para visualizar los cambios introducidos, es necesario actualizar los elementos.

//+------------------------------------------------------------------+
//| Obtenemos los símbolos del frame en la tabla                     |
//+------------------------------------------------------------------+
void CProgram::GetFrameSymbolsToTable(void)
  {
//--- Obtenemos la lista de los símbolos y el número de las curvas
   string symbols[];
   int symbols_total  =m_frame_gen.CopySymbols(symbols);
   int balances_total =(symbols_total>1)? symbols_total+1 : symbols_total;
//--- Establecemos el tamaño de la tabla
   m_table_symbols.Rebuilding(1,balances_total,true);
//--- Ancho de la columna de la lista
   int width[]={111};
   m_table_symbols.ColumnsWidth(width);
//--- Establecemos el encabezado
   m_table_symbols.SetHeaderText(0,"Balances");
//--- Llenamos la tabla con datos desde los frames
   for(uint r=0; r<m_table_symbols.RowsTotal(); r++)
     {
      uint clr=m_graph3.GetGraphicPointer().CurveGetByIndex(r).Color();
      m_table_symbols.TextColor(0,r,::ColorToARGB(clr));
      m_table_symbols.SetValue(0,r,(symbols_total>1)?(r<1)? "BALANCE" : symbols[r-1]: symbols[r],0);
     }
//--- Actualizar la tabla
   m_table_symbols.Update(true);
   m_table_symbols.GetScrollHPointer().Update(true);
   m_table_symbols.GetScrollVPointer().Update(true);
  }

Sería muy conveniente si al seleccionar algún resultado en la tabla, se seleccionaría su curva en la tabla. Para eso escribiremos el método CProgram::SelectCurve(). Le pasamos el número del repaso para la búsqueda de la curva necesaria en el gráfico. Los nombres de las curvas corresponden a los números de los repasos a los que pertenecen. Por eso, podemos encontrarlos simplemente comparando ciclicamente el número pasado del repaso con el que figura en el nombre de la curva. Una vez encontrada la curva necesaria, guardamos su índice y detenemos el ciclo.

Ahora hay que mover la curva encontrada en la capa superior. Es que si marcamos esta curva simplemente cambiando su color, puede perderse entre las demás. Por eso, hay que alternar la curva encontrada y la última curva dibujada.

Para eso, usamos los índices para obtener los punteros de estas dos curvas. Luego, copiamos sus nombres y los arrays de datos. Después de eso, las alternamos. Establecemos el grosor y el color de la línea para la última curva. Vamos a hacerla negra para que se destaque mejor entre las demás. Para que los cambios tengan efecto, hay que reiniciar el gráfico.

//+------------------------------------------------------------------+
//| Evento de la finalización del proceso de la optimización         |
//+------------------------------------------------------------------+
void CProgram::SelectCurve(const ulong pass)
  {
   CGraphic *graph=m_graph5.GetGraphicPointer();
//--- Buscamos la curva según el número del repaso
   ulong curve_index =0;
   int curves_total  =graph.CurvesTotal();
   for(int i=0; i<curves_total; i++)
     {
      if(pass==(ulong)graph.CurveGetByIndex(i).Name())
        {
         curve_index=i;
         break;
        }
     }
//--- La última curva y la curva seleccionada en el gráfico
   CCurve *selected_curve =graph.CurveGetByIndex((int)curve_index);
   CCurve *last_curve     =graph.CurveGetByIndex((int)curves_total-1);
//--- Copiamos el array seleccionada y el último array de datos
   double y1[],y2[];
   string name1=selected_curve.Name();
   string name2=last_curve.Name();
   selected_curve.GetY(y1);
   last_curve.GetY(y2);
//---
   last_curve.Name(name1);
   selected_curve.Name(name2);
   last_curve.Update(y1);
   selected_curve.Update(y2);
//---
   last_curve.LinesWidth(2);
   last_curve.Color(clrBlack);
//--- Actualizar el gráfico
   graph.CurvePlotAll();
   graph.Update();
  }

Ahora, seleccionando una fila en la tabla, vamos a observar la curva del balance que le corresponde en el gráfico.

 Fig. 3 - Selección de las curvas en el gráfico.

Fig. 3. Selección de las curvas en el gráfico.


Procesamiento de eventos interactuando con la interfaz gráfica

Nos queda estudiar los métodos del procesamiento de eventos que se generan en caso de la interacción con la interfaz gráfica de nuestra aplicación. Se trata de los siguientes métodos:

  • Selección de una fila de la tabla con el clic izquierdo.
  • Selección de los resultados en la tabla usando el teclado.
  • Selección del criterio para elegir los resultados en la lista desplegable.

Cuando seleccionamos una fila haciendo clic en ellal, se genera el evento de usuario ON_CLICK_LIST_ITEM. Para su procesamiento se invoca el método CProgram::TableRowSelection(). Se le pasa el parámetro long del evento. Este parámetro es el identificador del elemento del que ha sido generado este evento. Si el identificador no pertenece al elemento, el programa saldrá del método y comprobará el siguiente elemento en el procesador de eventos de los elementos de la aplicación. Si el identificador coincide con el que tiene la tabla de los resultados, a continuación obtenemos el número del repaso desde la primera columna de la tabla. Por eso, para obtener el número del repaso, es necesario especificar los índices de la columna y la fila recién seleccionada pasando estos valores en el método CTable::GetValue().

Pues bien, hemos obtenido el número del repaso. Ahora, se puede obtener los datos desde este frame, y luego, los símbolos incluidos en este resultado. Los insertamos en la tabla en la primera pestaña del segundo grupo. Al final, seleccionamos la curva del balance en el gráfico de todos los resultados.

//+---------------------------------------------------------------------------+
//| Selección de la fila de la tabla pulsando el botón izquierdo del ratón    |
//+---------------------------------------------------------------------------+
bool CProgram::TableRowSelection(const long element_id)
  {
//--- Selección de la fila de la tabla
   if(element_id!=m_table_main.Id())
      return(false);
//--- Obtenemos el número del repaso desde la tabla 
   ulong pass=(ulong)m_table_main.GetValue(0,m_table_main.SelectedItem());
//--- Obtenemos los datos según el número del repaso
   m_frame_gen.GetFrameData(pass);
//--- Añadimos los símbolos a la tabla
   GetFrameSymbolsToTable();
//--- Seleccionar la curva en el gráfico según el número del repaso
   SelectCurve(pass);
   return(true);
  }

Cuando llega el evento de usuario ON_CLICK_LIST_ITEM también se procesa la acción de la selección del criterio para la selección de los resultados en la lista desplegable del combobox (CComboBox). Para eso, se usa el método CProgram::ShowResultsBySelectedCriteria(). Después de una verificación exitosa del identificador del control, obtenemos el índice del punto seleccionado en la lista desplegable. En esta versión de la aplicación, se ofrecen tres criterios en la lista desplegable:

  • Result — resultado de usuario devuelto por la función OnTester().
  • Profit —  beneficio neto del resultado de la prueba.
  • Recovery factor — factor de recuperación.  

Luego determinamos el índice de la columna con los datos pertenecientes al criterio seleccionado. El primer elemento corresponde a la columna con el índice 1, el segundo, a la columna con el índice 2, el tercero, a la columna con el índice 5. Luego, obtenemos los frames con los mejores resultados según el criterio seleccionado. Para eso hay que llamar al método CFrameGenerator::OnChangedSelectionCriteria() pasándole el índice de la columna. Ahora tenemos todo listo para obtener los balances de los mejores resultados en el gráfico. Este proceso se visualiza en la barra de ejecución. El último desafío aquí será la obtención de todos los datos en la tabla de los mejores resultados.

//+------------------------------------------------------------------+
//| Muestra los resultados según el criterio especificado            |
//+------------------------------------------------------------------+
bool CProgram::ShowResultsBySelectedCriteria(const long element_id)
  {
//--- Comprobación del identificador del control
   if(element_id!=m_criterion.Id())
      return(false);
//--- Determinamos el índice del criterio para obtener los mejores resultados
   int index=m_criterion.GetListViewPointer().SelectedItemIndex();
   int column_index=(index<1)? 1 : (index==1)? 2 : 5;
   m_frame_gen.OnChangedSelectionCriteria(column_index);
//--- Visualizamos el proceso de la obtención de los mejores resultados
   GetBestOptimizationResults();
//--- Obtenemos los datos en la tabla de los resultados de la optimización
   GetFrameDataToTable();
   return(true);
  }

En el manejador de eventos del programa, los métodos arriba analizados se invocan consecutivamente cuando llega el evento ON_CLICK_LIST_ITEM, hasta que devuelva true.

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Evento del clic en las filas de la tabla
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      //--- Selección de la fila de la tabla
      if(TableRowSelection(lparam))
         return;
      //--- Selección del criterio para seleccionar los resultados
      if(ShowResultsBySelectedCriteria(lparam))
         return;
      //---
      return;
     }
...
  }

Abajo Usted puede observar el proceso de la selección de la lista desplegable, obtención de datos y su dibujado en el gráfico:

 Fig. 4 - Selección de los resultados según el criterio especificado

Fig. 4 - Selección de los resultados según el criterio especificado.

Para seleccionar una fila a través del teclado, necesitaremos el método CProgram::SelectingResultsUsingKeys(). Tenemos que pasarle el código de la tecla pulsada. Viene en el parámetro long del evento CHARTEVENT_KEYDOWN. Al principio del método, obtenemos el índice de la fila actual seleccionada en la tabla. Luego, en el operador-conmutador switch, determinamos qué tecla fue pulsada. Aquí tiene un ejemplo del procesamiento de la pulsación de cuatro teclas:

  • KEY_UP — si pulsamos la tecla Up, disminuimos el índice actual de la fila seleccionada en 1. Se seleccionará la siguiente fila de arriba. 
  • KEY_DOWN — si pulsamos la tecla Down, aumentamos el índice actual de la fila seleccionada en 1. Se seleccionará la siguiente fila de abajo.
  • KEY_HOME — si pulsamos la tecla Home, determinamos el índice de la primera fila. 
  • KEY_END — si pulsamos la tecla End, determinamos el índice de la última fila.

A continuación, es necesario realizar las comprobaciones. El programa saldrá del método en las siguientes ocasiones:

  • si la fila no ha sido encontrada,
  • si está seleccionada la misma fila,
  • si hemos salido fuera de la lista.

Si las comprobaciones han sido superadas, la fila indicada se selecciona en la tabla, y en caso necesario, la barra de desplazamiento vertical se desplaza.

Una vez seleccionada la fila, ocurre lo siguiente.

  1. Obtenemos el número del repaso desde la primera columna de la tabla.
  2. Obtenemos los datos según el número del repaso.
  3. Insertamos los símbolos en la lista al lado del gráfico de multisímbolos.
  4. Seleccionamos la curva del balance en el gráfico de todos los resultados.

Código del método CProgram::SelectingResultsUsingKeys():

//+------------------------------------------------------------------+
//| Selección de los resultados usando las teclas                    |
//+------------------------------------------------------------------+
bool CProgram::SelectingResultsUsingKeys(const long key)
  {
//--- Obtenemos el índice de la fila seleccionada
   int selected_row=m_table_main.SelectedItem();
//--- Determinar la dirección y la fila para mover la barra de desplazamiento
   switch((int)key)
     {
      case KEY_UP :
         selected_row--;
         break;
      case KEY_DOWN :
         selected_row++;
         break;
      case KEY_HOME :
         selected_row=0;
         break;
      case KEY_END :
         selected_row=(int)m_table_main.RowsTotal()-1;
         break;
     }
//--- Salir si (1) la fila no está seleccionada o (2) tenemos seleccionada la misma fila que antes o (3) hemos salido fuera de la lista
   if(selected_row==WRONG_VALUE || selected_row==m_table_main.SelectedItem() || 
      selected_row<0 || selected_row>=(int)m_table_main.RowsTotal())
      return(false);
//--- Seleccionar la fila y mover la barra de desplazamiento
   m_table_main.SelectRow(selected_row);
   m_table_main.Update();
   m_table_main.GetScrollVPointer().Update(true);
//--- Obtenemos el número del repaso de la fila seleccionada de la tabla
   ulong pass=(ulong)m_table_main.GetValue(0,m_table_main.SelectedItem());
//--- Obtenemos los datos según el número del repaso
   m_frame_gen.GetFrameData(pass);
//--- Añadimos los símbolos a la tabla
   GetFrameSymbolsToTable();
//--- Seleccionar la curva en el gráfico según el número del repaso
   SelectCurve(pass);
   return(true);
  }

El método CProgram::SelectingResultsUsingKeys() se invoca cuando llega el evento de la pulsación del teclado (CHARTEVENT_KEYDOWN) en el manejador de eventos del programa:

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Pulsación en la tecla
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- Selección de los resultados usando las teclas
      if(SelectingResultsUsingKeys(lparam))
         return;
      //---
      return;
     }
...
  }

Así es como funciona eso:

 Fig. 5 – Selección de las filas de la tabla usando las teclas.

Fig. 5 – Selección de las filas de la tabla usando las teclas.

Conclusión

En el artículo ha sido demostrado un caso más cuando la biblioteca de las interfaces gráficas EasyAndFast será útil. Es evidente que el componente visual es muy importante para el análisis de los resultados de las pruebas. Este análisis profundo y amplio de los arrays de estos resultados de las pruebas puede llevar a nuevas ideas. Algunas de ellas ya han sido propuestas a otros participantes de la comunidad MQL.

Por ejemplo, en el arrays de los frames se puede almacenar los informes completos de las pruebas, y no sólo las indicaciones estadísticas y los datos de balances. Otra idea discutida en el foro consiste en usar el criterio de usuario en la selección de los resultados. Por ejemplo, se puede usar varias listas desplegables o los checkbox para formar un criterio de usuario, que va a calcularse de acuerdo con la fórmula especificada en los ajustes. Es difícil de imaginar cómo se podría implementar todo eso sin la interfaz gráfica.

Las ideas propuestas por Ustedes en los comentarios para el artículo pueden ser implementadas en una de las siguientes versiones. Por eso, no duden en proponer sus ideas de cómo se puede seguir desarrollando la aplicación para trabajar con los resultados de la optimización.

Más abajo Usted puede descargar los archivos adjuntos para el testeo y el análisis detallado del código presentado en el artículo.

Nombre del archivo Comentario
MacdSampleCFrames.mq5 EA modificado desde la entrega estándar MACD Sample
Program.mqh Archivo con la clase del programa
CreateGUI.mqh Archivo con la implementación de los métodos desde la clase del programa en el archivo Program.mqh
Strategy.mqh Archivo con la clase modificada de la estrategia MACD Sample (versión de multisímbolos)
FormatString.mqh Archivo con las funciones auxiliares para el formateo de las cadenas
FrameGenerator.mqh Clase para el trabajo con los resultados de la optimización

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/4636

Archivos adjuntos |
MQL5.zip (61.61 KB)
Aplicando el método de Monte Carlo para optimizar estrategias comerciales Aplicando el método de Monte Carlo para optimizar estrategias comerciales
Antes de iniciar un robot en la cuenta comercial, habitualmente lo probamos y optimizamos usando el historial de las cotizaciones. Pues, aquí surge una pregunta razonable, ¿cómo nos pueden ayudar los resultados anteriores en el historial en el futuro? En este artículo, se muestra la aplicación del método de Monte Carlo para construir sus propios criterios de optimización de las estrategias comerciales. Aparte de eso, se consideran los criterios de la estabilidad del Asesor Experto.
Random Decision Forest en el aprendizaje reforzado Random Decision Forest en el aprendizaje reforzado
Random Forest (RF) (en castellano, Bosques Aleatorios) con aplicación del bagging es uno de los métodos del aprendizaje automático más fuerte, que cede un poco ante el boosting del gradiente (Potenciación del gradiente). En este artículo, se realiza el intento de desarrollar un sistema comercial autoenseñable, que toma decisiones a base de la experiencia adquirida de la interacción con el mercado.
Construimos el indicador Zigzag usando osciladores. Ejemplo de ejecución de la tarea técnica Construimos el indicador Zigzag usando osciladores. Ejemplo de ejecución de la tarea técnica
En este artículo, se demuestra el desarrollo del indicador ZigZag de acuerdo con uno de los ejemplos de la tareas descrito en el artículo «Cómo crear una Tarea Técnica al encargar un indicador». El indicador se construye por los extremos que se definen a través del oscilador. En el indicador está prevista la posibilidad de usar uno de cinco osciladores a elegir: WPR, CCI, Chaikin, RSI, Stochastic Oscillator.
Trabajando con los resultados de la optimización mediante la interfaz gráfica Trabajando con los resultados de la optimización mediante la interfaz gráfica
Continuamos desarrollar el tema del procesamiento y el análisis de los resultados de la optimización. Ahora nuestra tarea consiste en seleccionar 100 mejores resultados de la optimización y mostrarlos en la tabla de la interfaz gráfica. Hagamos que el usuario obtenga el gráfico del balance de multisímbolos y de la reducción (drawdown) en gráficos separados seleccionando una fila de la tabla de los resultados de la optimización.