Experto comercial con interfaz gráfica: Llenando la funcionalidad (Parte II)

20 septiembre 2018, 08:25
Anatoli Kazharski
0
2 159

Contenido

Introducción

En el artículo anterior mostramos cómo crear rápidamente la interfaz gráfica de un experto. En esta parte, vamos a trabajar con este material para conectar la interfaz gráfica con la funcionalidad del asesor. 

Obteniendo los datos de los símbolos e indicadores

Para comenzar, tenemos que posibilitar la obtención de datos de los símbolos e indicadores. Reunimos en un recuadro los símbolos fórex, apoyándonos en los valores del filtro en el campo de edición Symbols filter. Esto lo hace posible el método CProgram::GetSymbols().

Al comienzo del método, marcamos en el indicador de progreso que ahora está en marcha el proceso de obtención de símbolos. Inicialmente, no se sabe cuántos símbolos habrá. Por eso establecemos la franja del indicador en un 50%. A continuación, liberamos la matriz de símbolos. Al trabajar con la aplicación, podríamos necesitar formar otra lista de símbolos, por eso debemos hacer esto cada vez al llamar el método CProgram::GetSymbols().

El filtro en el campo de edición Symbols filter se usará solo si su casilla de verificación está activada y en el campo de edición hay algunos rótulos introducidos a través de una coma. Si se respetan estas condiciones, obtendremos estos rótulos en la matriz como elementos aparte, de manera que después podamos usarlos para buscar los símbolos necesarios. En cualquier caso, limpiamos de símbolos especiales los bordes de cada elemento.

La siguiente etapa es el ciclo de recopilación de símbolos fórex. Tendrá lugar de acuerdo con la lista completa de todos los símbolos existentes en el servidor. Al inicio de cada iteración, obtenemos el nombre del símbolo y lo quitamos de la ventana «Observación del mercado». De esta forma, las listas en la interfaz gráfica del programa y en esta ventana coincidirán. A continuación, comprobamos si el símbolo obtenido pertenece a la categoría de símbolos fórex. Si para trabajar necesitamos todos los símbolos, simplemente comentaremos esta condición o la eliminaremos. Dentro de este artículo vamos a trabajar solo con símbolos fórex.

Si el filtro de nombres está activado, comprobamos en el ciclo si hay coincidencias entre el nombre obtenido en esta iteración y el rótulo del campo de edición Symbols filter. Si no hay coincidencias, añadimos el símbolo a la matriz.

Si no se ha encontrado ningún símbolo, a la matriz se añadirá solo el símbolo actual del gráfico principal. Después de esto, todos los símbolos añadidos a la matriz se harán visibles en la ventana «Observación del mercado». 

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                   |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Símbolos para el comercio
   string            m_symbols[];
   //---
private:
   //--- Obtiene los símbolos
   void              GetSymbols(void);
  };
//+------------------------------------------------------------------+
//| Obtiene los símbolos                                             |
//+------------------------------------------------------------------+
void CProgram::GetSymbols(void)
  {
   m_progress_bar.LabelText("Get symbols...");
   m_progress_bar.Update(1,2);
   ::Sleep(5);
//--- Liberar la matriz de símbolos
   ::ArrayFree(m_symbols);
//--- Matriz de elementos de las líneas
   string elements[];
//--- Filtrando los nombres de los símbolos
   if(m_symb_filter.IsPressed())
     {
      string text=m_symb_filter.GetValue();
      if(text!="")
        {
         ushort sep=::StringGetCharacter(",",0);
         ::StringSplit(text,sep,elements);
         //---
         int elements_total=::ArraySize(elements);
         for(int e=0; e<elements_total; e++)
           {
            //--- Limpiando los extremos
            ::StringTrimLeft(elements[e]);
            ::StringTrimRight(elements[e]);
           }
        }
     }
//--- Reunimos la matriz de símbolos fórex
   int symbols_total=::SymbolsTotal(false);
   for(int i=0; i<symbols_total; i++)
     {
      //--- Obtenemos el nombre del símbolo
      string symbol_name=::SymbolName(i,false);
      //--- Lo ocultamos en la ventana ‘Observación del mercado’
      ::SymbolSelect(symbol_name,false);
      //--- Si no se trata de un símbolo fórex, pasamos al siguiente
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX)
         continue;
      //--- Filtrando los nombres de los símbolos
      if(m_symb_filter.IsPressed())
        {
         bool check=false;
         int elements_total=::ArraySize(elements);
         for(int e=0; e<elements_total; e++)
           {
            //--- Buscamos una coincidencia en el nombre del símbolo
            if(::StringFind(symbol_name,elements[e])>-1)
              {
               check=true;
               break;
              }
           }
         //--- Si el filtro no deja pasar, saltamos al siguiente
         if(!check)
            continue;
        }
      //--- Guardamos el símbolo en la matriz
      int array_size=::ArraySize(m_symbols);
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=symbol_name;
     }
//--- Si la matriz está vacía, establecemos el símbolo actual por defecto
   int array_size=::ArraySize(m_symbols);
   if(array_size<1)
     {
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=::Symbol();
     }
//--- Mostramos en la ventana de Observación del mercado
   int selected_symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<selected_symbols_total; i++)
      ::SymbolSelect(m_symbols[i],true);
  }

Ahora analizamos la obtención de los manejadores de los indicadores de todos los símbolos elegidos con la ayuda del método CProgram::GetHandles(). Primero le asignamos a la matriz de manejadores el mismo tamaño que la matriz de símbolos. Obtendremos los manejadores con el mismo marco temporal que se indica en el cuadro combinado Timeframes. Puesto que del cuadro combinado se puede obtener un valor de línea, a continuación deberemos convertirlo al tipo correspondiente (ENUM_TIMEFRAMES). En el ciclo, rellenamos la matriz de manejadores. En este caso, se trata del indicador Stochastic con los valores por defecto. En cada iteración actualizamos el indicador de progreso. Al final del método, recordamos el primer índice del manejador del gráfico que se represente.

class CProgram : public CWndEvents
  {
private:
   //--- Manejadores del indicador
   int               m_handles[];
   //--- Índice del manejador del gráfico actual
   int               m_current_handle_index;
   //---
private:
   //--- Obtiene los manejadores
   void              GetHandles(void);
  };
//+------------------------------------------------------------------+
//| Obtiene los manejadores del indicador para todos los símbolos    |
//+------------------------------------------------------------------+
void CProgram::GetHandles(void)
  {
//--- Establecer el tamaño para la matriz de los manejadores
   int symbols_total=::ArraySize(m_symbols);
   ::ArrayResize(m_handles,symbols_total);
//--- Obtenemos el valor de la lista desplegable del cuadro combinado
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
//--- Iteramos por la lista de símbolos
   for(int i=0; i<symbols_total; i++)
     {
      //--- Obtenemos el manejador del indicador
      m_handles[i]=::iStochastic(m_symbols[i],StringToTimeframe(tf),5,3,3,MODE_SMA,STO_LOWHIGH);
      //--- Barra de progreso
      m_progress_bar.LabelText("Get handles: "+string(symbols_total)+"/"+string(i)+" ["+m_symbols[i]+"] "+((m_handles[i]!=WRONG_VALUE)? "ok" : "wrong")+"...");
      m_progress_bar.Update(i,symbols_total);
      ::Sleep(5);
     }
//--- Recordar el primer índice del manejador del gráfico
   m_current_handle_index=0;
  }

Obtenemos los valores de los indicadores con la ayuda del método CProgram::GetIndicatorValues(). Describimos su algoritmo. Primero, establecemos un tamaño de la matriz para los valores del indicador igual al tamaño de la matriz de los manejadores. En el ciclo principal iteramos por la matriz de manejadores y en cada iteración hacemos cinco intentos de obtener los datos del indicador. En cualquier caso, vamos a comprobar la validez del manejador, si no lo hemos obtenido anteriormente, intentamos obtenerlo ahora. Al final del ciclo principal, actualizamos el indicador de progreso, para ver en qué etapa se encuentra ahora el programa.

class CProgram : public CWndEvents
  {
private:
   //--- Valores del indicador
   double            m_values[];
   //---
private:
   //--- Obtiene los valores de los indicadores en todos los símbolos
   void              GetIndicatorValues(void);
  };
//+------------------------------------------------------------------+
//| Obtiene los valores de los indicadores en todos los símbolos     |
//+------------------------------------------------------------------+
void CProgram::GetIndicatorValues(void)
  {
//--- Establecemos el tamaño
   int handles_total=::ArraySize(m_handles);
   ::ArrayResize(m_values,handles_total);
//--- Obtenemos el valor de la lista desplegable del cuadro combinado
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
//--- Obtenemos los datos de los indicadores para todos los símbolos en la lista
   for(int i=0; i<handles_total; i++)
     {
      //--- Hacemos 5 intentos de obtener los datos
      int attempts=0;
      int received=0;
      while(attempts<5)
        {
         //--- Si el manejador no es válido, intentamos obtenerlo otra vez
         if(m_handles[i]==WRONG_VALUE)
           {
            //--- Obtenemos el manejador del indicador
            m_handles[i]=::iStochastic(m_symbols[i],StringToTimeframe(tf),5,3,3,MODE_SMA,STO_LOWHIGH);
            continue;
           }
         //--- Intentamos obtener los valores del indicador
         double values[1];
         received=::CopyBuffer(m_handles[i],1,0,1,values);
         if(received>0)
           {
            //--- Guardamos el valor
            m_values[i]=values[0];
            break;
           }
         //--- Aumentamos el contador
         attempts++;
         ::Sleep(100);
        }
      //--- Barra de progreso
      m_progress_bar.LabelText("Get values: "+string(handles_total)+"/"+string(i)+" ["+m_symbols[i]+"] "+((received>0)? "ok" : "wrong")+"...");
      m_progress_bar.Update(i,handles_total);
      ::Sleep(5);
     }
  }

Después de formar la lista de símbolos y obtener los datos de los indicadores, debemos añadir los valores de estas matrices al recuadro en la pestaña Trade. CProgram::RebuildingTables() se encarga de resolver esta tarea. El número de símbolos puede cambiar. Por eso, con cada llamada de este método, el recuadro se reconstuirá por completo.

Primero se eliminan de él todas las líneas excepto una, la de reserva. A continuación, se añaden de nuevo al recuadro las líneas según el número de símbolos. Después, iteramos por él en el ciclo y añadimos los valores obtenidos anteriormente a matrices aparte. Aparte de los propios valores, tenemos que destacar con color el texto, para ver qué señales se han formado ya según los valores del indicador. Los valores por debajo del mínimo del indicador Stochastic los destacaremos en azul como señales de compra, y los valores por encima del máximo, los destacaremos en color rojo como señales de venta. En cada iteración, a medida que el programa va trabajando, se actualizará el indicador de progreso. Al final del método, tenemos que actualizar el recuadro y las barras de desplazamiento.

//+------------------------------------------------------------------+
//| Reconstruye el recuadro de símbolos                              |
//+------------------------------------------------------------------+
void CProgram::RebuildingTables(void)
  {
//--- Eliminar todas las líneas
   m_table_symb.DeleteAllRows();
//--- Establecemos el número de líneas según el número de símbolos
   int symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<symbols_total-1; i++)
      m_table_symb.AddRow(i);
//--- Establecemos los valores en la primera columna
   uint rows_total=m_table_symb.RowsTotal();
   for(uint r=0; r<(uint)rows_total; r++)
     {
      //--- Establecemos los valores
      m_table_symb.SetValue(0,r,m_symbols[r]);
      m_table_symb.SetValue(1,r,::DoubleToString(m_values[r],2));
      //--- Establecemos los colores
      color clr=(m_values[r]>(double)m_up_level.GetValue())? clrRed :(m_values[r]<(double)m_down_level.GetValue())? C'85,170,255' : clrBlack;
      m_table_symb.TextColor(0,r,clr);
      m_table_symb.TextColor(1,r,clr);
      //--- Actualizar el indicador de progreso
      m_progress_bar.LabelText("Initialize tables: "+string(rows_total)+"/"+string(r)+"...");
      m_progress_bar.Update(r,rows_total);
      ::Sleep(5);
     }
//--- Actualizar el recuadro
   m_table_symb.Update(true);
   m_table_symb.GetScrollVPointer().Update(true);
   m_table_symb.GetScrollHPointer().Update(true);
  }

Todos los métodos descritos anteriormente se llaman en el método CProgram::RequestData(). En este se transmite un único argumento, en función de cuyo valor comprobamos el indicador del elemento de control: el botón Request. Después de realizar esta comprobación, ocultamos el recuadro y hacemos visible el indicador de progreso. A continuación, se llaman de forma consecutiva todos los métodos enumerados anteriormente, para obtener los datos e introducirlos en el recuadro. A continuación, ocultamos el indicador de progreso, establecemos el marco temporal desde el cuadro combinado en el gráfico y hacemos visibles los últimos cambios. 

//+------------------------------------------------------------------+
//| Solicitando los datos                                            |
//+------------------------------------------------------------------+
bool CProgram::RequestData(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_request.Id())
      return(false);
//--- Ocultar recuadro
   m_table_symb.Hide();
//--- Mostrar progreso
   m_progress_bar.Show();
   m_chart.Redraw();
//--- Inicializar el gráfico y los recuadros
   GetSymbols();
   GetHandles();
   GetIndicatorValues();
   RebuildingTables();
//--- Ocultar progreso
   m_progress_bar.Hide();
//--- Obtenemos el valor desde la lista desplegable del cuadro combinado
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
//--- Obtenemos el puntero del gráfico según el índice
   m_sub_chart1.GetSubChartPointer(0).Period(StringToTimeframe(tf));
   m_sub_chart1.ResetCharts();
//--- Mostrar recuadro
   m_table_symb.Show();
   m_chart.Redraw();
   return(true);
  }

Obteniendo los datos de las posiciones abiertas

Cuando el experto ha sido cargado en el gráfico, hay que determinar de inmediato si hay posiciones abiertas, para representar esta información en el recuadro en la pestaña Positions. Es posible ver la lista con todas las posiciones en la ventana «Comercio». Para cerrar solo una posición del símbolo, hay que pulsar sobre la cruz en la celda del recuadro en la columna Profit. Si hay varias posiciones por símbolo (en la cuenta de cobertura) y debemos cerrarlas todas, necesitaremos varios pasos. En el recuadro de posiciones de la interfaz gráfica, hacemos que en la línea para cada símbolo se encuentre la información de conjunto del resultado actual, la carga del depósito y el precio medio. Además, añadimos la posibilidad de cerrar con una pulsación todas las posiciones de golpe en el símbolo indicado. 

En primer lugar, analizamos el método CProgram::GetPositionsSymbols(), para obtener la lista de símbolos de las posiciones abiertas. Al mismo se le añade una matriz dinámica vacía, en la que se obtendrán los símbolos. A continuación, iteramos en el ciclo por todas las posiciones abiertas. En cada iteración obtenemos el nombre del símbolos de la posición y lo añadimos a una variable de línea a través del separador «,». Antes de añadir el nombre del símbolo, comprobamos si está ya en esta línea

Después de finalizar el ciclo y formar las líneas de los símbolos, obtenemos en la matriz transmitida los elementos de esta línea y retornamos el número de símbolos obtenidos.

//+------------------------------------------------------------------+
//| Obtiene en la matriz los símbolos de las posiciones abiertas     |
//+------------------------------------------------------------------+
int CProgram::GetPositionsSymbols(string &symbols_name[])
  {
   string symbols="";
//--- Vamos a iterar por primera vez en el ciclo y obtenemos los símbolos de las posiciones abiertas
   int positions_total=::PositionsTotal();
   for(int i=0; i<positions_total; i++)
     {
      //--- Elegimos la posición y obtenemos su símbolo
      string position_symbol=::PositionGetSymbol(i);
      //--- Si existe el nombre del símbolo
      if(position_symbol=="")
         continue;
      //--- Si no existe esta línea todavía, la añadimos
      if(::StringFind(symbols,position_symbol,0)==WRONG_VALUE)
         ::StringAdd(symbols,(symbols=="")? position_symbol : ","+position_symbol);
     }
//--- Obtenemos los elementos de la línea según el separador
   ushort u_sep=::StringGetCharacter(",",0);
   int symbols_total=::StringSplit(symbols,u_sep,symbols_name);
//--- Retornamos el número de símbolos
   return(symbols_total);
  }

Ahora que tenemos la matriz de símbolos, podemos obtener los datos de cada posición conjunta, indicando simplemente el nombre del símbolo. Analizamos los métodos para obtener los índices en todas las columnas de datos en el recuadro de posiciones.

Para obtener el número de posiciones del símbolo indicado, usamos el método CProgram::PositionsTotal(). Este itera en el ciclo por todas las posiciones y considera solo aquellas que coinciden con el símbolo indicado en el argumento del método.

//+----------------------------------------------------------------------+
//| Número de transacciones de la posición con las propiedades indicadas |
//+----------------------------------------------------------------------+
int CProgram::PositionsTotal(const string symbol)
  {
//--- Contador de posiciones
   int pos_counter=0;
//--- Comprobamos si hay una posición con las propiedades indicadas
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      //--- Si no se ha logrado elegir una posición, pasamos a la siguiente
      if(symbol!=::PositionGetSymbol(i))
         continue;
      //--- Aumentar el contador
      pos_counter++;
     }
//--- Retornamos el número de posiciones
   return(pos_counter);
  }

Podemos obtener el volumen de las posiciones con ayuda del método CProgram::PositionsVolumeTotal(). Aparte del símbolo según el cual podemos obtener el volumen total de las posiciones, al método también se le puede transmitir su tipo. Pero el tipo de posiciones no es un argumento obligatorio en este método. Por defecto, se indica el valor WRONG_VALUE. Si el tipo no se indica, esta comprobación no será utilizada, y el método retornará el volumen total de todas las posiciones. 

//+------------------------------------------------------------------+
//| Volumen total de las posiciones con las propiedades indicadas    |
//+------------------------------------------------------------------+
double CProgram::PositionsVolumeTotal(const string symbol,const ENUM_POSITION_TYPE type=WRONG_VALUE)
  {
//--- Contador del volumen
   double volume_counter=0;
//--- Comprobamos si hay una posición con las propiedades indicadas
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      //--- Si no se ha logrado elegir una posición, pasamos a la siguiente
      if(symbol!=::PositionGetSymbol(i))
         continue;
      //--- Si hay que comprobar el tipo
      if(type!=WRONG_VALUE)
        {
         //--- Si el tipo no coincide, pasamos a la siguiente posición
         if(type!=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE))
            continue;
        }
      //--- Sumamos el volumen
      volume_counter+=::PositionGetDouble(POSITION_VOLUME);
     }
//--- Retornamos el volumen
   return(volume_counter);
  }

El método CProgram::PositionsFloatingProfitTotal() permite obtener el beneficio flotante total de las posiciones del símbolo indicado. Al realizar el cálculo, se tiene en cuenta el swap acumulado de las posiciones. Aquí podemos indicar también como argumento adicional opcional el tipo de posición según el cual necesitamos obtener el beneficio flotante. De esta forma, el método se convierte en universal. 

//+--------------------------------------------------------------------------+
//| Beneficio flotante total de las posiciones con las propiedades indicadas |
//+--------------------------------------------------------------------------+
double CProgram::PositionsFloatingProfitTotal(const string symbol,const ENUM_POSITION_TYPE type=WRONG_VALUE)
  {
//--- Contador del beneficio actual
   double profit_counter=0.0;
//--- Comprobamos si hay una posición con las propiedades indicadas
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      //--- Si no se ha logrado elegir una posición, pasamos a la siguiente
      if(symbol!=::PositionGetSymbol(i))
         continue;
      //--- Si hay que comprobar el tipo
      if(type!=WRONG_VALUE)
        {
         //--- Si el tipo no coincide, pasamos a la siguiente posición
         if(type!=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE))
            continue;
        }
      //--- Sumamos el beneficio actual + el swap acumulado
      profit_counter+=::PositionGetDouble(POSITION_PROFIT)+::PositionGetDouble(POSITION_SWAP);
     }
//--- Retornar el resultado
   return(profit_counter);
  }

Calculamos el precio medio con el método CProgram::PositionAveragePrice(). En el ciclo obtenemos el precio y el volumen de cada posición del símbolo. A continuación, sumamos el producto de estos índices, y aparte el volumen de las posiciones. Después de finalizar el ciclo, para obtener el precio medio de las posiciones del símbolo indicado debemos dividir la suma del producto de los precios y volúmenes entre la suma de los volúmenes. El método presantado retorna precisamente este valor.

//+------------------------------------------------------------------+
//| Precio medio de la posición                                      |
//+------------------------------------------------------------------+
double CProgram::PositionAveragePrice(const string symbol)
  {
//--- Para calcular el precio medio
   double sum_mult    =0.0;
   double sum_volumes =0.0;
//--- Comprobamos si hay una posición con las propiedades indicadas
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      //--- Si no se ha logrado elegir una posición, pasamos a la siguiente
      if(symbol!=::PositionGetSymbol(i))
         continue;
      //--- Obtenemos el precio y el volumen de la posición
      double pos_price  =::PositionGetDouble(POSITION_PRICE_OPEN);
      double pos_volume =::PositionGetDouble(POSITION_VOLUME);
      //--- Sumamos los índices intermedios
      sum_mult+=(pos_price*pos_volume);
      sum_volumes+=pos_volume;
     }
//--- Prevenir la división por cero
   if(sum_volumes<=0)
      return(0.0);
//--- Retornar el precio medio
   return(::NormalizeDouble(sum_mult/sum_volumes,(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  }

Analizamos el siguiente índice: la carga del depósito. Para obtenerlo, debemos llamar el método universal CProgram::DepositLoad(). Dependiendo de los argumentos transmitidos, podemos obtener los valores en diferentes presentaciones: en la divisa del depósito o en tanto por ciento. Además, podemos obtener la carga total del depósito de todas las posiciones abiertas, o bien solo del símbolo indicado. 

El método tiene cuatro argumentos, tres de los cuales son opcionales. Si el primer argumento tiene el valor false, el método retornará el valor en la divisa del depósito. Si se ha transmitido el valor true, se retornará el valor en tanto por ciento con respecto a los fondos libres

Si debemos obtener la carga del depósito actual del símbolo indicado, entonces para aquellos casos en los que la divisa de la cuenta sea distinta a la divisa básica del símbolo, en el cálculo necesitaremos el precio de la posición. Si hay varias posiciones abiertas en el símbolo, entonces deberemos transmitir el precio medio. 

//+------------------------------------------------------------------+
//| Carga del depósito                                               |
//+------------------------------------------------------------------+
double CProgram::DepositLoad(const bool percent_mode,const double price=0.0,const string symbol="",const double volume=0.0)
  {
//--- Calculamos el valor actual de la carga del depósito
   double margin=0.0;
//--- Carga total de la cuenta
   if(symbol=="" || volume==0.0)
      margin=::AccountInfoDouble(ACCOUNT_MARGIN);
//--- Carga del símbolo indicado
   else
     {
      //--- Obtenemos los datos para el cálculo del margen
      double leverage         =((double)::AccountInfoInteger(ACCOUNT_LEVERAGE)==0)? 1 : (double)::AccountInfoInteger(ACCOUNT_LEVERAGE);
      double contract_size    =::SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);
      string account_currency =::AccountInfoString(ACCOUNT_CURRENCY);
      string base_currency    =::SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);
      //--- Si la divisa de la cuenta comercial es la misma que la divisa básica del símbolo
      if(account_currency==base_currency)
         margin=(volume*contract_size)/leverage;
      else
         margin=(volume*contract_size)/leverage*price;
     }
//--- Obtenemos los fondos actuales
   double equity=(::AccountInfoDouble(ACCOUNT_EQUITY)==0)? 1 : ::AccountInfoDouble(ACCOUNT_EQUITY);
//--- Retornar la carga actual del depósito
   return((!percent_mode)? margin : (margin/equity)*100);
  }

Todos los métodos para obtener los indicadores se añaden al recuadro mediante la llamada del método CProgram::SetValuesToPositionsTable(). Al método se debe añadir la matriz de los símbolos que nos interesan. Primero comprobamos que la matriz elegida no sea menor que el número de líneas en el recuadro. A continuación, iteramos en el ciclo por todas las líneas del recuadro, obteniendo de forma consecutiva los indicadores y rellenando con ellos las celdas del recuadro. Además de los propios valores, indicaremos el color del texto: el color verde para los resultados positivos, el rojo para los resultados negativos, y el gris para el cero. Preste atención a que la carga del depósito se representará para cada símbolo separada con una barra oblicua («/») en tanto por ciento y dinero

//+------------------------------------------------------------------+
//| Establecer los valores en el recuadro de posiciones              |
//+------------------------------------------------------------------+
void CProgram::SetValuesToPositionsTable(string &symbols_name[])
  {
//--- Comprobando si está fuera del intervalo
   uint symbols_total =::ArraySize(symbols_name);
   uint rows_total    =m_table_positions.RowsTotal();
   if(symbols_total<rows_total)
      return;
//--- Obtenemos los índices en el recuadro
   for(uint r=0; r<rows_total; r++)
     {
      int    positions_total =PositionsTotal(symbols_name[r]);
      double pos_volume      =PositionsVolumeTotal(symbols_name[r]);
      double buy_volume      =PositionsVolumeTotal(symbols_name[r],POSITION_TYPE_BUY);
      double sell_volume     =PositionsVolumeTotal(symbols_name[r],POSITION_TYPE_SELL);
      double pos_profit      =PositionsFloatingProfitTotal(symbols_name[r]);
      double buy_profit      =PositionsFloatingProfitTotal(symbols_name[r],POSITION_TYPE_BUY);
      double sell_profit     =PositionsFloatingProfitTotal(symbols_name[r],POSITION_TYPE_SELL);
      double average_price   =PositionAveragePrice(symbols_name[r]);
      string deposit_load    =::DoubleToString(DepositLoad(false,average_price,symbols_name[r],pos_volume),2)+"/"+
                              ::DoubleToString(DepositLoad(true,average_price,symbols_name[r],pos_volume),2)+"%";
      //--- Establecemos los valores
      m_table_positions.SetValue(0,r,symbols_name[r]);
      m_table_positions.SetValue(1,r,(string)positions_total);
      m_table_positions.SetValue(2,r,::DoubleToString(pos_volume,2));
      m_table_positions.SetValue(3,r,::DoubleToString(buy_volume,2));
      m_table_positions.SetValue(4,r,::DoubleToString(sell_volume,2));
      m_table_positions.SetValue(5,r,::DoubleToString(pos_profit,2));
      m_table_positions.SetValue(6,r,::DoubleToString(buy_profit,2));
      m_table_positions.SetValue(7,r,::DoubleToString(sell_profit,2));
      m_table_positions.SetValue(8,r,deposit_load);
      m_table_positions.SetValue(9,r,::DoubleToString(average_price,(int)::SymbolInfoInteger(symbols_name[r],SYMBOL_DIGITS)));
      //--- Establecemos el color
      m_table_positions.TextColor(3,r,(buy_volume>0)? clrBlack : clrLightGray);
      m_table_positions.TextColor(4,r,(sell_volume>0)? clrBlack : clrLightGray);
      m_table_positions.TextColor(5,r,(pos_profit!=0)? (pos_profit>0)? clrGreen : clrRed : clrLightGray);
      m_table_positions.TextColor(6,r,(buy_profit!=0)? (buy_profit>0)? clrGreen : clrRed : clrLightGray);
      m_table_positions.TextColor(7,r,(sell_profit!=0)?(sell_profit>0)? clrGreen : clrRed : clrLightGray);
     }
  }

Para actualizar el recuadro de posiciones, después de los cambios introducidos haremos un método aparte, puesto que tendremos que llamarlo en varios lugares del programa.

//+------------------------------------------------------------------+
//| Actualiza el recuadro de las posiciones                          |
//+------------------------------------------------------------------+
void CProgram::UpdatePositionsTable(void)
  {
//--- Actualizar el recuadro
   m_table_positions.Update(true);
   m_table_positions.GetScrollVPointer().Update(true);
   m_table_positions.GetScrollHPointer().Update(true);
  }

Inicializamos el recuadro de posiciones en el método CProgram::InitializePositionsTable(). En él se llaman todos los métodos analizados en este apartado. Primero obtenemos en la matriz los símbolos de las posiciones abiertas. A continuación, debemos preparar el recuadro, por eso eliminamos todas las líneas y añadimos los nuevas según el número de símbolos obtenido en la matriz. Si hay posiciones abiertas, debemos nombrar las celdas de la primera columna como botones. Para ello, necesitamos establecer el tipo correspondiente (CELL_BUTTON) y añadir una imagen. Después de ello, establecemos los valores en las celdas y actualizamos el recuadro.

//+------------------------------------------------------------------+
//| Inicializando el recuadro de posiciones                          |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Controls\\close_black.bmp"
//---
void CProgram::InitializePositionsTable(void)
  {
//--- Obtenemos los símbolos de las posiciones abiertas
   string symbols_name[];
   int symbols_total=GetPositionsSymbols(symbols_name);
//--- Eliminar todas las líneas
   m_table_positions.DeleteAllRows();
//--- Establecemos el número de líneas según el número de símbolos
   for(int i=0; i<symbols_total-1; i++)
      m_table_positions.AddRow(i);
//--- Si hay posiciones
   if(symbols_total>0)
     {
      //--- Matriz de imágenes para los botnoes
      string button_images[1]={"Images\\EasyAndFastGUI\\Controls\\close_black.bmp"};
      //--- Establecemos los valores en la tercera columna
      for(uint r=0; r<(uint)symbols_total; r++)
        {
         //--- Establecemos el tipo y las imágenes
         m_table_positions.CellType(0,r,CELL_BUTTON);
         m_table_positions.SetImages(0,r,button_images);
        }
      //--- Establecemos los valores en el recuadro
      SetValuesToPositionsTable(symbols_name);
     }
//--- Actualizar el recuadro
   UpdatePositionsTable();
  }

Inicializando recuadros con datos

Debemos inicializar el recuadro de los símbolos y el recuadro de posiciones justo después de crear la interfaz gráfica del programa. Podemos saber que ha finalizado su formación según el evento personalizado ON_END_CREATE_GUI en el manejador de eventos. Para inicializar el recuadro de símbolos, debemos llamar el método CProgram::RequestData(), que ya hemos analizado anteriormente. Para que el método funcione con éxito, debemos transmitir a este el identificador del elemento del botón Request.

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Evento de creación de GUI
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
     {
      //--- Solicitando datos
      RequestData(m_request.Id());
      //--- Inicializando el recuadro de posiciones
      InitializePositionsTable();
      return;
     }
  }

Al final, después de cargar el programa en el gráfico, el recuadro de los símbolos tendrá el aspecto siguiente:

 Fig. 3 – Recuadro de símbolos inicializado.

Fig. 1. 3 – Recuadro de símbolos inicializado.

Si antes de cargar el programa ya había posiciones abiertas en la cuenta, el recuadro de posiciones tendrá el aspecto siguiente:

 Fig. 4 – Recuadro de posiciones inicializado.

Fig. 2. Recuadro de posiciones inicializado.

Actualizando los recuadros en tiempo real

El precio se mueve todo el tiempo, por eso, durante la sesión comercial, los datos en los recuadros deben recalcularse constantemente. Actualizaremos el recuadro después de intervalos determinados en el temporizador del programa. Para posibilitar la actualización de elementos con diferentes intervalos, podemos usar objetos del tipo CTimeCounter. Esta clase se encuentra en la librería EasyAndFast. Para usarla en el proyecto, basta con incluir el archivo con su contenido:

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
#include <EasyAndFastGUI\TimeCounter.mqh>
...

En nuestro experto vamos a necesitar tres contadores de tiempo para actualizar los datos en la barra de estado y en los recuadros. 

  • En la barra de estado se actualizarán dos puntos según el temporizador: el segundo y el tercero. En el segundo se representa la carga total del depósito para toda la cuenta, y en el tercero, la hora actual del servidor comercial. Establecemos el intervalo temporal para este contador en 500 ms.
  • En el recuadro de símbolos actualizaremos los valores actuales de los indicadores de todos los símbolos. Puesto que puede haber muchos símbolos, no hay que hacer esto con mucha frecuencia, por lo tanto, vamos a establecer un intervalo de 5000 ms.
  • En el recuadro de posiciones hay indicadores que también dependen de los precios actuales. Por eso, aquí también debemos actualizar de forma periódica, para tener datos actuales. Para este contador, estableceremos un intervalo de 1000 ms.

Para ajustar los contadores, solo tenemos que declarar los objetos del tipo CTimeCounter y establecer sus parámetros en el constructor (ver la lista de abajo). El primer parámetro es la frecuencia del temporizador, y el segundo, el intervalo temporal después del cual el método CTimeCounter::CheckTimeCounter() retornará el valor true. A continuación, el contador se pondrá a cero y comenzará a acumularse de nuevo.

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                   |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
...
   //--- Contadores de tiempo
   CTimeCounter      m_counter1;
   CTimeCounter      m_counter2;
   CTimeCounter      m_counter3;
...
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
  {
//--- Estableciendo los parámetros para los contadores de tiempo
   m_counter1.SetParameters(16,500);
   m_counter2.SetParameters(16,5000);
   m_counter3.SetParameters(16,1000);
...
  }

Para actualizar la barra de estado, en el bloque del primer contador en el temporizador del programa debemos añadir el código mostrado más abajo. Para que se muestren los cambios introducidos, no olvide actualizar cada punto por separado.

//+------------------------------------------------------------------+
//| Temporizador                                                     |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
...
//--- Actualizar los puntos en la barra de estado
   if(m_counter1.CheckTimeCounter())
     {
      //--- Establecer los valores
      m_status_bar.SetValue(1,"Deposit load: "+::DoubleToString(DepositLoad(false),2)+"/"+::DoubleToString(DepositLoad(true),2)+"%");
      m_status_bar.SetValue(2,::TimeToString(::TimeTradeServer(),TIME_DATE|TIME_SECONDS));
      //--- Actualizar los puntos
      m_status_bar.GetItemPointer(1).Update(true);
      m_status_bar.GetItemPointer(2).Update(true);
     }
...
  }

Para acelerar la actualización del recuadro donde debemos sustituir solo los valores de los indicadores, usaremos un método aparte, CProgram::UpdateSymbolsTable(). Antes de llamarlo, debemos actualizar primero la matriz de valores de los indicadores. A continuación, llamamos el método CProgram::UpdateSymbolsTable(). Aquí, en cada iteración se comprueba si la matriz está fuera del intervalo. Si se ha superado la comprobación, acutalizamos las celdas de la segunda columna del recuadro y corregimos el color del texto. El proceso de obtención de datos e inicialización de recuadros se muestra en el indicador de progreso.

//+------------------------------------------------------------------+
//| Actualiza el recuadro de símbolos                                |
//+------------------------------------------------------------------+
void CProgram::UpdateSymbolsTable(void)
  {
   uint values_total=::ArraySize(m_values);
//--- Establecemos los valores en el recuadro de símbolos
   uint rows_total=m_table_symb.RowsTotal();
   for(uint r=0; r<(uint)rows_total; r++)
     {
      //--- Detener el ciclo, si la matriz está fuera del intervalo
      if(r>values_total-1 || values_total<1)
         break;
      //--- Establecemos los valores
      m_table_symb.SetValue(1,r,::DoubleToString(m_values[r],2));
      //--- Establecemos los colores
      color clr=(m_values[r]>(double)m_up_level.GetValue())? clrRed :(m_values[r]<(double)m_down_level.GetValue())? C'85,170,255' : clrBlack;
      m_table_symb.TextColor(0,r,clr,true);
      m_table_symb.TextColor(1,r,clr,true);
      //--- Actualizar el indicador de progreso
      m_progress_bar.LabelText("Initialize tables: "+string(rows_total)+"/"+string(r)+"...");
      m_progress_bar.Update(r,rows_total);
      ::Sleep(5);
     }
//--- Actualizar el recuadro
   m_table_symb.Update();
  }

El bloque del segundo contador de tiempo para la actualización del recuadro de símbolos se muestra más abajo. De esta forma, cada 5 segundos, el programa obtendrá los valores actuales de los indicadores de todos los símbolos y actualizará el recuadro con ellos.

void CProgram::OnTimerEvent(void)
  {
...
//--- Actualizar el recuadro de símbolos
   if(m_counter2.CheckTimeCounter())
     {
      //--- Mostrar progreso
      m_progress_bar.Show();
      m_chart.Redraw();
      //--- Actualizar los valores en el recuadro
      GetIndicatorValues();
      UpdateSymbolsTable();
      //--- Ocultar progreso
      m_progress_bar.Hide();
      m_chart.Redraw();
     }
...
  }

Para actualizar el recuadro de posiciones, primero obtenemos la matriz de símbolos de las posiciones abiertas en el temporizador. A continuación, actualizamos el recuadro con los datos actuales. Lo clasificamos según la misma columna y dirección que antes de la actualización. Aplicamos al recuadro para mostrar los cambios introducidos.

void CProgram::OnTimerEvent(void)
  {
...
//--- Actualizar el recuadro de posiciones
   if(m_counter3.CheckTimeCounter())
     {
      //--- Obtenemos los símbolos de las posiciones abiertas
      string symbols_name[];
      int symbols_total=GetPositionsSymbols(symbols_name);
      //--- Actualizar los valores en el recuadro
      SetValuesToPositionsTable(symbols_name);
      //--- Clasificamos, si el usuario ya ha hecho esto antes de la actualización
      m_table_positions.SortData((uint)m_table_positions.IsSortedColumnIndex(),m_table_positions.IsSortDirection());
      //--- Actualizar el recuadro
      UpdatePositionsTable();
     }
  }

Procesando los eventos de los elementos de control

En este apartado vamos a analizar los métodos para procesar los eventos que se generan cuando el usuario interctúa con la interfaz gráfica de nuestro experto. Ya hemos analizado el método CProgram::RequestData() para obtener los símbolos y datos de los indicadores. Si la inicialización no es la primera, el método se llama al pulsar el botón Request en cualquier momento durante la ejecución del programa. Al pulsar el botón se genera un evento de usuario con el identificador ON_CLICK_BUTTON.

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Eventos de pulsación de botones
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Solicitando datos
      if(RequestData(lparam))
         return;
      //---
      return;
     }
...
  }

En el gif de abajo vemos lo siguiente. En el recuadro se ha formado una lista de símbolos fórex que contienen USD. A continuación, vamos a formar rápidamente una lista de símbolos que contienen EUR. Para ello, solo hay que introducir en el campo de edición Symbols filter el texto «EUR» y pulsar el botón Request. Si queremos ver todos los símbolos y divisas con USD y EUR disponibles en el servidor, tendremos que introducir estas divisas separadas con una coma: «USD,EUR».

 Fig. 5 – formación de la lista de símbolos fórex.

Fig. 3. Formación de la lista de símbolos fórex.

La formación de la lista de símbolos fórex y la obtención de los manejadores de los indicadores se realiza según el periodo indicado en el cuadro combinado Timeframes. Si elegimos otro marco temporal en la lista desplegable, deberemos obtener los nuevos manejadores y actualizar los valores en el recuadro. Para ello, necesitaremos el método CProgram::ChangePeriod(). Si ha llegado el idenditificador del cuadro combinado, entonces actualizamos en primer lugar el marco temporal en el objeto gráfico. A continuación, obtenemos los manejadores y los datos de los indicadores de todos los símbolos en el recuadro, después de lo cual, este se actualizará para representar los cambios introducidos. 

//+------------------------------------------------------------------+
//| Cambio de marco temporal                                         |
//+------------------------------------------------------------------+
bool CProgram::ChangePeriod(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_timeframes.Id())
      return(false);
//--- Obtenemos el valor desde la lista desplegable del cuadro combinado
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
//--- Obtenemos el puntero del gráfico según el índice
   m_sub_chart1.GetSubChartPointer(0).Period(StringToTimeframe(tf));
   m_sub_chart1.ResetCharts();
//--- Mostrar progreso
   m_progress_bar.Show();
   m_chart.Redraw();
//--- Obtenemos los manejadores y los datos de los indicadores
   GetHandles();
   GetIndicatorValues();
//--- Actualizamos el recuadro
   UpdateSymbolsTable();
//--- Ocultar progreso
   m_progress_bar.Hide();
   m_chart.Redraw();
   return(true);
  }

Cuando elegimos un ítem en la lista desplegable, se genera el evento personalizado con el identificador ON_CLICK_COMBOBOX_ITEM:

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Evento de selección en el cuadro combinado
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      //--- Cambio de marco temporal
      if(ChangePeriod(lparam))
         return;
      //---
      return;
     }
...
  }

Este es el aspecto del cambio de marco temporal y la obtención de nuevos datos:

 Fig. 6 – Cambio de marco temporal.

Fig. 4. Cambio de marco temporal.

Ahora analizamos cómo cambiar rápidamente el símbolo en el objeto gráfico. En el recuadro de símbolos en la primera columna ya se encuentra el nombre de los símbolos. Por eso podemos alternar entre ellos con solo destacar la línea en el recuadro. Clicando en esta o aquella línea se llama el método CProgram::ChangeSymbol(). Aquí, primero se comprueba el identificador del recuadro de símbolos. A continuación, tenemos que comprobar si se ha destacado la línea en el recuadro, puesto que la selección de líneas se quita pulsando de nuevo sobre ellas. Si se han superado estas comprobaciones, a continuación necesitaremos guardar el índice de la línea destacada como índice del manejador. Según este, podremos más tarde establecer el indicador en el gráfico (ver más adelante en el artículo).

Una vez obtenido el símbolo de la primera columna del recuadro según la línea destacada, lo definimos en el objeto gráfico. Como información adicional, mostraremos la descripción completa del símbolo en el primer punto de la barra de estado. Al quitar la selección de la línea del recuadro, el texto se define en el valor por defecto

//+------------------------------------------------------------------+
//| Cambio de símbolo                                                |
//+------------------------------------------------------------------+
bool CProgram::ChangeSymbol(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_table_symb.Id())
      return(false);
//--- Salir, si la línea no está seleccionada
   if(m_table_symb.SelectedItem()==WRONG_VALUE)
     {
      //--- Mostrar la descripción completa del símbolo en la barra de estado
      m_status_bar.SetValue(0,"For Help, press F1");
      m_status_bar.GetItemPointer(0).Update(true);
      return(false);
     }
//--- Guardamos el índice del manejador
   m_current_handle_index=m_table_symb.SelectedItem();
//--- Obtenemos el símbolo
   string symbol=m_table_symb.GetValue(0,m_current_handle_index);
//--- Actualizar el gráfico
   m_sub_chart1.GetSubChartPointer(0).Symbol(symbol);
   m_sub_chart1.ResetCharts();
//--- Mostrar la descripción completa del símbolo en la barra de estado
   m_status_bar.SetValue(0,::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
   m_status_bar.GetItemPointer(0).Update(true);
   m_chart.Redraw();
   return(true);
  }

Al seleccionar una línea en el recuadro se genera el evento personalizado con el identificador ON_CLICK_LIST_ITEM. También podemos alternar los símbolos con la tecla «Up», «Down», «Home» y «End». En este caso, se generará el evento CHARTEVENT_KEYDOWN. El método para procesarlo se ha analizado en el artículo anterior, por eso no vamos a hablar de él aquí.

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Eventos de selección del ítem en la lista/recuadro
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      //--- Cambio de símbolo
      if(ChangeSymbol(lparam))
         return;
      //---
      return;
     }
//--- Pulsar sobre la tecla
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- Selección de resultados usando las teclas
      if(SelectingResultsUsingKeys(lparam))
         return;
      //---
      return;
     }
...
  }

Como resultado del procesamiento de estos eventos, vemos aproximadamente esta imagen:

 Fig. 7 – Alternancia de símbolos.

Fig. 5. Alternancia de símbolos.

En ocasiones, necesitamos ver en el gráfico el indicador usado para obtener señales. Para activar la muestra del indicador, se ha pensado la casilla de verificación Show indicator. El método CProgram::ShowIndicator() es responsable de la interacción con el mismo. Aquí también deberemos comprobar la identidad del elemento y la salida de los límites del intervalo de la matriz de los manejadores. Para añadir o eliminar el indicador de un objeto gráfico, necesitaremos el identificador de este gráfico. A continuación, si la casilla de verificación está activada, deberemos añadir el indicador al gráfico. Puesto que el indicador siempre será uno, el número de la subventana lo indicamos como 1. Para casos más complejos, deberemos determinar el número de indicadores en el gráfico. 

//+------------------------------------------------------------------+
//| Visibilidad del indicador                                        |
//+------------------------------------------------------------------+
bool CProgram::ShowIndicator(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_show_indicator.Id())
      return(false);
//--- Comprobando si la matriz está fuera del intervalo
   int handles_total=::ArraySize(m_handles);
   if(m_current_handle_index<0 || m_current_handle_index>handles_total-1)
      return(true);
//--- Obtenemos el identificador del gráfico
   long sub_chart_id=m_sub_chart1.GetSubChartPointer(0).GetInteger(OBJPROP_CHART_ID);
//--- Número de la subventana para el indicador
   int subwindow =1;
//--- Obtenemos el puntero del gráfico según el índice
   if(m_show_indicator.IsPressed())
     {
      //--- Añadimos el indicador al gráfico
      ::ChartIndicatorAdd(sub_chart_id,subwindow,m_handles[m_current_handle_index]);
     }
   else
     {
      //--- Eliminamos el indicador del gráfico
      ::ChartIndicatorDelete(sub_chart_id,subwindow,ChartIndicatorName(sub_chart_id,subwindow,0));
     }
//--- Actualizar el gráfico
   m_chart.Redraw();
   return(true);
  }

Al interactuar con la casilla de verificación, se genera el evento personalizado ON_CLICK_CHECKBOX:

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Eventos de pulsación en el elemento "Casilla de verificación"
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_CHECKBOX)
     {
      //--- Si hemos pulsado en la casilla de verificación "Show indicator"
      if(ShowIndicator(lparam))
         return;
      //---
      return;
     }
  }

Este es el aspecto que tiene en funcionamiento:

 Fig. 8 – Muestra del indicador.

Fig. 6. Muestra del indicador.

Hay otros dos elementos de control que tienen relación con el indicador en la interfaz gráfica del experto. Se trata de los campos de edición numéricos de los niveles del indicador Stochastic: Up level y Down level. Por defecto, en ellos tenemos los valores 80 y 20. Si los valores de los indicadores en cada símbolo sobrepasan estos límites por encima o por debajo, el texto en las celdas del recuadro de los símbolos cambiará de negro a azul para el nivel superior, y a rojo para el inferior. Si cambiamos los valores en estos campos de edición, en la siguiente actualización (cada cinco segundos), también cambiará la indicación de color. 

Así es como funciona esto al cambiar los valores de 80/20 a 90/10, y al contrario:

 Fig. 9 – Cambio de los niveles de señal del indicador.

Fig. 7. Cambio de los niveles de señal del indicador.

Varios elementos de control han sido pensados para trabajar con las propiedades del gráfico. Son:

  • las casillas de verificación Date scale y Price scale para controlar la visibilidad de las escalas del gráfico;
  • el campo de edición Chart scalepara controlar la escala del gráfico
  • y el botón Chart shift para activar la sangría en la parte derecha del gráfico. 

Los métodos de procesamiento de eventos de las casillas de verificación Date scale y Price scale son muy semejantes. Tanto en el uno como en el otro, dependiendo del estado de la casilla de verificación, se activa o se desactiva la propiedad correspondiente del gráfico. El método CStandardChart::ResetCharts() desplaza el gráfico al final del todo.

//+------------------------------------------------------------------+
//| Visibilidad de la escala temporal                                |
//+------------------------------------------------------------------+
bool CProgram::DateScale(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_date_scale.Id())
      return(false);
//--- Obtenemos el puntero del gráfico según el índice
   m_sub_chart1.GetSubChartPointer(0).DateScale(m_date_scale.IsPressed());
   m_sub_chart1.ResetCharts();
//--- Actualizar el gráfico
   m_chart.Redraw();
   return(true);
  }
//+------------------------------------------------------------------+
//| Visibilidad de la escala de precio                               |
//+------------------------------------------------------------------+
bool CProgram::PriceScale(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_price_scale.Id())
      return(false);
//--- Obtenemos el puntero del gráfico según el índice
   m_sub_chart1.GetSubChartPointer(0).PriceScale(m_price_scale.IsPressed());
   m_sub_chart1.ResetCharts();
//--- Actualizar el gráfico
   m_chart.Redraw();
   return(true);
  }

Para controlar la escala del gráfico, se usa el método CProgram::ChartScale(). Aquí, si el valor en el campo de edición ha cambiado, este se asigna al gráfico.

//+------------------------------------------------------------------+
//| Escala del gráfico                                               |
//+------------------------------------------------------------------+
bool CProgram::ChartScale(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_chart_scale.Id())
      return(false);
//--- Establecer escala
   if((int)m_chart_scale.GetValue()!=m_sub_chart1.GetSubChartPointer(0).Scale())
      m_sub_chart1.GetSubChartPointer(0).Scale((int)m_chart_scale.GetValue());
//--- Actualizar
   m_chart.Redraw();
   return(true);
  }

El cambio de valor en el campo de edición Chart scale se procesa según la llegada de los eventos personalizados con los identificadores ON_CLICK_BUTTON y ON_END_EDIT.

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Eventos de pulsación de botones
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Escala del gráfico
      if(ChartScale(lparam))
         return;
      //---
      return;
     }
//--- Eventos de finalización del cambio de valor en el campo de edición
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      //--- Escala del gráfico
      if(ChartScale(lparam))
         return;
      //---
      return;
     }
  }

Más abajo se muestra el código del método CProgram::ChartShift(), para activar en el gráfico la sangría por la derecha. En él, después de comprobar la ID del elemento, primero obtenemos el identificador del gráfico, y a continuación, usándolo como clave de acceso, podemos establecer la sangría (CHART_SHIFT).

//+------------------------------------------------------------------+
//| Desplazamiento del gráfico                                       |
//+------------------------------------------------------------------+
bool CProgram::ChartShift(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_chart_shift.Id())
      return(false);
//--- Obtenemos el identificador del gráfico
   long sub_chart_id=m_sub_chart1.GetSubChartPointer(0).GetInteger(OBJPROP_CHART_ID);
//--- Establecemos la sangría en la parte derecha del gráfico
   ::ChartSetInteger(sub_chart_id,CHART_SHIFT,true);
   m_sub_chart1.ResetCharts();
   return(true);
  }

Este es el aspecto que tiene:

 Fig. 10 – Control de las propiedades del gráfico.

Fig. 8.  Control de las propiedades del gráfico.

Métodos de realización de operaciones comerciales

Vamos a mostrar con ejemplos cómo podemos conectar de forma sencilla y rápida los métodos comerciales con la interfaz gráfica del experto. Nuestro asesor no solo visualizará los datos, sino que también realizará operaciones comerciales. Es más cómodo trabajar cuando todo se encuentra en un mismo lugar y es posible alternar rápidamente los gráficos, y en caso necesario, comerciar. Como ejemplo, para las operaciones comerciales usaremos las posibilidades de la biblioteca estándar. Pero también podemos incluir otras bibliotecas comerciales. 

Incluimos en el proyecto el archivo Trade.mqh con la clase CTrade y declaramos el ejemplar de esta clase:

//--- Clase para las operaciones comerciales
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                   |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Operaciones comerciales
   CTrade            m_trade;
  };

Establecemos el modo asincrónico de ejecución de transacciones, para que el programa no espere el resultado de cada operación comercial. Además, establecemos el deslizamiento máximo permitido. Es decir, las transacciones se ejecutarán con cualquier desviación respecto al precio indicado en la operación comercial.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
  {
...
   m_trade.SetAsyncMode(true);
   m_trade.SetDeviationInPoints(INT_MAX);
  }

La pulsación de los botones Buy y Sell será procesada por los métodos semejantes CProgram::OnBuy() y CProgram::OnSell(). El volumen de la transacción lo obtendremos del campo de edición Lot. El símbolo con el que comerciamos lo tomaremos en el objeto gráfico. Se trata del mínimo necesario para realizar operaciones comerciales. En la clase CTrade existen los métodos CTrade::Buy() y CTrade::Sell(), al llamar a los cuales solo se pueden transmitir estos dos argumentos. 

//+------------------------------------------------------------------+
//| Compra                                                           |
//+------------------------------------------------------------------+
bool CProgram::OnBuy(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_buy.Id())
      return(false);
//--- Volumen y símbolo para la apertura de la posición
   double lot    =::NormalizeDouble((double)m_lot.GetValue(),2);
   string symbol =m_sub_chart1.GetSubChartPointer(0).Symbol();
//--- Abrir posición
   m_trade.Buy(lot,symbol);
   return(true);
  }
//+------------------------------------------------------------------+
//| Venta                                                            |
//+------------------------------------------------------------------+
bool CProgram::OnSell(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_sell.Id())
      return(false);
//--- Volumen y símbolo para la apertura de la posición
   double lot    =::NormalizeDouble((double)m_lot.GetValue(),2);
   string symbol =m_sub_chart1.GetSubChartPointer(0).Symbol();
//--- Abrir posición
   m_trade.Sell(lot,symbol);
   return(true);
  }

Para cerrar todas las posiciones a la vez o solo las del símbolo indicado, deberemos escribir un método aparte: en la clase CTrade no existe uno así. Si el símbolo ha sido transmitido al método (parámetro opcional), se cerrarán las posiciones solo de este símbolo. Si el símbolo no ha sido indicado, se cerrarán todas las posiciones.

//+------------------------------------------------------------------+
//| Se cierran todas las posiciones                                  |
//+------------------------------------------------------------------+
bool CProgram::CloseAllPosition(const string symbol="")
  {
//--- Comprobamos si hay una posición con las propiedades indicadas
   int total=::PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- Elegimos la posición
      string pos_symbol=::PositionGetSymbol(i);
      //--- Si se indica cerrar según el símbolo
      if(symbol!="")
         if(symbol!=pos_symbol)
            continue;
      //--- Obtenemos el ticket
      ulong position_ticket=::PositionGetInteger(POSITION_TICKET);
      //--- Resetear el último error
      ::ResetLastError();
      //--- si la posición no se ha cerrado, se muestra un mensaje sobre ello
      if(!m_trade.PositionClose(position_ticket))
         ::Print(__FUNCTION__,": > An error occurred when closing a position: ",::GetLastError());
     }
//---
   return(true);
  }

El cierre de todas las posiciones está vinculado al botón Close all positions. Su pulsación será procesada en el método CProgram::OnCloseAllPositions(). Para excluir las pulsaciones casuales sobre el botón, se abrirá una ventana de diálogo para confirmar la operación.

//+------------------------------------------------------------------+
//| Cerrar todas las posiciones                                      |
//+------------------------------------------------------------------+
bool CProgram::OnCloseAllPositions(const long id)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_close_all.Id())
      return(false);
//--- Ventana de diálogo
   int mb_id=::MessageBox("Are you sure you want to close \nall positions?","Close positions",MB_YESNO|MB_ICONWARNING);
//--- Cerrar la posición
   if(mb_id==IDYES)
      CloseAllPosition();
//---
   return(true);
  }

Este es el aspecto que tiene:

 Fig. 11 – Cierre de todas las posiciones

Fig. 9. Cierre de todas las posiciones.

Podrá cerrar las posiciones del símbolo en la pestaña Positions. En las celdas de la primera columna del recuadro de posiciones se han añadido botones en forma de cruces. Con su ayuda podremos cerrar a la vez todas las posiciones del símbolo cuyos datos se muestran en esta línea. La pulsación de los botones en las celdas genera un evento personalizado con el identificador ON_CLICK_BUTTON. Pero en el elemento del tipo CTable hay franjas de desplazmiento cuyos botones generan los mismos eventos, y el identificador del elemento también coincide. Por eso debemos rastrear el parámetro de línea (sparam) del evento, para no procesar por accidente la pulsación en otros botones del elemento. En el parámetro de línea se indica el tipo de elemento en el que se ha dado la pulsación. En las franjas de desplazamiento se llama «scroll». Si ha tenido lugar un evento con ese valor, el programa saldrá del método. Después de ello, habrá que comprobar si hay posiciones abiertas.

Si todas las comprobaciones han sido superadas, tendremos que extraer de la descripción del parámetro de línea el índice de la línea que determina el símbolo en la primera columna del recuadro. Aquí, para excluir también la pulsación casual de los botones, primero se abrirá la ventana de diálogo para confirmar la acción. Pulsando el botón , se cerrarán solo las posiciones del símbolo indicado

//+------------------------------------------------------------------+
//| Cerrar todas las posiciones del símbolo indicado                 |
//+------------------------------------------------------------------+
bool CProgram::OnCloseSymbolPositions(const long id,const string desc)
  {
//--- Comprobando el identificador del elemento
   if(id!=m_table_positions.Id())
      return(false);
//--- Salir si se ha pulsado el botón de la barra de deslizamiento
   if(::StringFind(desc,"scroll",0)!=WRONG_VALUE)
      return(false);
//--- Salir si no hay posiciones
   if(::PositionsTotal()<1)
      return(true);
//--- Extraemos los datos de la línea
   string str_elements[];
   ushort sep=::StringGetCharacter("_",0);
   ::StringSplit(desc,sep,str_elements);
//--- Obtenemos el índice y el símbolo
   int    row_index =(int)str_elements[1];
   string symbol    =m_table_positions.GetValue(0,row_index);
//--- Ventana de diálogo
   int mb_id=::MessageBox("Are you sure you want to close \nall positions on symbol "+symbol+"?","Close positions",MB_YESNO|MB_ICONWARNING);
//--- Cerramos todas las posiciones en el símbolo indicado
   if(mb_id==IDYES)
      CloseAllPosition(symbol);
//---
   return(true);
  }

Este es el aspecto que tiene:

 Fig. 11 – Cierre de todas las posiciones del símbolo indicado

Fig. 10. Cierre de todas las posiciones del símbolo indicado

Todos las operaciones comerciales descritas más arriba se procesan con la llegada del evento ON_CLICK_BUTTON:

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Eventos de pulsación de botones
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ...
      //--- Compra
      if(OnBuy(lparam))
         return;
      //--- Venta
      if(OnSell(lparam))

         return;
      //--- Cerrar todas las posiciones
      if(OnCloseAllPositions(lparam))
         return;
      //--- Cerrar todas las posiciones del símbolo indicado
      if(OnCloseSymbolPositions(lparam,sparam))
         return;
      //---
      return;
     }
...
  }

Cada operación comercial deberá reflejarse en el recuadro de posiciones. Para ello, tendremos que rastrear los eventos comerciales y la historia de transacciones. Si el número de transacciones ha cambiado, el recuadro se deberá formar de nuevo. Para comprobar si ha cambiado la historia, se usa el método CProgram::IsLastDealTicket(). Después de cada comprobación, deberemos guardar la hora y el ticket de la última transacción. Guardamos la hora, para no solicitar cada vez toda la historia de transacciones. Comprobamos según el ticket si ha cambiado el número de transacciones en la historia. Puesto que la transacción inicia varios eventos comerciales, este método retornará true solo una vez.

class CProgram : public CWndEvents
  {
private:
   //--- Hora y ticket de la última transacción comprobada
   datetime          m_last_deal_time;
   ulong             m_last_deal_ticket;
   //---
private:
   //--- Comprobar una nueva transacción en la historia
   bool              IsLastDealTicket(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_last_deal_time(NULL),
                           m_last_deal_ticket(WRONG_VALUE)
  {
...
  }
//+------------------------------------------------------------------+
//| Comprobar una nueva transacción en la historia                   |
//+------------------------------------------------------------------+
bool CProgram::IsLastDealTicket(void)
  {
//--- Salir si no se ha obtenido la historia
   if(!::HistorySelect(m_last_deal_time,UINT_MAX))
      return(false);
//--- Obtenemos el número de transacciones en la lista recibida
   int total_deals=::HistoryDealsTotal();
//--- Iteramos por todas las transacciones en la lista obtenida, desde la última transacción hacia la primera
   for(int i=total_deals-1; i>=0; i--)
     {
      //--- Obtenemos el ticket de la transacción
      ulong deal_ticket=::HistoryDealGetTicket(i);
      //--- Si los tickets son iguales, salimos
      if(deal_ticket==m_last_deal_ticket)
         return(false);
      //--- Si los tickets no son iguales, informamos sobre ello
      else
        {
         datetime deal_time=(datetime)::HistoryDealGetInteger(deal_ticket,DEAL_TIME);
         //--- Recordamos la hora y el ticket de la última transacción
         m_last_deal_time   =deal_time;
         m_last_deal_ticket =deal_ticket;
         return(true);
        }
     }
//--- Tickets de otro símbolo
   return(false);
  }

El método CProgram::IsLastDealTicket() lo llamamos en el manejador de eventos comerciales. Si la historia ha cambiado, el recuadro de posiciones se formará de nuevo:

//+------------------------------------------------------------------+
//| Evento de operación comercial                                    |
//+------------------------------------------------------------------+
void CProgram::OnTradeEvent(void)
  {
//--- Si hay una nueva transacción
   if(IsLastDealTicket())
     {
      //--- Inicializando el recuadro de posiciones
      InitializePositionsTable();
     }
  }

Este es el aspecto que tiene:

 Fig. 12 – Formando el recuadro al cerrar las posiciones del símbolo.

Fig. 11. Formando el recuadro al cerrar las posiciones del símbolo.

Conclusión

En el artículo hemos mostrado cómo crear interfaces gráficas para programas de cualquier nivel de complejidad. Usted puede seguir desarrollado este programa y usarlo para sus propios propósitos. La idea se puede mejorar añadiendo nuestros propios indicadores y los resultados de los cálculos.

Para aquellos que no quieran escarbar en el código y compilar el programa por sí mismos, en el mercado encontrarán la aplicación preparada Trading Exposure.

En el artículo se incluyen los archivos necesarios para realizar las pruebas y analizar con más detalle el código presentado en el mismo.

Nombre del archivo Comentarios
MQL5\Experts\TradePanel\TradePanel.mq5 Experto comercial para el comercio manual con la interfaz gráfica
MQL5\Experts\TradePanel\Program.mqh Archivo con la clase del programa
MQL5\Experts\TradePanel\CreateGUI.mqh Archivo con la implementación de los métodos para crear una interfaz gráfica a partir de la clase de programa en el archivo Program.mqh
MQL5\Include\EasyAndFastGUI\Controls\Table.mqh Clase actualizada CTable
MQL5\Include\EasyAndFastGUI\Keys.mqh Clase actualizada CKeys

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

Archivos adjuntos |
MQL5.zip (48 KB)
14 000 robots comerciales en MetaTrader Market 14 000 robots comerciales en MetaTrader Market

En la mayor tienda de aplicaciones comerciales ya tiene a su disposición 13 970 productos. Entre ellos, encontrará 4 800 robots, 6 500 indicadores, 2 400 utilidades y otras soluciones. En este caso, además, la mitad de las aplicaciones (6 000) se pueden alquilar. Una cuarta parte del total de los productos (3 800) es de acceso gratuito.

Monitoreo de la cuenta comercial: una herramienta imprescindible para el tráder Monitoreo de la cuenta comercial: una herramienta imprescindible para el tráder

El monitoreo de la cuenta comercial es un informe detallado de todas las transacciones realizadas.

Análisis comparativo de 10 estrategias de flat Análisis comparativo de 10 estrategias de flat

En el artículo se analizan las ventajas y desventajas del comercio con flat (mercado plano). Asimismo, se han creado y probado 10 estrategias basadas en el monitoreo del movimiento del precio dentro del canal. Cada estrategia está provista de un mecanismo de filtrado, para descartar las señales falsas de entrada en el mercado.

950 sitios web transmiten el calendario económico de MetaQuotes 950 sitios web transmiten el calendario económico de MetaQuotes

La adición del widget proporciona a los sitios web un horario de publicación detallado de 500 índices e indicadores de las mayores economías mundiales. De esta forma, los tráders, aparte del contenido principal del sitio web, reciben de manera operativa información actual sobre todos los eventos importantes, complementada con explicaciones y gráficos.