Gráfico del balance de multisímbolos en MetaTrader 5
Contenido
- Introducción
- Desarrollo de la interfaz gráfica
- Asesor Experto de multisímbolos para las pruebas
- Escritura de datos en el archivo
- Extracción de datos del archivo
- Visualización de datos en los gráficos
- Demostración del resultado obtenido
- Gráfico del balance de multisímbolos durante el trading y las pruebas
- Visualizando los informes del servicio «Señales»
- Conclusión
Introducción
En uno de los artículos anteriores, hemos considerado la visualización de los gráficos del balance de multisímbolos. Pero desde aquel entonces, han sido desarrolladas muchas bibliotecas MQL que permiten implementar todo eso en el terminal MetaTrader 5 sin usar los programas ajenos.
En este artículo, voy a mostrar el ejemplo de la aplicación con la interfaz gráfica en la que se muestra el gráfico del balance de multisímbolos y la reducción del depósito según los resultados de la última prueba. El historial de las transacciones va a escribirse en el archivo al final de la simulación del Asesor Experto (EA). Luego, se puede leer y visualizar estos datos en los gráficos.
Aparte de eso, en el artículo se presenta la versión del EA en la que el gráfico del balance de multisímbolos se visualiza y se actualiza en la interfaz gráfica directamente durante el trading, así como durante la simulación en modo de visualización.
Desarrollo de la interfaz gráfica
La manera de como conectar y usar la biblioteca EasyAndFast y como crear una interfaz gráfica para su aplicación MQL a través de ella fue demostrada en el artículo Visualizando la optimización de una estrategia comercial en MetaTrader 5 Por eso, pasaremos directamente a la interfaz gráfica según el tema en cuestión.
Estos son los elementos que van a usarse en la interfaz gráfica.
- Formulario para los controles.
- Botón para actualizar los gráficos con los resultados de la última prueba.
- Gráfico para visualizar el balance de multisímbolos.
- Gráfico para visualizar las reducciones del depósito (drawdown).
- Barra de estado para mostrar la información final adicional.
Las declaraciones de los métodos para crear estos elementos se muestran el código de abajo. La implementación de los métodos se encuentra en el archivo de inclusión separado.
//+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Ventana CWindow m_window1; //--- Barra de estado CStatusBar m_status_bar; //--- Gráficos CGraph m_graph1; CGraph m_graph2; //--- Botones CButton m_update_graph; //--- 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); //--- Gráficos bool CreateGraph1(const int x_gap,const int y_gap); bool CreateGraph2(const int x_gap,const int y_gap); //--- Botones bool CreateUpdateGraph(const int x_gap,const int y_gap,const string text); }; //+------------------------------------------------------------------+ //| Métodos para crear los controles | //+------------------------------------------------------------------+ #include "CreateGUI.mqh" //+------------------------------------------------------------------+
El método principal para crear las interfaces gráficas será el siguiente:
//+------------------------------------------------------------------+ //| Crea la interfaz gráfica | //+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Creación del formulario para los controles if(!CreateWindow("Expert panel")) return(false); //--- Creación de controles if(!CreateStatusBar(1,23)) return(false); if(!CreateGraph1(1,50)) return(false); if(!CreateGraph2(1,159)) return(false); if(!CreateUpdateGraph(7,25,"Update data")) return(false); //--- Terminar la creación de GUI CWndEvents::CompletedGUI(); return(true); }
En total, si ahora compilamos el EA y lo cargamos en el gráfico en el terminal, el resultado actual será el siguiente:
Fig. 1. Interfaz gráfica del Asesor Experto.
A continuación, hablaremos de la escritura de datos en el archivo.
Asesor Experto de multisímbolos para las pruebas
Para las pruebas vamos a usar el EA MACD Sample de la entrega estándar, pero haremos que sea de multisímbolos. El esquema de multisímbolos que se utiliza en esta versión no es exacta. Con los mismos parámetros, el resultado va a ser diferente dependiendo del símbolo en el que va a realizarse la simulación (se elige en los ajustes del Probador de Estrategias). Por eso, este EA está destinado sólo para las pruebas y demostración de los resultados obtenidos en el marco del tema en cuestión.
Próximamente, las nuevas posibilidades para crear los EAs de multisímbolos serán presentadas en las actualizaciones del terminal MetaTrader 5. Entonces, se podrá pensar en la creación la versión final y universal para los EAS de este tipo. Pero si Usted necesita urgentemente un esquema de multisímbolos rápido y preciso, se puede probar la variante que ha sido propuesta en el foro.
Vamos a insertar otro parámetro string en los parámetros externos para especificar los símbolos que serán usados en la prueba:
//--- Parámetros externos sinput string Symbols ="EURUSD,USDJPY,GBPUSD,EURCHF"; // Symbols input double InpLots =0.1; // Lots input int InpTakeProfit =167; // Take Profit (in pips) input int InpTrailingStop =97; // Trailing Stop Level (in pips) input int InpMACDOpenLevel =16; // MACD open level (in pips) input int InpMACDCloseLevel =19; // MACD close level (in pips) input int InpMATrendPeriod =14; // MA trend period
Los símbolos se especifican separados con coma. Los métodos para la lectura de este parámetro están implementados en la clase del programa (CProgram), así como para el chequeo de los símbolos y para la colocación de aquéllos que figuran en la lista del servidor en la Observación del Mercado. Como opción, se puede especificar los símbolos para el trading a través de una lista preparada de antemano en el archivo, tal como ha sido demostrado en el artículo Libro de recetas MQL5: Desarrollar un Asesor Experto multidivisa con un número ilimitado de parámetros. Es más, se puede componer varias listas en el archivo a selección del usuario, y este ejemplo se puede ver en el artículo Guía práctica de MQL5: Reducción del efecto del sobreajuste y el manejo de la falta de cotizaciones. Se puede inventar varios maneras para seleccionar los símbolos y sus listas a través de la interfaz gráfica. Voy a demostrar esta opción en uno de los siguientes artículos.
Antes de verificar los símbolos en la lista general, hay que guardarlos en el array. Luego, pasaremos este array (source_array[]) en el método CProgram::CheckTradeSymbols(). Aquí, repasamos los símbolos especificados en el parámetro externo en el primer ciclo, y luego en el segundo ciclo, comprobamos si figura este símbolo en la lista en el servidor del bróker. Si es así, lo insertamos en la ventana «Observación de Mercado» y en el array de los símbolos verificados.
Al final del método, si los símbolos no han sido encontrados, va a usarse sólo el símbolo actual del EA.
class CProgram : public CWndEvents { private: //--- Comprueba los símbolos para el trading en el array pasado y devuelve el array de los disponibles void CheckTradeSymbols(string &source_array[],string &checked_array[]); }; //+------------------------------------------------------------------+ //| Comprueba los símbolos para el trading en el array pasado y | //| devuelve el array de los disponibles | //+------------------------------------------------------------------+ void CProgram::CheckTradeSymbols(string &source_array[],string &checked_array[]) { int symbols_total =::SymbolsTotal(false); int size_source_array =::ArraySize(source_array); //--- Buscamos los símbolos indicados en la lista general for(int i=0; i<size_source_array; i++) { for(int s=0; s<symbols_total; s++) { //--- Obtenemos el nombre del símbolo actual en la lista general string symbol_name=::SymbolName(s,false); //--- Si coincide if(symbol_name==source_array[i]) { //--- Colocamos el símbolo en Observación de Mercado ::SymbolSelect(symbol_name,true); //--- Añadimos al array de los símbolos confirmados int size_array=::ArraySize(checked_array); ::ArrayResize(checked_array,size_array+1); checked_array[size_array]=symbol_name; break; } } } //--- Si los símbolos no han sido encontrados, usamos el símbolo actual if(::ArraySize(checked_array)<1) { ::ArrayResize(checked_array,1); checked_array[0]=_Symbol; } }
El método CProgram::CheckSymbols() se usa para leer el parámetro string externo en el que especifican los símbolos. Aquí, la cadena se divide en el array por el separador ','. En las cadenas obtenidas, los espacios se cortan a ambos lados. Después de eso, el array se envía para la verificación al método CProgram::CheckTradeSymbols(), que hemos considerado antes.
class CProgram : public CWndEvents { private: //--- Comprueba y selecciona los símbolos para el trading en el array desde la cadena int CheckSymbols(const string symbols_enum); }; //+-------------------------------------------------------------------------------------+ //| Comprueba y selecciona los símbolos para el trading en el array desde la cadena | //+-------------------------------------------------------------------------------------+ int CProgram::CheckSymbols(const string symbols_enum) { if(symbols_enum!="") ::Print(__FUNCTION__," > input trade symbols: ",symbols_enum); //--- Obtenemos los símbolos de la cadena string symbols[]; ushort u_sep=::StringGetCharacter(",",0); ::StringSplit(symbols_enum,u_sep,symbols); //--- Cortamos los espacios por ambos lados int elements_total=::ArraySize(symbols); for(int e=0; e<elements_total; e++) { ::StringTrimLeft(symbols[e]); ::StringTrimRight(symbols[e]); } //--- Comprobamos los símbolos ::ArrayFree(m_symbols); CheckTradeSymbols(symbols,m_symbols); //--- Devolvemos el número de los símbolos para el trading return(::ArraySize(m_symbols)); }
El archivo con la clase de la estrategia comercial se incluye en el archivo con la clase de la aplicación y se crea el array dinámico del array tipo CStrategy.
#include "Strategy.mqh" //+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Array de estrategias CStrategy m_strategy[]; };
Durante la inicialización, precisamente aquí obtenemos el array de símbolos y su cantidad del parámetro externo. Luego, establecemos el tamaño para el array de estrategias según el número de los símbolos, e inicializamos todas las instancias de las estrategias, pasando el nombre del símbolo en cada una de ellas.
class CProgram : public CWndEvents { private: //--- Total de símbolos int m_symbols_total; }; //+------------------------------------------------------------------+ //| Inicialización | //+------------------------------------------------------------------+ bool CProgram::OnInitEvent(void) { //--- Obtenemos símbolos para el trading m_symbols_total=CheckSymbols(Symbols); //--- Tamaño del array de sistemas de trading ::ArrayResize(m_strategy,m_symbols_total); //--- Inicialización for(int i=0; i<m_symbols_total; i++) { if(!m_strategy[i].OnInitEvent(m_symbols[i])) return(false); } //--- Inicialización con éxito return(true); }
A continuación, hablaremos de la escritura de datos de la última prueba en el archivo.
Escritura de datos en el archivo
Los datos de la última prueba van a guardarse en la carpeta compartida de los terminales. De esta manera, el archivo estará disponible desde cualquier terminal MetaTrader 5. Determinaremos inmediatamente el nombre de la carpeta y del archivo en el constructor:
class CProgram : public CWndEvents { private: //--- Ruta hacia el archivo con los resultados de la última prueba string m_last_test_report_path; }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CProgram::CProgram(void) : m_symbols_total(0) { //--- Ruta hacia el archivo con los resultados de la última prueba m_last_test_report_path=::MQLInfoString(MQL_PROGRAM_NAME)+"\\LastTest.csv"; }
Vamos a analizar el método CProgram::CreateSymbolBalanceReport(), a través del cual va a realizarse la escritura en el archivo. Para trabajar en este método (así como en el otro del que hablaremos más tarde), vamos a necesitar los arrays de los balances de los símbolos.
//--- Arrays para los balances de todos los símbolos struct CReportBalance { double m_data[]; }; //+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Arrays de los balances de todos los símbolos CReportBalance m_symbol_balance[]; //--- private: //--- Crea el informe de la simulación para las transacciones en el formato CSV void CreateSymbolBalanceReport(void); }; //+---------------------------------------------------------------------------------+ //| Crea el informe de la simulación para las transacciones en el formato CSV | //+---------------------------------------------------------------------------------+ void CProgram::CreateSymbolBalanceReport(void) { ... }
Al principio del método, abrimos el archivo para trabajar en la carpeta compartida de los terminales (FILE_COMMON):
... //--- Creamos el archivo para la escritura de datos en la carpeta compartida del terminal int file_handle=::FileOpen(m_last_test_report_path,FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON); //--- Si el handle es válido (el archivo se ha creado/ha abierto) if(file_handle==INVALID_HANDLE) { ::Print(__FUNCTION__," > Error creating file: ",::GetLastError()); return; } ...
Necesitaremos unas cuantas variables auxiliares para la formación de algunos valores del informe. En el archivo, vamos a escribir el historial completo de transacciones con los datos que se listan a continuación:
- Hora de la transacción
- Símbolo
- Tipo
- Dirección
- Volumen
- Precio
- Swap
- Resultado (ganancias/pérdidas)
- Reducción (Drawdown)
- Balance. Esta columna va a contener el balance total, y en las siguientes, habrá balances de los símbolos que participaban en la simulación
Aquí mismo, formamos la primera línea con los encabezados de estos datos:
... double max_drawdown =0.0; // Reducción máxima double balance =0.0; // Balance string delimeter =","; // Separador string string_to_write =""; // Para formar la cadena de caracteres para la escritura //--- Formamos la línea de encabezados string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,PRICE,SWAP($),PROFIT($),DRAWDOWN(%),BALANCE"; ...
Si más de un símbolo participa en la simulación, hay que completar la línea de encabezados con sus nombres. Después de eso, se puede escribir los encabezados (la primera línea) en el archivo.
... //--- Si hay más de un símbolo, rellenamos la línea de encabezados int symbols_total=::ArraySize(m_symbols); if(symbols_total>1) { for(int s=0; s<symbols_total; s++) ::StringAdd(headers,delimeter+m_symbols[s]); } //--- Escribimos los encabezados del archivo ::FileWrite(file_handle,headers); ...
Luego, obtenemos el historial completo de transacciones y su cantidad, y después, establecemos los tamaños para los arrays:
... //--- Obtenemos el historial completo ::HistorySelect(0,LONG_MAX); //--- Averiguamos el número de transacciones int deals_total=::HistoryDealsTotal(); //--- Establecemos el tamaño del array de balances según el número de símbolos ::ArrayResize(m_symbol_balance,symbols_total); //--- Establecemos el tamaño de los arrays de transacciones para cada símbolo for(int s=0; s<symbols_total; s++) ::ArrayResize(m_symbol_balance[s].m_data,deals_total); ...
Repasamos el historial completo en el ciclo principal y formamos las cadenas para la escritura en el archivo. Al calcular el beneficio, sumamos también el swap y la comisión. Si resulta que hay más de un símbolo, los repasamos en el segundo ciclo y formamos el balance para cada símbolo.
Escribimos los datos en el archivo cadena por cadena. Al final del método, el archivo se cierra.... //--- Repasamos en el ciclo y escribimos los datos for(int i=0; i<deals_total; i++) { //--- Obtenemos el ticket de la transacción if(!m_deal_info.SelectByIndex(i)) continue; //--- Averiguamos el número de dígitos en el precio int digits=(int)::SymbolInfoInteger(m_deal_info.Symbol(),SYMBOL_DIGITS); //--- Calculamos el balance final balance+=m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission(); //--- Formamos la cadena para la escritura mediante la concatenación ::StringConcatenate(string_to_write, ::TimeToString(m_deal_info.Time(),TIME_DATE|TIME_MINUTES),delimeter, m_deal_info.Symbol(),delimeter, m_deal_info.TypeDescription(),delimeter, m_deal_info.EntryDescription(),delimeter, ::DoubleToString(m_deal_info.Volume(),2),delimeter, ::DoubleToString(m_deal_info.Price(),digits),delimeter, ::DoubleToString(m_deal_info.Swap(),2),delimeter, ::DoubleToString(m_deal_info.Profit(),2),delimeter, MaxDrawdownToString(i,balance,max_drawdown),delimeter, ::DoubleToString(balance,2)); //--- Si hay más de un símbolo, escribimos los valores del balance if(symbols_total>1) { //--- Repasamos todos los símbolos for(int s=0; s<symbols_total; s++) { //--- Si los símbolos coinciden y el resultado de la transacción no es nulo if(m_deal_info.Symbol()==m_symbols[s] && m_deal_info.Profit()!=0) //--- Reflejamos la transacción en el balance con este símbolo. Tomamos en cuenta el swap y la comisión m_symbol_balance[s].m_data[i]=m_symbol_balance[s].m_data[i-1]+m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission(); //--- De los contrario, escribiremos el valor anterior else { //--- Si el tipo de la transacción «Calculo del balance» (la primera transacción), el balance es el mismo para todos los símbolos if(m_deal_info.DealType()==DEAL_TYPE_BALANCE) m_symbol_balance[s].m_data[i]=balance; //--- De los contrario, escribimos el valor anterior en el índice actual else m_symbol_balance[s].m_data[i]=m_symbol_balance[s].m_data[i-1]; } //--- Añadimos el balance del símbolo a la cadena ::StringAdd(string_to_write,delimeter+::DoubleToString(m_symbol_balance[s].m_data[i],2)); } } //--- Escribimos la cadena formada ::FileWrite(file_handle,string_to_write); //--- Reseteo obligatorio de la variable para la siguiente cadena string_to_write=""; } //--- Cerramos el archivo ::FileClose(file_handle); ...
En el proceso de la formación de cadenas (ver el código de arriba) para la escritura en el archivo, se utiliza el método метод CProgram::MaxDrawdownToString() para el cálculo de la reducción total del balance. Al llamarlo por primera vez, la reducción es igual a cero. Recordamos el valor actual del balance como el máximo/mínimo local. En las siguientes llamadas al método, cuando el balance es mayor que el de la memoria, calculamos la reducción según los valores anteriores y actualizamos el máximo local. De los contrario, actualizamos el mínimo local y devolvemos el valor nulo (cadena vacía).
class CProgram : public CWndEvents { private: //--- Devuelve la reducción máxima del máximo local string MaxDrawdownToString(const int deal_number,const double balance,double &max_drawdown); }; //+------------------------------------------------------------------+ //| Devuelve la reducción máxima del máximo local | //+------------------------------------------------------------------+ string CProgram::MaxDrawdownToString(const int deal_number,const double balance,double &max_drawdown) { //--- Cadena para visualizar en el informe string str=""; //--- Para el cálculo del máximo local y la reducción static double max=0.0; static double min=0.0; //--- Si es la primera transacción if(deal_number==0) { //--- Todavía no hay reducción max_drawdown=0.0; //--- Establecemos el punto inicial como máximo local max=balance; min=balance; } else { //--- Si el balance actual es mayor que en la memoria if(balance>max) { //--- Calculamos la reducción para los valores anteriores max_drawdown=100-((min/max)*100); //--- Actualizamos al máximo local max=balance; min=balance; } else { //--- Devolvemos el valor nulo de la reducción y actualizamos el mínimo max_drawdown=0.0; min=fmin(min,balance); } } //--- Determinamos la cadena para el informe str=(max_drawdown==0)? "" : ::DoubleToString(max_drawdown,2); return(str); }
La estructura del archivo permite abrirlo en Excel. Eso se muestra en la captura de pantalla de abajo:
Fig. 2. Estructura del informe en Excel.
Al final, la llamada al método CProgram::CreateSymbolBalanceReport() para la escritura del informe después de la prueba debe realizarse al final de la prueba:
//+------------------------------------------------------------------+ //| Evento de finalización de la prueba | //+------------------------------------------------------------------+ double CProgram::OnTesterEvent(void) { //--- Escribimos el informe sólo después de la prueba if(::MQLInfoInteger(MQL_TESTER) && !::MQLInfoInteger(MQL_OPTIMIZATION) && !::MQLInfoInteger(MQL_VISUAL_MODE) && !::MQLInfoInteger(MQL_FRAME_MODE)) { //--- Formación del informe y la escritura en los archivos CreateSymbolBalanceReport(); } //--- return(0.0); }
A continuación, hablaremos de la lectura de datos del informe.
Extracción de datos del archivo
Después de todo lo que hemos implementado más arriba, ahora cada comprobación del EA en el Probador de Estrategias va a concluirse con la escritura del informe en el archivo. A continuación, examinaremos los métodos que se usan para leer los datos de este informe. En primer lugar, hay que leer el archivo y colocar su contenido en el array para que sea más cómodo trabajar con él. Para eso se utiliza el método CProgram::ReadFileToArray(). Aquí, abrimos el archivo en el que ha sido escrito el historial de transacciones al final de la prueba del EA. Leemos cíclicamente el archivo hasta la última cadena y rellenamos el array con datos iniciales.
class CProgram : public CWndEvents { private: //--- Array para los datos desde el archivo string m_source_data[]; //--- private: //--- Lectura del archivo al array pasado bool ReadFileToArray(const int file_handle); }; //+------------------------------------------------------------------+ //| Lectura del archivo al array pasado | //+------------------------------------------------------------------+ bool CProgram::ReadFileToArray(const int file_handle) { //--- Abrimos el archivo int file_handle=::FileOpen(m_last_test_report_path,FILE_READ|FILE_ANSI|FILE_COMMON); //--- Salir si el archivo no se ha abierto if(file_handle==INVALID_HANDLE) return(false); //--- Liberamos el array ::ArrayFree(m_source_data); //--- Leemos el archivo al array while(!::FileIsEnding(file_handle)) { int size=::ArraySize(m_source_data); ::ArrayResize(m_source_data,size+1,RESERVE); m_source_data[size]=::FileReadString(file_handle); } //--- Cerrar el archivo ::FileClose(file_handle); return(true); }
Necesitaremos el método auxiliar CProgram::GetStartIndex() para determinar el índice de la columna con el título BALANCE. Como argumento, hay que pasarle la cadena con los encabezados donde va a realizarse la búsqueda del nombre de la columna y el array dinámico para los elementos de la cadena dividida por el separador ','.
class CProgram : public CWndEvents { private: //--- Índice inicial de los balances en el informe bool GetBalanceIndex(const string headers); }; //+---------------------------------------------------------------------------------+ //| Determinamos el índice a partir del cual hay que empezar a copiar los datos | //+---------------------------------------------------------------------------------+ bool CProgram::GetBalanceIndex(const string headers) { //--- Obtenemos los elementos de la cadena por el separador string str_elements[]; ushort u_sep=::StringGetCharacter(",",0); ::StringSplit(headers,u_sep,str_elements); //--- Buscamos la columna 'BALANCE' int elements_total=::ArraySize(str_elements); for(int e=elements_total-1; e>=0; e--) { string str=str_elements[e]; ::StringToUpper(str); //--- Si hemos encontrado la columna con el título necesario if(str=="BALANCE") { m_balance_index=e; break; } } //--- Mostrar el mensaje si la columna 'BALANCE' no ha sido encontrada if(m_balance_index==WRONG_VALUE) { ::Print(__FUNCTION__," > In the report file there is no heading \'BALANCE\' ! "); return(false); } //--- Resultado return(true); }
Los números de las transacciones van a mostrarse en el eje X de ambos gráficos. El rango de datos lo vamos a mostrar como información adicional en el título inferior del gráfico de balances. Para determinar la fecha inicial y final del historial de transacciones, ha sido realizado el método CProgram::GetDateRange(). Se pasan dos variables string por referencia para la fecha inicial y final del historial de transacciones.
class CProgram : public CWndEvents { private: //--- Rango de datos void GetDateRange(string &from_date,string &to_date); }; //+------------------------------------------------------------------+ //| Obtenemos la fecha inicial y final en el rango de prueba | //+------------------------------------------------------------------+ void CProgram::GetDateRange(string &from_date,string &to_date) { //--- Salir si hay menos de 3 cadenas int strings_total=::ArraySize(m_source_data); if(strings_total<3) return; //--- Obtenemos la fecha inicial y final del informe string str_elements[]; ushort u_sep=::StringGetCharacter(",",0); //--- ::StringSplit(m_source_data[1],u_sep,str_elements); from_date=str_elements[0]; ::StringSplit(m_source_data[strings_total-1],u_sep,str_elements); to_date=str_elements[0]; }
Para obtener los datos del balance y reducciones, se usan los métodos CProgram::GetReportDataToArray() y CProgram::AddDrawDown(). El segundo se invoca dentro del primero, y su código es muy corto (ver el código de abajo). Aquí, se traspasa el índice de la transacción y el valor de la reducción que se colocan en los arrays correspondientes, cuyos valores serán visualizados posteriormente en el gráfico. Guardamos el valor de la reducción en el array m_dd_y[], y el índice en el que debemos mostrar este valor lo guardamos en el array m_dd_x[]. De esta manera, si los índices no tienen valores, nada se visualizará en los gráficos (valores vacíos).
class CProgram : public CWndEvents { private: //--- Reducciones del balance general double m_dd_x[]; double m_dd_y[]; //--- private: //--- Añade la reducción a los arrays void AddDrawDown(const int index,const double drawdown); }; //+------------------------------------------------------------------+ //| Añade la reducción a los arrays | //+------------------------------------------------------------------+ void CProgram::AddDrawDown(const int index,const double drawdown) { int size=::ArraySize(m_dd_y); ::ArrayResize(m_dd_y,size+1,RESERVE); ::ArrayResize(m_dd_x,size+1,RESERVE); m_dd_y[size] =drawdown; m_dd_x[size] =(double)index; }
Primero, en el método CProgram::GetReportDataToArray(), se determinan los tamaños de los arrays y el número de las series para el gráfico de balances. Luego, inicializamos el array de los encabezados. Luego, se extraen cíclicamente los elementos de cada cadena por el separador, y los datos se colocan en los arrays de reducciones y balances.
class CProgram : public CWndEvents { private: //--- Obtiene los datos de los símbolos del informe int GetReportDataToArray(string &headers[]); }; //+------------------------------------------------------------------+ //| Obtiene los datos de los símbolos del informe | //+------------------------------------------------------------------+ int CProgram::GetReportDataToArray(string &headers[]) { //--- Obtenemos los elementos de la cadena de encabezados string str_elements[]; ushort u_sep=::StringGetCharacter(",",0); ::StringSplit(m_source_data[0],u_sep,str_elements); //--- Tamaños de arrays int strings_total =::ArraySize(m_source_data); int elements_total =::ArraySize(str_elements); //--- Liberamos los arrays ::ArrayFree(m_dd_y); ::ArrayFree(m_dd_x); //--- Obtenemos el número de las series int curves_total=elements_total-m_balance_index; curves_total=(curves_total<3)? 1 : curves_total; //--- Establecer el tamaño para los arrays según el número de las series ::ArrayResize(headers,curves_total); ::ArrayResize(m_symbol_balance,curves_total); //--- Establecer el tamaño para las series for(int i=0; i<curves_total; i++) ::ArrayResize(m_symbol_balance[i].m_data,strings_total,RESERVE); //--- Si hay varios símbolos (obtenemos los encabezados) if(curves_total>2) { for(int i=0,e=m_balance_index; e<elements_total; e++,i++) headers[i]=str_elements[e]; } else headers[0]=str_elements[m_balance_index]; //--- Obtenemos los datos for(int i=1; i<strings_total; i++) { ::StringSplit(m_source_data[i],u_sep,str_elements); //--- Reunimos los datos en los arrays if(str_elements[m_balance_index-1]!="") AddDrawDown(i,double(str_elements[m_balance_index-1])); //--- Si hay varios símbolos if(curves_total>2) for(int b=0,e=m_balance_index; e<elements_total; e++,b++) m_symbol_balance[b].m_data[i]=double(str_elements[e]); else m_symbol_balance[0].m_data[i]=double(str_elements[m_balance_index]); } //--- El primer valor de las series for(int i=0; i<curves_total; i++) m_symbol_balance[i].m_data[0]=(strings_total<2)? 0 : m_symbol_balance[i].m_data[1]; //--- Devolver el número de las series return(curves_total); }
En el siguiente apartado, veamos cómo se visualizan los datos obtenidos en los gráficos.
Visualización de datos en los gráficos
La llamada a los métodos auxiliares considerados en el apartado anterior va a realizarse al principio del método para la actualización del gráfico de balances CProgram::UpdateBalanceGraph(). Luego, las series actuales se eliminan del gráfico porque el número de los símbolos que participan en la última prueba puede cambiarse. Después, según el número actual de los símbolos determinado en el método CProgram::GetReportDataToArray(), añadimos cíclicamente nuevas series de datos de balances y de paso definimos el valor mínimo y máximo por el eje Y.
Aquí mismo guardamos el tamaño de las series y el paso de divisiones por el eje X en los campos de la clase. Además, vamos a necesitar estos valores para formatear el gráfico de las reducciones. Para el eje Y, se calculan los márgenes para los extremos del gráfico iguales a 5%. Al final, todos estos valores se aplican al gráfico de balances, y el gráfico se actualiza para la visualización de los últimos cambios.
class CProgram : public CWndEvents { private: //--- Total de datos en la serie double m_data_total; //--- Paso de graduación en la escala X double m_default_step; //--- private: //--- Actualiza los datos en el gráfico de balances void UpdateBalanceGraph(void); }; //+------------------------------------------------------------------+ //| Actualizar el gráfico de balances | //+------------------------------------------------------------------+ void CProgram::UpdateBalanceGraph(void) { //--- Obtenemos los datos del rango de pruebas string from_date=NULL,to_date=NULL; GetDateRange(from_date,to_date); //--- Definimos el índice desde el cual hay que empezar a copiar los datso if(!GetBalanceIndex(m_source_data[0])) return; //--- Obtienemos los datos de los símbolos del informe string headers[]; int curves_total=GetReportDataToArray(headers); //--- Actualizar todas las series del gráfico con nuevos datos CColorGenerator m_generator; CGraphic *graph=m_graph1.GetGraphicPointer(); //--- Vaciar el gráfico int total=graph.CurvesTotal(); for(int i=total-1; i>=0; i--) graph.CurveRemoveByIndex(i); //--- Máximo y mínimo del gráfico double y_max=0.0,y_min=m_symbol_balance[0].m_data[0]; //--- Añadimos los datos for(int i=0; i<curves_total; i++) { //--- Definimos el máximo/mínimo por el eje Y y_max=::fmax(y_max,m_symbol_balance[i].m_data[::ArrayMaximum(m_symbol_balance[i].m_data)]); y_min=::fmin(y_min,m_symbol_balance[i].m_data[::ArrayMinimum(m_symbol_balance[i].m_data)]); //--- Añadir la serie al gráfico CCurve *curve=graph.CurveAdd(m_symbol_balance[i].m_data,m_generator.Next(),CURVE_LINES,headers[i]); } //--- Número de valores y paso de la cuadrícula del eje X m_data_total =::ArraySize(m_symbol_balance[0].m_data)-1; m_default_step =(m_data_total<10)? 1 : ::MathFloor(m_data_total/5.0); //--- Rango y márgenes double range =::fabs(y_max-y_min); double offset =range*0.05; //--- Color para la primera serie graph.CurveGetByIndex(0).Color(::ColorToARGB(clrCornflowerBlue)); //--- Propiedades del eje horizontal CAxis *x_axis=graph.XAxis(); x_axis.AutoScale(false); x_axis.Min(0); x_axis.Max(m_data_total); x_axis.MaxGrace(0); x_axis.MinGrace(0); x_axis.DefaultStep(m_default_step); x_axis.Name(from_date+" - "+to_date); //--- Propiedades del eje vertical CAxis *y_axis=graph.YAxis(); y_axis.AutoScale(false); y_axis.Min(y_min-offset); y_axis.Max(y_max+offset); y_axis.MaxGrace(0); y_axis.MinGrace(0); y_axis.DefaultStep(range/10.0); //--- Actualizar el gráfico graph.CurvePlotAll(); graph.Update(); }
Para actualizar el gráfico de reducciones, se usa el método CProgram::UpdateDrawdownGraph(). Puesto que los datos ya han sido calculados en el método CProgram::UpdateBalanceGraph(), aquí sólo hay que aplicarlos al gráfico y actualizarlo.
class CProgram : public CWndEvents { private: //--- Actualiza los datos en el gráfico de reducciones void UpdateDrawdownGraph(void); }; //+------------------------------------------------------------------+ //| Actualizar el gráfico de reducciones | //+------------------------------------------------------------------+ void CProgram::UpdateDrawdownGraph(void) { //--- Actualizamos el gráfico de reducciones CGraphic *graph=m_graph2.GetGraphicPointer(); CCurve *curve=graph.CurveGetByIndex(0); curve.Update(m_dd_x,m_dd_y); curve.PointsFill(false); curve.PointsSize(6); curve.PointsType(POINT_CIRCLE); //--- Propiedades del eje horizontal CAxis *x_axis=graph.XAxis(); x_axis.AutoScale(false); x_axis.Min(0); x_axis.Max(m_data_total); x_axis.MaxGrace(0); x_axis.MinGrace(0); x_axis.DefaultStep(m_default_step); //--- Actualizar el gráfico graph.CalculateMaxMinValues(); graph.CurvePlotAll(); graph.Update(); }
La llamada a los métodos CProgram::UpdateBalanceGraph() y CProgram::UpdateDrawdownGraph() se realiza en el método CProgram::UpdateGraphs(). Antes de llamar a estos métodos, primero se invoca el método CProgram::ReadFileToArray() que obtiene los datos desde el archivo con los resultados de la última simulación del EA.
class CProgram : public CWndEvents { private: //--- Actualiza los datos en los gráficos de los resultados de la última prueba void UpdateGraphs(void); }; //+------------------------------------------------------------------+ //| Actualizar los gráficos | //+------------------------------------------------------------------+ void CProgram::UpdateGraphs(void) { //--- Llenamos el array con datos desde el archivo if(!ReadFileToArray()) { ::Print(__FUNCTION__," > Could not open the test results file!"); return; } //--- Actualizar el gráfico de balances y reducciones UpdateBalanceGraph(); UpdateDrawdownGraph(); }
Demostración del resultado obtenido
Para visualizar los resultados de la última prueba en los gráficos de la interfaz, hay que pulsar sólo un botón. El evento de esta acción se procesa en el método CProgram::OnEvent():
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento del clic en los botones if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- Clic en el botón 'Update data' if(lparam==m_update_graph.Id()) { //--- Actualizar gráficos UpdateGraphs(); return; } //--- return; } }
Si el EA ya ha sido simulado antes de pulsar el botón, vemos aproximadamente lo siguiente:
Fig. 3. Resultado de la última prueba del EA.
De esta manera, si el EA ha sido cargado en el gráfico, Usted tiene la posibilidad de ver inmediatamente los cambios en el gráfico de balances de multisímbolos, repasando los resultados de múltiples pruebas después de la optimización de parámetros.
Gráfico del balance de multisímbolos durante el trading y las pruebas
Ahora, vamos a examinar la segunda versión del EA, cuando el gráfico del balance de multisímbolos se dibuja y se actualiza directamente en el proceso del trading.
La interfaz gráfica se queda prácticamente igual que en la versión arriba descrita. La única diferencia consiste en que en vez del botón de actualización habrá calendario desplegable a través del cual se puede especificar a partir de que fecha hay que mostrar el resultado del trading en los gráficos.
El cambio del historial va a comprobarse en el método OnTrade() cuando surja el evento. Para comprobar que el historial se ha completado con una nueva transacción, se usa el método CProgram::IsLastDealTicket(). En este método, obtenemos el historial a partir de la hora guardada en la memoria después de la última llamada. Luego, comprobamos los tickets de la última transacción y el ticket guardado en la memoria. Si los tickets se diferencian, entonces actualizamos el ticket y la hora de la última transacción en la memoria para la siguiente comprobación, y devolvemos el indicio (true) de que el historial se ha cambiado.
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: //--- Comprobación de la nueva transacción bool IsLastDealTicket(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CProgram::CProgram(void) : m_last_deal_time(NULL), m_last_deal_ticket(WRONG_VALUE) { } //+----------------------------------------------------------------------------+ //| Devuelve el evento de la última transacción en el símbolo especificado | //+----------------------------------------------------------------------------+ bool CProgram::IsLastDealTicket(void) { //--- Salir si el historial no ha sido obtenido if(!::HistorySelect(m_last_deal_time,LONG_MAX)) return(false); //--- Obtenemos la propiedad de las transacciones en la lista obtenida int total_deals=::HistoryDealsTotal(); //--- Repasamos todas las transacciones en la lista obtenida de la última transacción a 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, avisamos de ello else { datetime deal_time=(datetime)::HistoryDealGetInteger(deal_ticket,DEAL_TIME); //--- Guardamos 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); }
Antes de repasar el historial de las transacciones y rellenar los arrays con datos, es necesario determinar qué símbolos se encuentran en el historial y cuántos son en total. Eso es necesario para el establecimiento de los tamaños de arrays. Para eso se utiliza el método CProgram::GetHistorySymbols(). Antes de su llamada, hay que seleccionar el historial en el intervalo necesario. Luego, añadimos los símbolos que encontramos en el historial a la cadena. Para que los símbolos no se repitan en la cadena, hacemos la comprobación de la presencia de la subcadena especificada. Después de eso, añadimos los símbolos encontrados en el historial al array y devolvemos el número de los símbolos.
class CProgram : public CWndEvents { private: //--- Array de los símbolos del historial string m_symbols_name[]; //--- private: //--- Obtenemos los símbolos del historial de la cuenta y devolvemos su cantidad int GetHistorySymbols(void); }; //+------------------------------------------------------------------------------+ //| Obtenemos los símbolos del historial de la cuenta y devolvemos su cantidad | //+------------------------------------------------------------------------------+ int CProgram::GetHistorySymbols(void) { string check_symbols=""; //--- Recorremos cíclicamente por primera vez y obtenemos los símbolos del trading int deals_total=::HistoryDealsTotal(); for(int i=0; i<deals_total; i++) { //--- Obtenemos el ticket de la transacción if(!m_deal_info.SelectByIndex(i)) continue; //--- Si hay nombre del símbolo if(m_deal_info.Symbol()=="") continue; //--- Si todavía no hay esta cadena, la añadimos if(::StringFind(check_symbols,m_deal_info.Symbol(),0)==-1) ::StringAdd(check_symbols,(check_symbols=="")? m_deal_info.Symbol() : ","+m_deal_info.Symbol()); } //--- Obtenemos los elementos de la cadena por el separador ushort u_sep=::StringGetCharacter(",",0); int symbols_total=::StringSplit(check_symbols,u_sep,m_symbols_name); //--- Devolvemos el número de los símbolos return(symbols_total); }
Para obtener el balance de multisímbolos, hay que llamar al método CProgram::GetHistorySymbolsBalance():
class CProgram : public CWndEvents { private: //--- Obtener el balance total y balances para cada símbolo separadamente void GetHistorySymbolsBalance(void); }; //+-------------------------------------------------------------------------+ //| Obtener el balance total y balances para cada símbolo separadamente | //+-------------------------------------------------------------------------+ void CProgram::GetHistorySymbolsBalance(void) { ... }
Aquí, primero, hay que obtener el balance inicial de la cuenta. Obtenemos el historial a partir de la primera transacción, ella representará este balance inicial. Se supone que hay posibilidad de especificar en el calendario la fecha a partir de la cual es necesario mostrar el resultado del trading. Por eso, seleccionamos el historial otra vez. Luego, usando el método CProgram::GetHistorySymbols(), obtenemos los símbolos del historial seleccionado y su cantidad, y después, establecemos los tamaños para los arrays. Para visualizar el intervalo del resultado del historial, definimos la fecha inicial y final.
... //--- Tamaño inicial del depósito ::HistorySelect(0,LONG_MAX); double balance=(m_deal_info.SelectByIndex(0))? m_deal_info.Profit() : 0; //--- Obtenemos el historial a partir de la fecha especificada ::HistorySelect(m_from_trade.SelectedDate(),LONG_MAX); //--- Obtenemos la cantidad de los símbolos int symbols_total=GetHistorySymbols(); //--- Liberamos los arrays ::ArrayFree(m_dd_x); ::ArrayFree(m_dd_y); //--- Establecemos el tamaño del array de balances según el número de los símbolos + 1 del balance total ::ArrayResize(m_symbols_balance,(symbols_total>1)? symbols_total+1 : 1); //--- Establecemos el tamaño de los arrays de transacciones para cada símbolo int deals_total=::HistoryDealsTotal(); for(int s=0; s<=symbols_total; s++) { if(symbols_total<2 && s>0) break; //--- ::ArrayResize(m_symbols_balance[s].m_data,deals_total); ::ArrayInitialize(m_symbols_balance[s].m_data,0); } //--- Número de curvas de balances int balances_total=::ArraySize(m_symbols_balance); //--- Inicio y fin del historial m_begin_date =(m_deal_info.SelectByIndex(0))? m_deal_info.Time() : m_from_trade.SelectedDate(); m_end_date =(m_deal_info.SelectByIndex(deals_total-1))? m_deal_info.Time() : ::TimeCurrent(); ...
Los balances de los símbolos y reducciones se calculan en el siguiente ciclo. Los datos obtenidos se colocan el los arrays. Para calcular la reducción, aquí también se usan los métodos descritos en los apartados anteriores.
... //--- Reducción máxima double max_drawdown=0.0; //--- Escribimos los arrays de balances en el array pasado for(int i=0; i<deals_total; i++) { //--- Obtenemos el ticket de la transacción if(!m_deal_info.SelectByIndex(i)) continue; //--- Inicialización en la primera transacción if(i==0 && m_deal_info.DealType()==DEAL_TYPE_BALANCE) balance=0; //--- A partir de la fecha especificada if(m_deal_info.Time()>=m_from_trade.SelectedDate()) { //--- Calculamos el balance total balance+=m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission(); m_symbols_balance[0].m_data[i]=balance; //--- Calculamos la reducción if(MaxDrawdownToString(i,balance,max_drawdown)!="") AddDrawDown(i,max_drawdown); } //--- Si hay más de un símbolo, escribimos los valores del balance if(symbols_total<2) continue; //--- Sólo a partir de la fecha especificada if(m_deal_info.Time()<m_from_trade.SelectedDate()) continue; //--- Repasamos todos los símbolos for(int s=1; s<balances_total; s++) { int prev_i=i-1; //--- Si el tipo de la transacción «Calculo del balance» (la primera transacción) ... if(prev_i<0 || m_deal_info.DealType()==DEAL_TYPE_BALANCE) { //--- ... el balance es el mismo para todos los símbolos m_symbols_balance[s].m_data[i]=balance; continue; } //--- Si los símbolos coinciden y el resultado de la transacción no es nulo if(m_deal_info.Symbol()==m_symbols_name[s-1] && m_deal_info.Profit()!=0) { //--- Reflejamos la transacción en el balance con este símbolo. Tomamos en cuenta el swap y la comisión. m_symbols_balance[s].m_data[i]=m_symbols_balance[s].m_data[prev_i]+m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission(); } //--- De los contrario, escribiremos el valor anterior else m_symbols_balance[s].m_data[i]=m_symbols_balance[s].m_data[prev_i]; } } ...
Los métodos CProgram::UpdateBalanceGraph() y CProgram::UpdateDrawdownGraph() sirven para añadir los datos a los gráficos y actualizarlos. Su código es prácticamente es el mismo que en la primera versión del EA considerado en los apartados anteriores, por eso pasamos directamente a la parte donde se invocan.
En primer lugar, estos métodos se invocan cuando se crea la interfaz gráfica, para que el usuario pueda ver inmediatamente el resultado del trading. Después de eso, los gráficos van a actualizarse en el método OnTrade() según vayan apareciendo los eventos comerciales.
class CProgram : public CWndEvents { private: //--- Inicialización de los gráficos void UpdateBalanceGraph(const bool update=false); void UpdateDrawdownGraph(void); }; //+------------------------------------------------------------------+ //| Evento de la operación comercial | //+------------------------------------------------------------------+ void CProgram::OnTradeEvent(void) { //--- Actualización del gráfico de balances y reducciones UpdateBalanceGraph(); UpdateDrawdownGraph(); }
Aparte de eso, usando la interfaz gráfica, el usuario puede indicar la fecha a partir de la cual es necesario construir el gráfico del balance. Para actualizar forzosamente el gráfico sin comprobar el último ticket de la transacción, hay que pasar el valor true en el método CProgram::UpdateBalanceGraph().
El evento del cambio de la fecha en el calendario (ON_CHANGE_DATE) se procesa así:
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento de la selección de la fecha en el calendario if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE) { if(lparam==m_from_trade.Id()) { UpdateBalanceGraph(true); UpdateDrawdownGraph(); m_from_trade.ChangeComboBoxCalendarState(); } //--- return; } }
A continuación, se muestra cómo funciona eso en el Probador de Estrategias en el modo de visualización:
Fig. 4. Demostración del resultado en el Probador de Estrategias en el modo de visualización.
Visualizando los informes del servicio «Señales»
Como otro complemento que puede resultar útil para los usuarios, vamos a crear el EA que permitirá visualizar los resultados del trading desde los informes en el servicio Señales.
Váyase a la página de la señal que le interesa y seleccione la pestaña Historial de transacciones:
Fig. 5. Historial de transacciones de la señal.
El enlace para la descarga del archivo CSV con el historial de transacciones se encuentra abajo de esta lista:
Fig. 6. Exportar el historial de transacciones en el archivo CSV.
Hay que colocar estos archivos para la implementación presentada en la carpeta del terminal \MQL5\Files. Añadimos un parámetro externo al EA. Va a contener el nombre del archivo-informe cuyos datos hay que visualizar en los gráficos.
//+------------------------------------------------------------------+ //| Program.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //--- Parámetros externos input string PathToFile=""; // Path to file ...
Fig. 7. Parámetro externo para especificar el archivo-informe.
La interfaz gráfica de esta versión del EA va a contener sólo dos gráficos. Cuando el EA se carga al gráfico en el terminal, intentará abrir el archivo especificado en los ajustes. Si este archivo no se encuentra, el programa mostrará un mensaje en el Registro. El conjunto de los métodos es aproximadamente el mismo que en las versiones arriba descritos. Hay algunas diferencias, pero el principio más o menos es el mismo. Vamos a analizar sólo los métodos donde el enfoque ha cambiado significativamente.
Pues bien, el archivo ha sido leído y las cadenas han sido traspasados de él al array para los datos iniciales. Ahora, hay que distribuir estos datos en el array bidimensional, tal como eso se hace en la tablas. Eso es necesario para un ordenamiento conveniente según la hora de la apertura de transacciones, desde el reciente hasta ulterior. Para eso vamos a necesitar un array de los arrays separado.
//--- Arrays para los datos desde el archivo struct CReportTable { string m_rows[]; }; //+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Tabla para el informe CReportTable m_columns[]; //--- Número de filas y columnas uint m_rows_total; uint m_columns_total; }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CProgram::CProgram(void) : m_rows_total(0), m_columns_total(0) { ... }
Para ordenar el array de los arrays, necesitaremos los siguientes métodos:
class CProgram : public CWndEvents { private: //--- Método de ordenamiento rápido void QuickSort(uint beg,uint end,uint column); //--- Comprobación de condiciones de ordenamiento bool CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction); //--- Alternar los valores en las celdas especificadas void Swap(uint r1,uint r2); };
Todos estos métodos fueron analizados detalladamente en uno de los artículos anteriores.
Todas las operaciones principales se realizan en el método CProgram::GetData(). Vamos a detallarlo. .
class CProgram : public CWndEvents { private: //--- Obtenemos los datos en los arrays int GetData(void); }; //+---------------------------------------------------------+ //| Obtiene los datos de los símbolos del informe | //+---------------------------------------------------------+ int CProgram::GetData(void) { ... }
Primero, vamos a determinar el número de las cadenas y elementos de la cadena por el separador ';'. Luego, obtenemos los nombres de los símbolos y su cantidad en el array separado que figura en el informe. Después de eso, preparamos los arrays y los llenamos con datos desde el informe.
...
//--- Obtenemos los elementos de la cadena de encabezados
string str_elements[];
ushort u_sep=::StringGetCharacter(";",0);
::StringSplit(m_source_data[0],u_sep,str_elements);
//--- Número de cadenas y elementos de la cadena
int strings_total =::ArraySize(m_source_data);
int elements_total =::ArraySize(str_elements);
//--- Obtenemos los símbolos
if((m_symbols_total=GetHistorySymbols())==WRONG_VALUE)
return;
//--- Liberamos los arrays
::ArrayFree(m_dd_y);
::ArrayFree(m_dd_x);
//--- Tamaño de las filas de datos
::ArrayResize(m_columns,elements_total);
for(int i=0; i<elements_total; i++)
::ArrayResize(m_columns[i].m_rows,strings_total-1);
//--- Llenamos los arrays con datos desde el archivo
for(int r=0; r<strings_total-1; r++)
{
::StringSplit(m_source_data[r+1],u_sep,str_elements);
for(int c=0; c<elements_total; c++)
m_columns[c].m_rows[r]=str_elements[c];
}
...
Todo esta listo para el ordenamiento de datos. Aquí hay que establecer el tamaño de los arrays de balances de los símbolos antes de llenarlos:
... //--- Número de filas y columnas m_rows_total =strings_total-1; m_columns_total =elements_total; //--- Ordenamos según la hora en la primera columna QuickSort(0,m_rows_total-1,0); //--- Tamaño de series ::ArrayResize(m_symbol_balance,m_symbols_total); for(int i=0; i<m_symbols_total; i++) ::ArrayResize(m_symbol_balance[i].m_data,m_rows_total); ...
Luego, primero llenamos el array del balance total y reducciones. Vamos a omitir todas las transacciones que pertenecen a la carga del depósito.
... //--- Balance y reducción máxima double balance =0.0; double max_drawdown =0.0; //--- Obtenemos los datos del balance total for(uint i=0; i<m_rows_total; i++) { //--- Balance inicial if(i==0) { balance+=(double)m_columns[elements_total-1].m_rows[i]; m_symbol_balance[0].m_data[i]=balance; } else { //--- Omitimos la reposición if(m_columns[1].m_rows[i]=="Balance") m_symbol_balance[0].m_data[i]=m_symbol_balance[0].m_data[i-1]; else { balance+=(double)m_columns[elements_total-1].m_rows[i]+(double)m_columns[elements_total-2].m_rows[i]+(double)m_columns[elements_total-3].m_rows[i]; m_symbol_balance[0].m_data[i]=balance; } } //--- Calculamos la reducción if(MaxDrawdownToString(i,balance,max_drawdown)!="") AddDrawDown(i,max_drawdown); } ...
Luego rellenamos los arrays de balances para cada símbolo separado.
... //--- Obtenemos los datos de los balances de los símbolos for(int s=1; s<m_symbols_total; s++) { //--- Balance inicial balance=m_symbol_balance[0].m_data[0]; m_symbol_balance[s].m_data[0]=balance; //--- for(uint r=0; r<m_rows_total; r++) { //--- Si los símbolos no coinciden, entonces el valor anterior if(m_symbols_name[s]!=m_columns[m_symbol_index].m_rows[r]) { if(r>0) m_symbol_balance[s].m_data[r]=m_symbol_balance[s].m_data[r-1]; //--- continue; } //--- Si el resultado de la transacción no es nulo if((double)m_columns[elements_total-1].m_rows[r]!=0) { balance+=(double)m_columns[elements_total-1].m_rows[r]+(double)m_columns[elements_total-2].m_rows[r]+(double)m_columns[elements_total-3].m_rows[r]; m_symbol_balance[s].m_data[r]=balance; } //--- De los contrario, escribiremos el valor anterior else m_symbol_balance[s].m_data[r]=m_symbol_balance[s].m_data[r-1]; } } ...
Después de eso, los datos se visualizan en los gráficos de la interfaz gráfica. Abajo, se muestran unos ejemplos de parte de diferentes proveedores de las señales:
Fig. 8. Demostración del resultado (ejemplo 1).
Fig. 9. Demostración del resultado (ejemplo 2).
Fig. 10. Demostración del resultado (ejemplo 3).
Fig. 11. Demostración del resultado (ejemplo 4).
Conclusión
En este artículo, se muestra la versión moderna de la aplicación MQL para ver los gráficos del balance de multisímbolos. Antes, para obtener este resultado, había que usar los programas ajenos. Ahora, se puede hacer eso sólo a través de MQL, sin tener que salir del terminal MetaTrader 5.
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. Cada versión del programa tiene la siguiente estructura de archivos:
Nombre del archivo | Comentario |
---|---|
MacdSampleMultiSymbols.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 |
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/4430
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso