
Colocando las entradas por los indicadores
Introducción
Por favor, responda a una pregunta, ¿cuando Usted observa una serie de transacciones rentables, le entran ganas de repetir su estrategia? O, tal vez, al repasar su estrategia comercial, ¿se ha puesto a pensar en cómo se puede evitar las transacciones no rentables? Creo que la mayoría de Ustedes dan una respuesta positiva por lo menos a una de estas preguntas. En este artículo, quiero proponer una técnica de distribución del historial de las transacciones por los indicadores, también contaré cómo seleccionar los indicadores que ayudarán a subir la eficacia del trading.
1. Planteamiento del problema
En el artículo anterior ya contaba sobre la construcción del Asesor Experto (EA) a base del filtro de Kalman. Durante la simulación, él mostró beneficios, pero también reveló 2 puntos débiles en la estrategia: una salida retardada y una serie de transacciones con pérdidas en el flat.
Pues bien, nuestra tarea consiste en reducir el número de transacciones con pérdidas de esta estrategia. Para eso, guardaremos los valores de la serie de indicadores en el momento de la apertura de las posiciones. Luego realizaremos el análisis y compararemos los valores de los indicadores con los resultados de las transacciones. Nos quedará sólo elegir los indicadores que ayudarán a mejorar los resultados del trading.
Primero, hagamos un plan de acciones.
- Elegimos el período de la simulación. Lo probamos y guardamos el informe.
- Analizamos sintácticamente (parsing) el informe de la simulación y creamos el array de transacciones (con resultado de operaciones).
- Aclaramos la lista de los indicadores a usar y el formato de almacenamiento de los datos. Preparamos las clases para el uso posterior.
- Preparamos los formularios de los informes para mostrar los resultados.
- Creamos el Asesor Experto analítico.
- Iniciamos el Asesor Experto analítico en el Probador de estrategias y analizamos los informes.
- Añadimos los indicadores necesarios al EA.
- Probamos el EA actualizado y comparamos los resultados.
2. La primera simulación del EA a probar
En el artículo arriba mencionado, el EA realizó 150 transacciones durante un mes. Esta cantidad no es suficiente para un análisis estadístico. Para la representación de los resultados, vamos a aumentar el período de prueba en ocho veces. Sin ninguna optimización, establecemos el período para la construcción de una función autoregresiva en 3120 barras (cerca de 3 meses) e iniciamos la prueba.
Los resultados de la simulación nos indican que hemos obtenido un gráfico del balance evidentemente con pérdidas, donde después de 1 o 2 transacciones rentables tenemos una serie de transacciones con pérdidas. En total, la parte de las transacciones rentables ha llegado a un poco menos de 34%. Aunque el tamaño medio de ganancias supera el tamaño medio de pérdidas en 45%, eso no es suficiente para obtener el benenficio durante el período completo de la simulación.
En el gráfico de precios se puede observar que cuando no hay una tendencia evidente (estando en flat), el EA abre y cierra las posiciones con pérdidas. Nuestra tarea consiste en disminuir la cantidad de estas transacciones, y si es posible, evitarlas por completo.
En primer lugar, hay que guardar el informe de la simulación para su procesamiento posterior. Pero aquí hay un momento importante: por razones de seguridad el trabajo con los archivos en el lenguaje MQL5 se encuentra bajo un estricto control. Los archivos con los que se realizan las operaciones utilizando los medios del lenguaje MQL5 deben encontrarse dentro del entorno protegido de archivos (file sandbox). Por eso, tenemos que guardar el informe dentro de este entorno. Pero puesto que vamos a iniciar el programa en el Probador de estrategias, también hay que tomar en cuenta que cada agente trabaja en su propio entorno protegido. Por tanto, para que durante la simulación en cualquier agente, el programa pueda obtener el acceso al informe, lo guardaremos en la carpeta compartida de los terminales.
Para averiguar la ruta hacia la carpeta compartida de los terminales de cliente, en MetaEditor abrimos el menú "File" y seleccionamos el submenú "Open Common Data Folder".
En la ventana que se abre, vamos a la carpeta "Files".
Luego, copiamos la línea de la ruta al portapapeles usando la combinación "Ctrl+C".
Ya sabemos la ruta hacia file sandbox, y ahora podemos guardar nuestro informe de la prueba. Para eso en "Strategy Tester" seleccionamos la pestaña "Result" y hacemos el clic derecho en cualquier lugar. En el menú que se abre, seleccionamos "Report" -> "HTML (Internet Explorer)".
Cuando ejecutemos estas operaciones, se abrirá la ventana de sistema para guardar el archivo. Primero, insertamos la ruta a nuestro file sandbox en el campo del nombre del archivo, y pulsamos «Guardar». Esta operación cambiará la carpeta para guardar el archivo.
En el siguiente paso, indicamos el nombre para guardar el informe de la prueba y guardamos el archivo.
Después de guardar el informe en file sandbox, pasamos a la siguiente fase de nuestro trabajo, a la creación del array de transacciones para el análisis posterior.
3. Creando el arrays de transacciones
3.1. Idea general sobre el análisis sintáctico
En el apartado anterior, hemos guardado el informe de simulación del EA. Ahora, procedemos a formar un array conveniente para el procesamiento a su base. Nosotros vemos la lista de transacciones en el navegador, pero los programas MQL5 no pueden cargar el array de datos desde el archivo html directamente. Por eso, hay que realizar el análisis sintáctico del informe.
En realidad, el archivo html representa un texto dividido por las etiquetas que describen su formateo y el dise;o. Al abrir el informe en un editor del texto, Usted puede encontrar fácilmente 2 etiquetas "<table>", lo que significa que todos los datos en el informe están divididos en 2 tablas de datos. La información sobre las transacciones se encuentra en la segunda tabla. La información sobre las órdenes se encuentra en su principio, le sigue la información sobre las transacciones.
Las etiquetas "<tr>...</tr>" sirven para marcar las líneas de la tabla. Para dividir la información en celdas dentro de las líneas, se usan las etiquetas "<td>...</td>".
3.2. Clase para almacenar la información sobre la transacción
Ya nos hemos aclarado con el formato de la representación de los datos en el informe. Ahora pasaremos al formato del guardado de los datos dentro de nuestro array. Puesto que el EA analizado trabaja sólo con un instrumento, se puede no guardar el nombre de este instrumento. No obstante, lo vamos a necesitar para inicializar los indicadores. Al final, la estructura de la escritura para la transacción incluirá las siguientes posiciones:
- hora de la apertura de la posición;
- volumen de la apertura de la posición;
- dirección de la transaccción;
- volumen del cierre de la posición;
- importe de la comisión;
- importe del swap;
- importe del beneficio.
Hemos aclarado los aspectos esenciales de esta fase del trabajo. Vamos a empezar a escribir el código. Priemro, crearemos la clase de la transacción CDeal.
class CDeal : public CObject { private: datetime OpenTime; // Time of open position double OpenedVolume; // Volume of opened position ENUM_POSITION_TYPE Direct; // Direct of opened position double ClosedVolume; // Closed volume double Comission; // Comission to position double Swap; // Swap of position double Profit; // Profit of position public: CDeal(); ~CDeal(); };
La inicialización de la clase va a realizarse durante la escritura de una nueva transacción abierta, cuando ya sepamos la hora de la apertura de la posición, el volumen y la dirección de la transacción. Por eso, en los parámetros de la función de la inicialización serán pasados sus valores y la comisión (si existe). Otros parámetros serán reseteados durante la inicialización. Al final, la función de la inicialización de la clase tendrá el siguiente aspecto:
CDeal::CDeal(ENUM_POSITION_TYPE type,datetime time,double volume,double comission=0.0) : ClosedVolume(0), Swap(0), Profit(0) { OpenTime = time; OpenedVolume = volume; Direct = type; Comission = comission; }
En nuestro futuro trabajo, tendremos que comprobar el estado de las transacciones guardadas. Para eso, crearemos la función IsClosed en la que vamos a comprobar si la transacción ya está cerrada en la base. Ahí vamos a comparar los volúmenes de la apertura y cierre de la transacción. Si son iguales, entonces la transacción ya está cerrada y la función devolverá true. Si la función no está cerrada, la función devolverá false y el volumen restante en el mercado.
bool CDeal::IsClosed(double &opened_volume) { opened_volume=OpenedVolume-ClosedVolume; return (opened_volume<=0); }
En el caso si necesitamos comprobar sólo el estado de la transacción y no hay necesidad de averiguar el volumen no cerrado, crearemos otra función con el mismo nombre.
bool CDeal::IsClosed(void) { double opened_volume; return IsClosed(opened_volume); }
Para cerrar correctamente una transacción, necesitamos saber su tipo. Creamos la función GetType que va a devolver el valor private de la variable Direct. Esta función es bastante corta, por eso podemos escribirla en el cuerpo de la clase.
ENUM_POSITION_TYPE Type(void) { return Direct; }
Después de comprobar el estatus, hay que completar las transacciones no cerradas. Para eso, creamos la función Close. En ella se pasan los siguientes parámetros: volumen del cierre, beneficio de la transacción, comisión y el swap acumulado. La función va a devolver false si el objeto pasado supera el volumen no cerrado de la transacción. En el resto de los casos, los parámetros pasados serán guardados en las variables correspondientes de la clase, y la función devolverá true.
bool CDeal::Close(double volume,double profit,double comission=0.0,double swap=0.0) { if((OpenedVolume-ClosedVolume)<volume) return false; ClosedVolume += volume; Profit += profit; Comission += comission; Swap += swap; return true; }
Luego, durante el análisis de las transacciones, vamos a necesitar una función que devolverá el beneficio solicitado de la transacción. La llamaremos GetProfit.
double CDeal::GetProfit(void) { return (Comission+Swap+Profit); }
Además, para obtener los datos sobre el estado de los indicadores a tiempo, necesitaremos saber la hora de la transacción. Para este propósito, crearemos la función GetTime.
datetime GetTime(void) { return OpenTime; }
3.3. Clase del análisis sintáctico del informe
Después de crear la clase para almacenar la información sobre cada transacción, pasaremos directamante al análisis sintáctico del informe (parsing). Para eso, creamos la clase CParsing. En esta clase declaramos:
- objeto de la clase CArrayObj — para almacenar el array de transacciones;
- objeto de la clase CFileTxt — para trabajar con el archivo del informe;
- variable tipo string — para almacenar el nombre del instrumento.
Aparte de las funciones de inicialización y deinicialización, la clase va a contener otras dos funciones:
- ReadFile — para el parsing
- GetSymbol — para devolver el nombre del instrumento a petición.
class CParsing { private: CArrayObj *car_Deals; //Array of deals CFileTxt *c_File; //File to parsing string s_Symbol; //Symbol of deals public: CParsing(CArrayObj *&array); ~CParsing(); bool ReadFile(string file_name); string GetSymbol(void) { return s_Symbol; } };
El objetivo principal de las funciones de esta clase consiste en crear unrray de transacciones para el procesamiento posterior. Entonces, el array creado debe estar disponible para el trabajo en el programa principal. A este fin, el objeto de la clase CArrayObj para almacenar el array de transacciones lo vamos a declarar en el programa principal, y la referencia a este objeto lo pasaremos a la clase durante la inicialización. Al final, la función de la inicialización tendrá el siguiente aspecto:
CParsing::CParsing(CArrayObj *&array) : s_Symbol(NULL) { if(CheckPointer(array)==POINTER_INVALID) { array=new CArrayObj(); } car_Deals=array; }
La eliminación del objeto de la clase CFileTxt la escribiremos en la función de la deinicialización. El cierre del archivo está escrito en la función de la deinicialización de la clase base CFile, no vamos a mostrarlo aquí:
CParsing::~CParsing() { if(CheckPointer(c_File)!=POINTER_INVALID) delete c_File; }
Pasaremos directamente al parsing. Al llamar a la función de parsing ReadFile, pasamos el nombre del informe en los parámetros. Lo primero que hacemos en la función es comprobar si el parámetro pasado está vacío. También comprobamos la presencia del array para almacenar la información sobre las transacciones. En caso de que si no se cumple por lo menos una de las condiciones, paramos la ejecución de la función y devolvemos false.
bool CParsing::ReadFile(string file_name) { //--- if(file_name==NULL || file_name=="" || CheckPointer(car_Deals)==POINTER_INVALID) return false;
Luego inicializamos el objeto de la clase CFileTxt e intentamos abrir el archivo pasado en el parámetro de la función. En caso del fallo, salimos fuera de la función con el resultado false.
if(CheckPointer(c_File)==POINTER_INVALID) { c_File=new CFileTxt(); if(CheckPointer(c_File)==POINTER_INVALID) return false; } //--- if(c_File.Open(file_name,FILE_READ|FILE_COMMON)<=0) return false;
Después de abrir el archivo, leemos su contenido en la variable tipo string. Si el archivo está vacío, salimos fuera de la función con el resultado false.
string html_report=NULL; while(!c_File.IsEnding()) html_report+=c_File.ReadString(); c_File.Close(); if(html_report==NULL || html_report=="") return false;
El siguiente paso es buscar el carácter que no figura en el texto del informe y puede ser utilizado como el separador. Si este carácter no se encuentra, salimos fuera de la función con el resultado false.
string delimiter = NULL; ushort separate = 0; for(uchar tr=1;tr<255;tr++) { string temp = CharToString(tr); if(StringFind(html_report,temp,0)>0) continue; delimiter = temp; separate = tr; break; } if(delimiter==NULL) return false;
Como ya ha sido mencionado, en la estructura de los archivos html, las tablas se cierran con la etiqueta "</table>". Reemplazamos esta etiqueta por nuestro separador, dividiendo el informe en líneas. Así, separamos la tabla requerida en una línea separada.
if(StringReplace(html_report,"</table>",delimiter)<=0) return false; //--- s_Symbol=NULL; car_Deals.Clear(); //--- string html_tables[]; int size=StringSplit(html_report,separate,html_tables); if(size<=1) return false;
Repitiendo este procedimiento con la etiqueta "</tr>", dividiremos la tabla en líneas.
if(StringReplace(html_tables[size-2],"</tr>",delimiter)<=0) return false; size=StringSplit(html_tables[size-2],separate,html_tables); if(size<=1) return false;
Ahora, procesaremos el array de las líneas obtenido cíclicamente. Primero, omitimos todas las líneas que contienen la información sobre las órdenes. En este caso, vamos a orientarnos en la línea con el texto "Deals" que separa las órdenes y las transacciones en el informe.
bool found_start=false; double opened_volume=0; for(int i=0;i<size;i++) { //--- if(!found_start) { if(StringFind(html_tables[i],"Deals",0)>=0) found_start=true; continue; }
Después de eso, dividimos cada línea en las celdas y transformamos la información en un formato corrrespondiente.
string columns[]; int temp=StringFind(html_tables[i],"<td>",0); if(temp<0) continue; if(temp>0) html_tables[i]=StringSubstr(html_tables[i],temp); StringReplace(html_tables[i],"<td>",""); StringReplace(html_tables[i],"</td>",delimiter); temp=StringSplit(html_tables[i],separate,columns); if(temp<13) continue; //--- ENUM_POSITION_TYPE e_direction = (ENUM_POSITION_TYPE)(columns[3]=="buy" ? POSITION_TYPE_BUY : columns[3]=="sell" ? POSITION_TYPE_SELL : -1); if(e_direction==-1) continue; //--- datetime dt_time = StringToTime(columns[0]); StringReplace(columns[5]," ",""); double d_volume = StringToDouble(columns[5]); StringReplace(columns[8]," ",""); double d_comission = StringToDouble(columns[8]); StringReplace(columns[9]," ",""); double d_swap = StringToDouble(columns[9]); StringReplace(columns[10]," ",""); double d_profit = StringToDouble(columns[10]); if(s_Symbol==NULL || s_Symbol=="") { s_Symbol=columns[2]; StringTrimLeft(s_Symbol); StringTrimRight(s_Symbol); }
En el siguiente paso, comprobamos si la transacción es una operación del cierre de la posición. Si el resultado es positivo, cerramos las posiciones en nuestra base según el método FIFO.
if(opened_volume>0 && StringFind(columns[4],"out",0)>=0) { int total=car_Deals.Total(); double total_volume=MathMin(opened_volume,d_volume); for(int d=0;(d<total && e_direction!=(-1) && total_volume>0);d++) { CDeal *deal=car_Deals.At(d); if(CheckPointer(deal)==POINTER_INVALID) continue; //--- if(deal.Type()==e_direction) continue; //--- double deal_unclosed=0; if(deal.IsClosed(deal_unclosed)) continue; double close_volume = MathMin(deal_unclosed,total_volume); double close_comission = d_comission/d_volume*close_volume; double close_swap = d_swap/total_volume*close_volume; double close_profit = d_profit/total_volume*close_volume; if(deal.Close(close_volume,close_profit,close_comission,close_swap)) { opened_volume -= close_volume; d_volume -= close_volume; total_volume -= close_volume; d_comission -= close_comission; d_swap -= close_swap; d_profit -= close_profit; } } }
Luego, comprobamos si la operación de la apertura de la posición ha sido realizada. Si es necesario, creamos una nueva transacción en nuestra base.
if(d_volume>0 && StringFind(columns[4],"in",0)>=0) { CDeal *deal = new CDeal(e_direction,dt_time,d_volume,d_comission); if(CheckPointer(deal)==POINTER_INVALID) return false; if(!car_Deals.Add(deal)) return false; opened_volume += d_volume; } }
Si por lo menos una transacción ha sido guardada, la función devuelve true, de lo contrario, es false.
return (car_Deals.Total()>0); }
Pasamos a la siguiente fase del trabajo.
4. Preparación de las clases para trabajar con los indicadores
Como ya hemos dicho antes, una de nuestras tareas consiste en separar las transacciones no rentables cuando no hay una tendencia evidente. La cuestión de la determinación de la tendencia se levanta de una manera regular, inclusive en esta web (por ejemplo, los artículos [3] y [4]). Yo no pretendo descubrir algunos métodos visibles de la identeficación de la tendencia. Solamente deseo proponer una tecnología de comparación de las transacciones realizadas y las indicaciones de los indicadores para el análisis posterior y una optimización asumida de los sistemas comerciales. Por eso, vamos a considerar aquí los indicadores más conocidos que ya entran en la entrega estándar del terminal.
4.1. Clase para incluir el indicador ATR
El primero será el indicador del tipo oscilador Average True Range. Como se sabe, la volatilidad del mercado se aumenta en caso de los movimientos tendenciales. Precisamente sobre ello nos avisará el crecimiento del valor del oscilador. ¿Qué valores tenderemos que guardar? Puesto que el EA analizado coloca las órdenes sólo en la apertura de la vela, yo propongo guardar el valor del indicador en la última vela cerrada, así como la relación de este valor al enterior. El primer valor mostrará la volatilidad actual, y el segundo demostrará la dinámica del cambio de la volatilidad.
Este indicador es un típico indicador de su clase con un búfer. Por eso, tiene sentido crear la única clase para trabajar con semejantes indicadores.
El enfoque de guardar los valores de los indicadores será el mismo que en el caso de las transacciones: primero, crearemos la clase para guardar los valores del indicador para una transacción, y luego crearemos la clase del nivel superior para trabajar directamente con los indicadores según las solicitudes externas y guardar los datos en el array.
La primera clase la llamaremos CValue. Va contener 3 variables private para almacenar la información sobre los valores del indicador (Value), la relación entre dos últimos valores del indicador (Dinamic) y el número del ticket de la orden para la que se guardan los valores (Deal_Ticket). El número del ticket será necesario para la comparación posterior de los valores del indicador con las órdenes durante el análisis. Todos los valores necesarios para el almacenamiento serán pasados a la instancia de la clase durante su inicialización. Para extraer la información necesaria, crearemos las funciones GetTicket, GetValue y GetDinamic, que van a devolver los valores de las variables correspondientes. Crearemos adicionalmente la función GetValues que va a devolver simultáneamente el valor del indicador y su dinámica.
class CValue : public CObject { private: double Value; //Indicator's value double Dinamic; //Dinamics value of indicator long Deal_Ticket; //Ticket of deal public: CValue(double value, double dinamic, long ticket); ~CValue(void); //--- long GetTicket(void) { return Deal_Ticket; } double GetValue(void) { return Value; } double GetDinamic(void) { return Dinamic; } void GetValues(double &value, double &dinamic); };
Luego, creamos la clase del nivel superior para almacenar el array de datos COneBufferArray. Va a contener el array de datos almacenados y el manejador (handle) del indicador en el bloque private. Recordaré que hemos decidido crear una clase universal para trabajar con los indicadores de un búfer. Pero la llamada a diferentes indicadores se acompaña con un diverso conjunto de parámetros. Por eso, según mi opinión, la opción más simple será inicializar el indicadoe en el programa principal y sólo después de eso inicializar la clase y pasarle el handle del indicador necesario. Vamos a introducir la variable s_Name para la identificación posterior del indicador en el informe.
class COneBufferArray : CObject { private: CArrayObj *IndicatorValues; //Array of indicator's values int i_handle; //Handle of indicator string s_Name; string GetIndicatorName(int handle); public: COneBufferArray(int handle); ~COneBufferArray(); //--- bool SaveNewValues(long ticket); //--- double GetValue(long ticket); double GetDinamic(long ticket); bool GetValues(long ticket, double &value, double &dinamic); int GetIndyHandle(void) { return i_handle; } string GetName(void) { return (s_Name!= NULL ? s_Name : "..."); } };
Para guardar los datos según una solicitud externa, vamos a crear la función SaveNewValues que va a contener sólo un parámetro, el ticket de la orden. Al principio de la función, comprobamos el estado de los arrays para el almacenamiento de los datos y el handle del indicador. En caso del error, la función devolverá el valor false.
bool COneBufferArray::SaveNewValues(long ticket) { if(CheckPointer(IndicatorValues)==POINTER_INVALID) return false; if(i_handle==INVALID_HANDLE) return false;
Después de eso, obtenemos los datos del indicador. Si no se puede cargar los valores del indicador, la función devolverá false.
double ind_buffer[]; if(CopyBuffer(i_handle,0,1,2,ind_buffer)<2) return false;
En el siguiente paso, creamos la instancia de la clase CValue y le pasamos los valores necesarios. En caso del error de la creación de la instancia, la función devolverá false.
CValue *object=new CValue(ind_buffer[1], (ind_buffer[0]!=0 ? ind_buffer[1]/ind_buffer[0] : 1), ticket); if(CheckPointer(object)==POINTER_INVALID) return false;
Si la clase todavía no sabe el nombre del indicador, lo obtenemos desde el gráfico llamando a la función GetIndicatorName (el código de la función se adjunta).
if(s_Name==NULL) s_Name=GetIndicatorName(i_handle);
Para terminar, añadimos la instancia recién creada de la clase de datos al array y salimos de la función, devolviendo el resultado de la operación.
return IndicatorValues.Add(object);
}
Para devolver los datos desde el array por la solicitud, creamos las funciones GetValue, GetDinamic y GetValues que van a devolver los valores necesarios por el número del ticket de la orden.
El código completo de la clase se adjunta al artículo.
He usado esta clase para la recopilación de los datos de los indicadores CCI, Volúmenes, Fuerza, oscilador de Chaikin y la desviación estándar.
4.2. Clase para incluir el indicador MACD
Vamos a añadir a nuestra colección otro indicador— MACD. Como se sabe se usa para determinar la fuerza y la dirección de la tendencia.
A diferencia de los indicadores arriba mencionados, MACD tiene 2 búferes de indicadores (Main y Signal). Por tanto, vamos a guardar la información sobre dos líneas. Usando el algoritmo arriba mencionado para los indicadores, el código de la clase para el almacenamiento de datos tendrá el siguiente aspecto:
class CMACDValue : public CObject { private: double Main_Value; //Main line value double Main_Dinamic; //Dinamics value of main lime double Signal_Value; //Signal line value double Signal_Dinamic; //Dinamics value of signal lime long Deal_Ticket; //Ticket of deal public: CMACDValue(double main_value, double main_dinamic, double signal_value, double signal_dinamic, long ticket); ~CMACDValue(void); //--- long GetTicket(void) { return Deal_Ticket; } double GetMainValue(void) { return Main_Value; } double GetMainDinamic(void) { return Main_Dinamic; } double GetSignalValue(void) { return Signal_Value; } double GetSignalDinamic(void) { return Signal_Dinamic; } void GetValues(double &main_value, double &main_dinamic, double &signal_value, double &signal_dinamic); };
Los cambios correspondientes también han ocurrido en la clase para el trabajo con el array de datos. A diferencia de la clase universal descrito en el apartado 4.1, esta clase va a trabajar con un indicador determinado, por eso, durante la inicialización de la clase, no va a recibir el handle del indicador, sino los parámetros necesarios para su inicialización. La inicialización del indicador va a realizarse directamente en la clase.
class CMACD { private: CArrayObj *IndicatorValues; //Array of indicator's values int i_handle; //Handle of indicator public: CMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price); ~CMACD(); //--- bool SaveNewValues(long ticket); //--- double GetMainValue(long ticket); double GetMainDinamic(long ticket); double GetSignalValue(long ticket); double GetSignalDinamic(long ticket); bool GetValues(long ticket, double &main_value, double &main_dinamic, double &signal_value, double &signal_dinamic); };
La lógica de las funciones es la misma, sólo se ha cambiado el número de los búferes de indicadores y las variables guardadas.
bool CMACD::SaveNewValues(long ticket) { if(CheckPointer(IndicatorValues)==POINTER_INVALID) return false; if(i_handle==INVALID_HANDLE) return false; double main[], signal[]; if(!CopyBuffer(i_handle,0,1,2,main)<2 || !CopyBuffer(i_handle,1,1,2,signal)<2) return false; CMACDValue *object=new CMACDValue(main[1], (main[0]!=0 ? main[1]/main[0] : 1), signal[1], (signal[0]!=0 ? signal[1]/signal[0] : 1), ticket); if(CheckPointer(object)==POINTER_INVALID) return false; return IndicatorValues.Add(object); }
Esta lógica de escalamiento se aplica a cualquier número de los búferes de indicadores. Pero si hay que guardar sólo unos determinados búferes de indicadores, basta con describir eso en la función SaveNewValues de la clase correspondiente. Pero no recomendaría hacer eso en esta fase, porque todavía no sabemos si hay una interdependencia entre las transacciones rentables y los valores de los determinados búferes de indicadores, y si existe, ¿cuál es su grado?
Para el mejor entendimiento del material , si se puede decir de esta manera, voy a poner otro ejemplo de almacenamiento de datos de un indicador con tres búferes de datos.
4.3. Clase para incluir el indicador ADX
El indicador ADX se usa ampliamente para determinar la fuerza y la dirección de la tendencia. Corresponde a nuestra tarea y se añade merecidamente a nuestra «hucha».
Este indicador tiene 3 búferes de indicadores, y de acuerdo con el método de escalamiento propuesto antes, aumentamos el número de las variables guardadas. De esta manera, la clase de almacenamiento de datos tiene el siguiente aspecto.
class CADXValue : public CObject { private: double ADX_Value; //ADX value double ADX_Dinamic; //Dinamics value of ADX double PDI_Value; //+DI value double PDI_Dinamic; //Dinamics value of +DI double NDI_Value; //-DIvalue double NDI_Dinamic; //Dinamics value of -DI long Deal_Ticket; //Ticket of deal public: CADXValue(double adx_value, double adx_dinamic, double pdi_value, double pdi_dinamic, double ndi_value, double ndi_dinamic, long ticket); ~CADXValue(void); //--- long GetTicket(void) { return Deal_Ticket; } double GetADXValue(void) { return ADX_Value; } double GetADXDinamic(void) { return ADX_Dinamic; } double GetPDIValue(void) { return PDI_Value; } double GetPDIDinamic(void) { return PDI_Dinamic; } double GetNDIValue(void) { return NDI_Value; } double GetNDIDinamic(void) { return NDI_Dinamic; } void GetValues(double &adx_value, double &adx_dinamic, double &pdi_value, double &pdi_dinamic, double &ndi_value, double &ndi_dinamic); };
El aumento de datos almacenados provocará el cambio en la clase del trabajo con el array.
class CADX { private: CArrayObj *IndicatorValues; //Array of indicator's values int i_handle; //Handle of indicator public: CADX(string symbol, ENUM_TIMEFRAMES timeframe, uint period); ~CADX(); //--- bool SaveNewValues(long ticket); //--- double GetADXValue(long ticket); double GetADXDinamic(long ticket); double GetPDIValue(long ticket); double GetPDIDinamic(long ticket); double GetNDIValue(long ticket); double GetNDIDinamic(long ticket); bool GetValues(long ticket,double &adx_value,double &adx_dinamic,double &pdi_value,double &pdi_dinamic,double &ndi_value,double &ndi_dinamic); }; bool CADX::SaveNewValues(long ticket) { if(CheckPointer(IndicatorValues)==POINTER_INVALID) return false; if(i_handle==INVALID_HANDLE) return false; double adx[], pdi[], ndi[]; if(!CopyBuffer(i_handle,0,1,2,adx)<2 || !CopyBuffer(i_handle,1,1,2,pdi)<2 || !CopyBuffer(i_handle,1,1,2,ndi)<2) return false; CADXValue *object=new CADXValue(adx[1], (adx[0]!=0 ? adx[1]/adx[0] : 1), pdi[1], (pdi[0]!=0 ? pdi[1]/pdi[0] : 1), ndi[1], (ndi[0]!=0 ? ndi[1]/ndi[0] : 1), ticket); if(CheckPointer(object)==POINTER_INVALID) return false; return IndicatorValues.Add(object); }
Creo que ahora el principio de la construcción de las clases para el trabajo con los indicadores está bien claro. Por eso no vamos a describir el código para los siguientes indicadores para ahorrar el volumen del artículo. De la misma manera he añadido los indicadores BW MFI y Alligator a la «hucha» para el analisis. El que quiere puede encontrar el código completo de las clases en los archivos adjuntos.
5. Preparamos los formularios de los informes para mostrar los resultados
Después de obtener la información de parte de los indicadores necesarios en el momento de la ejecución de las transacciones, ha llegado el momento para pensar en el análisis de los resultados obtenidos. Yo creo que la manera más clara será construir los gráficos de dependencia del beneficio por las transacciones a base de los valores correspondientes de los indicadores. Yo propongo construir los gráficos usando la tecnoloquía propuesta por Victor en el artículo [2].
Cabe mencionar lo siguiente: puesto que realizo la optimización de la estrategia comercial, voy a buscar las dependencias del beneficio de las indicaciones de los indicadores. Si el lector intenta repetir alguna estrategia, tiene que buscar las dependencias entre el número de transacciones y las indicaciones de los indicadores.
Primero, crearemos las clases que van a preparar la información de cada indicador.
5.1. Clase universal de los indicadores con un búfer
La primera será la clase para trabajar con los indicadores de un búfer. ¿Qué información podemos analizar? Recordaremos que hemos guardado el valor del búfer de indicadores y la dinámica de su cambio. Por tanto, podemos analizar lo siguiente:
- dependencia entre el beneficio de las operaciones realizadas y el valor del indicador en el momento de la apertura de la posición,
- influencia de la tendencia del movimiento de la línea del indicador en el beneficio,
- así como, la influencia integral del valor del indicador y su din”amica en el resultado de las operaciones realizadas.
Para dibujar los gráficos, crearemos la clase CStaticOneBuffer. Esta clase va a contener una referencia al array de los datos guardados DataArray, array de los valores del indicador Value con el paso establecido d_Step, así como, dos arrays del beneficio total separadamente para las posiciones largas y cortas. Nótese, los arrays para calcular el beneficio total serán bidimensionales. El tamaño de la primera dimensión va a corresponder al tamaño del array Value. La segunda dimensión va a contener tres elementos: el primero será para la dinámica descendiente del indicador, el segundo se usa para el moviento horizontal del indicador y el tercero servirá para el movimiento ascendiente.
Al inicializar la clase, en los parámetros vamos a pasar la referencia al array de datos y el tamaño del paso para los valores del indicador.
class CStaticOneBuffer : CObject { private: COneBufferArray *DataArray; double d_Step; //Step in values Array double Value[]; //Array of values double Long_Profit[][3]; //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2 double Short_Profit[][3]; //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2 bool AdValues(double value, double dinamic, double profit, ENUM_POSITION_TYPE type); int GetIndex(double value); bool Sort(void); public: CStaticOneBuffer(COneBufferArray *data, double step); ~CStaticOneBuffer(); bool Ad(long ticket, double profit, ENUM_POSITION_TYPE type); string HTML_header(void); string HTML_body(void); };
En la función de la inicialización, guardaremos los valores pasados y resetearemos los arrays que usamos.
CStaticOneBuffer::CStaticOneBuffer(COneBufferArray *data,double step) { DataArray = data; d_Step = step; ArrayFree(Value); ArrayFree(Long_Profit); ArrayFree(Short_Profit); }
Para recopilar la información estadística, crearemos la función Ad en la que vamos a pasar la información sobre la transacción. Dentro de la función van a ubicarse los parámetros correspondientes del indicador, y los datos van a guardarse en los elementos necesarios de los arrays.
bool CStaticOneBuffer::Ad(long ticket,double profit,ENUM_POSITION_TYPE type) { if(CheckPointer(DataArray)==POINTER_INVALID) return false; double value, dinamic; if(!DataArray.GetValues(ticket,value,dinamic)) return false; value = NormalizeDouble(value/d_Step,0)*d_Step; return AdValues(value,dinamic,profit,type); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CStaticOneBuffer::AdValues(double value,double dinamic,double profit,ENUM_POSITION_TYPE type) { int index=GetIndex(value); if(index<0) return false; switch(type) { case POSITION_TYPE_BUY: if(dinamic<1) Long_Profit[index,0]+=profit; else if(dinamic==1) Long_Profit[index,1]+=profit; else Long_Profit[index,2]+=profit; break; case POSITION_TYPE_SELL: if(dinamic<1) Short_Profit[index,0]+=profit; else if(dinamic==1) Short_Profit[index,1]+=profit; else Short_Profit[index,2]+=profit; break; } return true; }
Para visualizar los gráficos, crearemos las funciones HTML_header y HTML_body en las que van a generarse las partes del código del encabezado y del cuerpo de la página HTML. Los principios de la construcción del código de la página HTML se describen detalladamente en el artículo [2], no vamos a hacer eso aquí. El código completo de la función se adjunta al artículo.
5.2. Clase para visualizar los datos del indicador Bill Williams MFI
El siguiente indicador será Bill Williams MFI. Según la manera de visualizarse en el gráfico recuerda los indicadores con un búfer, pero hay algunas diferencias: BW MFI tiene también el búfer de la paleta de colores que también tiene importancia. Al mismo tiempo, a diferencia de los indicadores con dos búferes, no nos va a interesar la dinámica del cambio del búfer de colores. Por eso, a los gráficos de los indicadores con un búfer propuestos antes se les añadirán los gráficos de la dependencia del beneficio del color del indicador y los gráficos de la influencia integral de valores y la dinámica del indicador tomando en cuenta el color actual del indicador.
Para la recopilación de los datos estadísticos y la creación de los gráficos analíticos crearemos la clase CStaticBWMFI. La estructura de la clase es la misma que hemos considerado antes. Se han cambiado los arrays del cálculo del beneficio, ahora tienen tres dimensiones. La tercera dimensión ha obtenido 4 elementos según el número de los colores utilizados.
class CStaticBWMFI : CObject { private: CBWMFI *DataArray; double d_Step; //Step in values Array double Value[]; //Array of values double Long_Profit[][3][4]; //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2 double Short_Profit[][3][4]; //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2 bool AdValues(double value, double _color, double dinamic, double profit, ENUM_POSITION_TYPE type); int GetIndex(double value); bool Sort(void); public: CStaticBWMFI(CBWMFI *data, double step); ~CStaticBWMFI(); bool Ad(long ticket, double profit, ENUM_POSITION_TYPE type); string HTML_header(void); string HTML_body(void); };
Encontrará el código completo de la clase en los archivos adjuntos.
5.3. Clase para visualizar los datos del indicador MACD
Ahora hablaremos del indicador MACD. Como se sabe, tiene dos búferes: un histograma y una línea de señal. Según la interpretación de las señales de este indicador, el valor y la dirección de este indicador son importantes, así como la posición de la línea de señal (por encima o por debajo del histograma). Para un análisis en profundidad, tenemos que crear una serie de gráficos.
- Dependencia de la rentabilidad de las transacciones de los valores del histograma y de su dirección (de forma paralela y en conjunto).
- Dependencia de la rentabilidad de las transacciones de los valores de la línea de señal y de su dirección.
- Dependencia del beneficio de la posición de la línea de señal respecto al histograma.
- Dependencia del beneficio de la influencia de los valores del histograma, su dirección y la posición de la línea de señal respecto al histograma.
class CStaticMACD : CObject { private: CMACD *DataArray; double d_Step; //Step in values Array double Value[]; //Array of values double SignalValue[]; //Array of values double Long_Profit[][3][3]; //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2 double Short_Profit[][3][3]; //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2 double Signal_Long_Profit[][3]; //Array of long trades profit, direct -> DOWN-0, EQUAL-1, UP-2 double Signal_Short_Profit[][3]; //Array of short trades profit, direct -> DOWN-0, EQUAL-1, UP-2 bool AdValues(double main_value, double main_dinamic, double signal_value, double signal_dinamic, double profit, ENUM_POSITION_TYPE type); int GetIndex(double value); int GetSignalIndex(double value); bool Sort(void); public: CStaticMACD(CMACD *data, double step); ~CStaticMACD(); bool Ad(long ticket, double profit, ENUM_POSITION_TYPE type); string HTML_header(void); string HTML_body(void); };
Como puede ver, la estructura de la clase, el nombre y la destinación de las funciones son los mismos. Se ha cambiado sólo el contenido interno de las funciones, véase los archivos adjuntos.
5.4. Clase para visualizar los datos del indicador ADX
La clase CStaticADX será la siguiente. Va a recopilar la estadística según los valores del indicador ADX. Las reglas de la interpretación de las señales del indicador son las siguientes: +DI muestra la fuerza del movimiento positivo, -DI muestra la fuerza del movimiento negativo, ADX muestra la fuerza del movimiento medio. Vamos a construir los gráficos de las dependencias a base de estas reglas:
- la dependencia del beneficio del valor +DI, su dirección y la posición respecto a ADX;
- la dependencia del beneficio del valor -DI, su dirección y la posición respecto a ADX;
Cuando creaba la clase de la recopilación de la estadística, decidí recopilar un poco más de los datos. Como resultado, tenía que guardar los datos sobre:
- el valor del indicador;
- la dirección de la línea;
- la posición respecto a la línea del movimiento opuesto;
- la dirección de la línea del movimiento opuesto;
- la posición respecto a la línea ADX;
- la dirección de la línea ADX.
class CProfitData { public: double Value; double LongProfit[3]/*UppositePosition*/[3]/*Upposite Direct*/[3]/*ADX position*/[3]/*ADX direct*/; double ShortProfit[3]/*UppositePosition*/[3]/*Upposite Direct*/[3]/*ADX position*/[3]/*ADX direct*/; CProfitData(void) { ArrayInitialize(LongProfit,0); ArrayInitialize(ShortProfit,0); } ~CProfitData(void) {}; }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CStaticADX : CObject { private: CADX *DataArray; double d_Step; //Step in values Array CProfitData *PDI[][3]; //Array of values +DI CProfitData *NDI[][3]; //Array of values -DI bool AdValues(double adx_value, double adx_dinamic, double pdi_value, double pdi_dinamic, double ndi_value, double ndi_dinamic, double profit, ENUM_POSITION_TYPE type); int GetPDIIndex(double value); int GetNDIIndex(double value); bool Sort(void); public: CStaticADX(CADX *data, double step); ~CStaticADX(); bool Ad(long ticket, double profit, ENUM_POSITION_TYPE type); string HTML_header(void); string HTML_body(void); };
Por lo demás, fueron guardados los enfoques y los principios de la construcción de las clases anteriores. Encontrará el código completo de la clase en los archivos adjuntos.
5.5. Clase para visualizar los datos del indicador Alligator
Al final de este bloque, crearemos la clase para recopilar la estadística del indicador Alligator. Las señales de este indicador se basan en tres medias móviles de diferentes períodos. Por tanto, al interpretar las señales de este indicador, no nos importan los valores determinados de las líneas de los indicadores Lo que más nos interesa es la dirección y la posición de las líneas.
Para concretizar las señales del indicador, introduciremos la determinación de la tendencia según la posición de las líneas. Si la línea LIPS está por encima de TEETH, y TEETH es por encima de JAW, consideramos BUY como una tendencia. Si LIPS está por debajo de TEETH, y TEETH está por debajo de JAW, consideramos sell como una tendencia. En caso de la falta de un orden estricto de las líneas, consideramos que la tendencia no está definida o estamos en FLAT.
Por tanto, los gráficos de la dependencia van a construirse a base de las señales de la dirección de la tendencia y la dinámica de las líneas del indicador.
Basándose en lo dicho antes, crearemos la clase CStaticAlligator. Los principios de la construcción de la clase se heredan de las clases anteriores.
class CStaticAlligator : CObject { private: CAlligator *DataArray; double Long_Profit[3]/*Signal*/[3]/*JAW direct*/[3]/*TEETH direct*/[3]/*LIPS direct*/; //Array of long deals profit double Short_Profit[3]/*Signal*/[3]/*JAW direct*/[3]/*TEETH direct*/[3]/*LIPS direct*/; //Array of short feals profit bool AdValues(double jaw_value, double jaw_dinamic, double teeth_value, double teeth_dinamic, double lips_value, double lips_dinamic, double profit, ENUM_POSITION_TYPE type); public: CStaticAlligator(CAlligator *data); ~CStaticAlligator(); bool Ad(long ticket, double profit, ENUM_POSITION_TYPE type); string HTML_header(void); string HTML_body(void); };
Encontrará el código completo de la clase en los archivos adjuntos.
6. Construimos el Asesor Experto para recopilar y analizar la información
Ahora, cuando el trabajo preparatorio está hecho, crearemos el Asesor Experto que va a iniciarse directamente en el Probador de estrategias para la recopilación de la información y visualización de los datos analíticos. En primer lugar, en los parámetros de entrada especificamos el nombre del informe de la simulación para el análisis, el timeframe usado y todos los parámetros necesarios de los indicadores utilizados.
input string FileName = "Kalman_test.html" ; input ENUM_TIMEFRAMES Timefarame = PERIOD_CURRENT ; input string s1 = "ADX" ; //--- input uint ADX_Period = 14 ; input string s2 = "Alligator" ; //--- input uint JAW_Period = 13 ; input uint JAW_Shift = 8 ; input uint TEETH_Period = 8 ; input uint TEETH_Shift = 5 ; input uint LIPS_Period = 5 ; input uint LIPS_Shift = 3 ; input ENUM_MA_METHOD Alligator_Method = MODE_SMMA ; input ENUM_APPLIED_PRICE Alligator_Price = PRICE_MEDIAN ; input string s3 = "ATR" ; //--- input uint ATR_Period = 14 ; input string s4 = "BW MFI" ; //--- input ENUM_APPLIED_VOLUME BWMFI_Volume = VOLUME_TICK ; input string s5 = "CCI" ; //--- input uint CCI_Period = 14 ; input ENUM_APPLIED_PRICE CCI_Price = PRICE_TYPICAL ; input string s6 = "Chaikin" ; //--- input uint Ch_Fast_Period = 3 ; input uint Ch_Slow_Period = 14 ; input ENUM_MA_METHOD Ch_Method = MODE_EMA ; input ENUM_APPLIED_VOLUME Ch_Volume = VOLUME_TICK ; input string s7 = "Force Index" ; //--- input uint Force_Period = 14 ; input ENUM_MA_METHOD Force_Method = MODE_SMA ; input ENUM_APPLIED_VOLUME Force_Volume = VOLUME_TICK ; input string s8 = "MACD" ; //--- input uint MACD_Fast = 12 ; input uint MACD_Slow = 26 ; input uint MACD_Signal = 9 ; input ENUM_APPLIED_PRICE MACD_Price = PRICE_CLOSE ; input string s9 = "Standart Deviation" ; //--- input uint StdDev_Period = 14 ; input uint StdDev_Shift = 0 ; input ENUM_MA_METHOD StdDev_Method = MODE_SMA ; input ENUM_APPLIED_PRICE StdDev_Price = PRICE_CLOSE ; input string s10 = "Volumes" ; //--- input ENUM_APPLIED_VOLUME Applied_Volume = VOLUME_TICK ;
Затем объявим экземпляры всех описанных выше классов.
CArrayObj *Deals;
CADX *ADX;
CAlligator *Alligator;
COneBufferArray *ATR;
CBWMFI *BWMFI;
COneBufferArray *CCI;
COneBufferArray *Chaikin;
COneBufferArray *Force;
CMACD *MACD;
COneBufferArray *StdDev;
COneBufferArray *Volume;
CStaticOneBuffer *IndicatorsStatic[];
CStaticBWMFI *BWMFI_Stat;
CStaticMACD *MACD_Stat;
CStaticADX *ADX_Stat;
CStaticAlligator *Alligator_Stat;
6.1. La función de la inicialización del EA
Puesto que nuestro Asesor Experto está destinado para el análisis de los datos en el Probador de estrategias, primero comprobaremos el entorno en el que va a iniciarse. Si el inicio se empieza fuera del Probador, tenemos que interrumpir su inicialización.
int OnInit() { //--- if(!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION)) return INIT_FAILED;
Luego, ejecutamos el parsing de los datos desde el archivo del informe de la simulación. Después de leer los datos desde el informe, ya no necesitamos la instancia de la clase del parsing y lo eliminamos de la memoria.
CParsing *Parsing = new CParsing(Deals); if(CheckPointer(Parsing)==POINTER_INVALID) return INIT_FAILED; if(!Parsing.ReadFile(FileName) || CheckPointer(Deals)==POINTER_INVALID || Deals.Total()<=0) { delete Parsing; return INIT_FAILED; } delete Parsing;
Después de eso, realizamos la inicialización de las clases de indicadores.
//--- ADX = new CADX(_Symbol,Timefarame,ADX_Period); if(CheckPointer(ADX)==POINTER_INVALID) return INIT_FAILED; //--- Alligator = new CAlligator(_Symbol,Timefarame,JAW_Period,JAW_Shift,TEETH_Period,TEETH_Shift,LIPS_Period,LIPS_Shift,Alligator_Method,Alligator_Price); if(CheckPointer(Alligator)==POINTER_INVALID) return INIT_FAILED; //--- int handle=iATR(_Symbol,Timefarame,ATR_Period); if(handle>0) { ATR = new COneBufferArray(handle); if(CheckPointer(ATR)==POINTER_INVALID) return INIT_FAILED; } //--- BWMFI = new CBWMFI(_Symbol,Timefarame,BWMFI_Volume); if(CheckPointer(BWMFI)==POINTER_INVALID) return INIT_FAILED; //--- handle=iCCI(_Symbol,Timefarame,CCI_Period,CCI_Price); if(handle>0) { CCI = new COneBufferArray(handle); if(CheckPointer(CCI)==POINTER_INVALID) return INIT_FAILED; } //--- handle=iChaikin(_Symbol,Timefarame,Ch_Fast_Period,Ch_Slow_Period,Ch_Method,Ch_Volume); if(handle>0) { Chaikin = new COneBufferArray(handle); if(CheckPointer(Chaikin)==POINTER_INVALID) return INIT_FAILED; } //--- handle=iForce(_Symbol,Timefarame,Force_Period,Force_Method,Force_Volume); if(handle>0) { Force = new COneBufferArray(handle); if(CheckPointer(Force)==POINTER_INVALID) return INIT_FAILED; } //--- MACD = new CMACD(_Symbol,Timefarame,MACD_Fast,MACD_Slow,MACD_Signal,MACD_Price); if(CheckPointer(MACD)==POINTER_INVALID) return INIT_FAILED; //--- handle=iStdDev(_Symbol,Timefarame,StdDev_Period,StdDev_Shift,StdDev_Method,StdDev_Price); if(handle>0) { StdDev = new COneBufferArray(handle); if(CheckPointer(StdDev)==POINTER_INVALID) return INIT_FAILED; } //--- handle=iVolumes(_Symbol,Timefarame,Applied_Volume); if(handle>0) { Volume = new COneBufferArray(handle); if(CheckPointer(Volume)==POINTER_INVALID) return INIT_FAILED; }
Al final de la función OnInit, establecemos el contador de las órdenes a 0 y salimos de la función..
cur_ticket = 0; //--- return(INIT_SUCCEEDED); }
6.2. Recopilación de datos estadísticos
La recopilación de los datos va a realizarse en la función Funciones OnTick. Al principio de la función, comprobaremos si la información está recopilada por todas las órdenes. Si es así, salimos de la función.
void OnTick() { if(cur_ticket>=Deals.Total()) return;
En el siguiente paso, la hora de la ejecución de la transacción analizada se compara con la hora del tick procesado. Si la hora de la transacción todavía no ha llegado, salimos de la función.
CDeal *object = Deals.At(cur_ticket); if(object.GetTime()>TimeCurrent()) return;
Si hemos pasado las comprobaciones anteriores, comprobamos el estado de las instancias de las clases de indicadores y guardamos la información necesaria, llamando a la función SaveNewValues para cada clase de indicadores.
if(CheckPointer(ADX)!=POINTER_INVALID) ADX.SaveNewValues(cur_ticket); //--- if(CheckPointer(Alligator)!=POINTER_INVALID) Alligator.SaveNewValues(cur_ticket); //--- if(CheckPointer(ATR)!=POINTER_INVALID) ATR.SaveNewValues(cur_ticket); //--- if(CheckPointer(BWMFI)!=POINTER_INVALID) BWMFI.SaveNewValues(cur_ticket); //--- if(CheckPointer(CCI)!=POINTER_INVALID) CCI.SaveNewValues(cur_ticket); //--- if(CheckPointer(Chaikin)!=POINTER_INVALID) Chaikin.SaveNewValues(cur_ticket); //--- if(CheckPointer(Force)!=POINTER_INVALID) Force.SaveNewValues(cur_ticket); //--- if(CheckPointer(MACD)!=POINTER_INVALID) MACD.SaveNewValues(cur_ticket); //--- if(CheckPointer(StdDev)!=POINTER_INVALID) StdDev.SaveNewValues(cur_ticket); //--- if(CheckPointer(Volume)!=POINTER_INVALID) Volume.SaveNewValues(cur_ticket);
Al final de la función, aumentamos el contador de las órdenes procesadas y salimos de la función.
cur_ticket++;
return;
}
6.3. Visualización de los gráficos para el análisis
El análisis de los datos y la visualización del informe va a realizarse en la función OnTester. Al iniciar la función, comprobaremos el número de las transacciones para el análisis.
double OnTester() { double ret=0.0; int total=Deals.Total();
Si es necesario realizar el análisis, realizamos la inicialización de las clases estadísticas.
Para facilitar el procesamiento posterior, reunimos las clases estadísticas de los indicadores con un búfer en el array. Por eso, vamos a contar los indicadores con un búfer en paralelo con la inicialización.
int total_indy=0; if(total>0) { if(CheckPointer(ADX)!=POINTER_INVALID) ADX_Stat=new CStaticADX(ADX,1); //--- if(CheckPointer(Alligator)!=POINTER_INVALID) Alligator_Stat=new CStaticAlligator(Alligator); //--- if(CheckPointer(ATR)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(ATR,_Point*10); if(CheckPointer(indy)!=POINTER_INVALID) { if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } } //--- if(CheckPointer(BWMFI)!=POINTER_INVALID) BWMFI_Stat=new CStaticBWMFI(BWMFI,_Point*100); //--- if(CheckPointer(CCI)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(CCI,10); if(CheckPointer(indy)!=POINTER_INVALID) if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } //--- if(CheckPointer(Chaikin)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(Chaikin,100); if(CheckPointer(indy)!=POINTER_INVALID) if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } //--- if(CheckPointer(Force)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(Force,0.1); if(CheckPointer(indy)!=POINTER_INVALID) if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } //--- if(CheckPointer(MACD)!=POINTER_INVALID) MACD_Stat=new CStaticMACD(MACD,_Point*10); //--- if(CheckPointer(StdDev)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(StdDev,_Point*10); if(CheckPointer(indy)!=POINTER_INVALID) if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } //--- if(CheckPointer(Volume)!=POINTER_INVALID) { CStaticOneBuffer *indy=new CStaticOneBuffer(Volume,100); if(CheckPointer(indy)!=POINTER_INVALID) if(ArrayResize(IndicatorsStatic,total_indy+1)>0) { IndicatorsStatic[total_indy]=indy; total_indy++; } } }
A continuación, comparamos los datos de los indicadores con las transacciones correspondientes y agrupamos la información según las direcciones necesarias para la visualización de los informes gráficos. Para eso, en cada clase estadística, llamaremos la función Ad, psando la información sobre la transacción en sus parámetros.
for(int i=0;i<total;i++) { CDeal *deal = Deals.At(i); ENUM_POSITION_TYPE type = deal.Type(); double d_profit = deal.GetProfit(); for(int ind=0;ind<total_indy;ind++) IndicatorsStatic[ind].Ad(i,d_profit,type); if(CheckPointer(BWMFI_Stat)!=POINTER_INVALID) BWMFI_Stat.Ad(i,d_profit,type); if(CheckPointer(MACD_Stat)!=POINTER_INVALID) MACD_Stat.Ad(i,d_profit,type); if(CheckPointer(ADX_Stat)!=POINTER_INVALID) ADX_Stat.Ad(i,d_profit,type); if(CheckPointer(Alligator_Stat)!=POINTER_INVALID) Alligator_Stat.Ad(i,d_profit,type); }
Después de la agrupación de los datos, creamos el archivo del informe Report.html y lo guardamos en la carpeta compartida de los terminales.
if(total_indy>0 || CheckPointer(BWMFI_Stat)!=POINTER_INVALID || CheckPointer(MACD_Stat)!=POINTER_INVALID || CheckPointer(ADX_Stat)!=POINTER_INVALID || CheckPointer(Alligator_Stat)!=POINTER_INVALID ) { int handle=FileOpen("Report.html",FILE_WRITE|FILE_TXT|FILE_COMMON); if(handle<0) return ret;
Al principio del archivo, escribimos el encabezado de nuestro informe html.
FileWrite(handle,"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">"); FileWrite(handle,"<html> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"); FileWrite(handle,"<title>Deals to Indicators</title> <!-- - -->"); FileWrite(handle,"<script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js\" type=\"text/javascript\"></script>"); FileWrite(handle,"<script src=\"https://code.highcharts.com/highcharts.js\" type=\"text/javascript\"></script>"); FileWrite(handle,"<!-- - --> <script type=\"text/javascript\">$(document).ready(function(){");
Luego, llamando consecutivamente a la función HTML_header de todas las clases estadísticas, introducimos los datos en nuestro archivo para construir los gráficos.
for(int ind=0;ind<total_indy;ind++) FileWrite(handle,IndicatorsStatic[ind].HTML_header()); if(CheckPointer(BWMFI_Stat)!=POINTER_INVALID) FileWrite(handle,BWMFI_Stat.HTML_header()); if(CheckPointer(MACD_Stat)!=POINTER_INVALID) FileWrite(handle,MACD_Stat.HTML_header()); if(CheckPointer(ADX_Stat)!=POINTER_INVALID) FileWrite(handle,ADX_Stat.HTML_header()); if(CheckPointer(Alligator_Stat)!=POINTER_INVALID) FileWrite(handle,Alligator_Stat.HTML_header());
Después de eso, llamando consecutivamente a la función HTML_body de cada clase estadística, creamos la plantilla de la visualización del informe. Obsérvese: con la llamada a esta función nosotros terminamos el trabajo con la clase estadística y la eliminamos para limpiar la memoria.
FileWrite(handle,"});</script> <!-- - --> </head> <body>"); for(int ind=0;ind<total_indy;ind++) { FileWrite(handle,IndicatorsStatic[ind].HTML_body()); delete IndicatorsStatic[ind]; } if(CheckPointer(BWMFI_Stat)!=POINTER_INVALID) { FileWrite(handle,BWMFI_Stat.HTML_body()); delete BWMFI_Stat; } if(CheckPointer(MACD_Stat)!=POINTER_INVALID) { FileWrite(handle,MACD_Stat.HTML_body()); delete MACD_Stat; } if(CheckPointer(ADX_Stat)!=POINTER_INVALID) { FileWrite(handle,ADX_Stat.HTML_body()); delete ADX_Stat; } if(CheckPointer(Alligator_Stat)!=POINTER_INVALID) { FileWrite(handle,Alligator_Stat.HTML_body()); delete Alligator_Stat; }
Al final, escribimos las etiquetas de cierre, cerramos el archivo, limpiamos los arrays y salimos de la función.
FileWrite(handle,"</body> </html>"); FileFlush(handle); FileClose(handle); } //--- ArrayFree(IndicatorsStatic); //--- return(ret); }
No olvidamos eliminar las clases restantes en la función OnDeinit.
7. Análisis de la información
Nuestro trabajo ya se acerca a su final lógico. Ha llegado el momento para ver sus resultados. Para eso, volvemos al Probador de estrategias, repetimos ahí todos los ajustes que hemos usado en la simulación del EA analizado en el segundo apartado de este artículo, e iniciamos la prueba de nuestro EA analítico recien creado.
Después de terminar la prueba, abrimos la carpeta compartida de los terminales y buscamos el archivo Report.html. Lo abrimos en el navegador. A continuación, voy a mostrar los ejemplos desde mi informe.
7.1. ATR
Al analizar los gráficos de las dependencias del beneficio del indicador ATR, yo no veo las zonas potencialmente rentables, y por tanto, no hay posibilidad de filtrar las transacciones.
7.2. CCI.
El gráfico de la dependencia del beneficio del indicador CCI permiten conseguir un cierto beneficio con las transacciones BUY si el valor del indicador está por encima de 200 y la línea del indicador es ascendiente. Pero en las transacciones SELL no hay zonas rentables.
7.3. Chaikin
El oscilador Chaikin, igual como ATR, no ha demostrado la correlación entre las indicaciones del indicador y el beneficio de las transacciones.
7.4. Indicador de fuerza
Los gráficos analíticos del indicador de fuerza tampoco han revelado ningunas dependencias.
7.5. Desviación estándar
El análisis de las dependencias de los valores del indicador StdDev permite identificar algunas zonas rentables para las órdenes de compra, pero no hay posibilidad de la filtración de las transacciones de venta.
7.6. Indicador de volúmenes
Tampoco se puede detectar las dependencias durante el análisis del indicador de volúmenes.
7.7. Bill Williams MFI
El indicador BW MFI permite obtener beneficio durante la filtración de las transacciones de compra si están abiertas con el color 0. Pero no se puede detectar ningunas dependencias para las transacciones de venta.
7.8. MACD.
Las señales del indicador MACD dan la posibilidad de la filtración de las transacciones rentables de compra. Eso se puede hacer, si se realizan las transacciones de compra cuando la línea de señal está por encima del histograma. Pero el análisis no muestra las zonas rentables para las transacciones de venta. Al mismo tiempo, el indicador permite reducir las operaciones no rentables, excluyendo las transacciones de venta si el histograma crece y la línea de señal está por debajo o igual al histograma.
7.9. ADX
El análisis de las señales del indicador ADX no da la posibilidad de filtrar las transacciones.
7.10. Alligator
Yo creo que el uso del indicador Alligator es más fecundo para la filtración de las transacciones. Se puede encontrar los patrones para realizar las transacciones en las combinaciones de la posición y la dirección de la línea. Se puede realizar las transacciones rentables de compra si:
- la posición de las líneas del indicador muestra la tendencia para la venta y la línea LIPS o JAW da vuelta hacia arriba;
- la posición de las líneas del indicador muestra la tendencia para la compra y las líneas LIPS y TEETH apuntan hacia arriba;
- tendencia no definida y las líneas TEETH y JAW apuntan hacia abajo.
Para las transacciones de venta vamos a usar las señales de espejo.
8. Corregimos el Asesor Experto inicial
Hemos realizado un gran trabajo en cuanto al análisis de las transacciones de nuestro EA. Ahora, veremos cómo eso influirá en los resultados del trabajo de nuestra estrategia. Para eso, al módulo de las señales comerciales del artículo [1] le añadimos los indicadores con las reglas de filtración de acuerdo con el análisis arriba realizado. Yo propongo añadir MACD y Alligator a nuestro módulo.
Yo recomendaría añadir los filtros de indicadores consecutivamente y realizar cíclicamente el procedimiento de la distribución de las transacciones por los indicadores después de añadir cada filtro. Eso dará un entendimiento más claro de la influencia de cada filtro en toda la estrategia y ayudará a estimar su influencia en conjunto. Además, si en la primera etapa, el análisis no permite detectar la dependencia del beneficio de las indicaciones de algún indicador, eso no significa en absoluto que Usted no verá esta dependencia en las siguientes iteraciones. No lo hago ahora simplemente para no inflar el artículo que ya está bastante extenso
Primero, añadimos los parámetros de los indicadores a la descripción del módulo.
//| Parameter=JAW_Period,uint,13,JAW Period | //| Parameter=JAW_Shift,uint,8,JAW Shift | //| Parameter=TEETH_Period,uint,8,TEETH Period | //| Parameter=TEETH_Shift,uint,5,TEETH Shift | //| Parameter=LIPS_Period,uint,5,LIPS Period | //| Parameter=LIPS_Shift,uint,3,LIPS_Shift | //| Parameter=Alligator_Method,ENUM_MA_METHOD,MODE_SMMA,Method | //| Parameter=Alligator_Price,ENUM_APPLIED_PRICE,PRICE_MEDIAN,Alligator Price | //| Parameter=MACD_Fast,uint,12,MACD Fast | //| Parameter=MACD_Slow,uint,26,MACD Slow | //| Parameter=MACD_Signal,uint,9,MACD Signal | //| Parameter=MACD_Price,ENUM_APPLIED_PRICE,PRICE_CLOSE,MACD Price |
Por tanto, al bloque private añadimos las variables para almacenar los parámetros, y al bloque public, la función para su escritura.
uint ci_MACD_Fast; uint ci_MACD_Slow; uint ci_MACD_Signal; ENUM_APPLIED_PRICE ce_MACD_Price; uint ci_JAW_Period; uint ci_JAW_Shift; uint ci_TEETH_Period; uint ci_TEETH_Shift; uint ci_LIPS_Period; uint ci_LIPS_Shift; ENUM_MA_METHOD ce_Alligator_Method; ENUM_APPLIED_PRICE ce_Alligator_Price; void JAW_Period(uint value) { ci_JAW_Period = value; } void JAW_Shift(uint value) { ci_JAW_Shift = value; } void TEETH_Period(uint value) { ci_TEETH_Period= value; } void TEETH_Shift(uint value) { ci_TEETH_Shift = value; } void LIPS_Period(uint value) { ci_LIPS_Period = value; } void LIPS_Shift(uint value) { ci_LIPS_Shift = value; } void Alligator_Method(ENUM_MA_METHOD value) { ce_Alligator_Method = value; } void Alligator_Price(ENUM_APPLIED_PRICE value) { ce_Alligator_Price= value; } void MACD_Fast(uint value) { ci_MACD_Fast = value; } void MACD_Slow(uint value) { ci_MACD_Slow = value; } void MACD_Signal(uint value) { ci_MACD_Signal = value; } void MACD_Price(ENUM_APPLIED_PRICE value) { ce_MACD_Price = value; }
Además, hay que aregar las clases para trabajar con los indicadores y las funciones de inicialización de la obtención de los datos necesarios. He usado una clase estándar para trabajar con MACD. Puesto que para Alligator no existe una clase estándar, la he sustituido por tres clases de las medias móviles, asignándoles los nombres de acuerdo con los nombres de las líneas del indicador.
protected: CiMACD m_MACD; // object-oscillator CiMA m_JAW; CiMA m_TEETH; CiMA m_LIPS; //--- method of initialization of the indicators bool InitMACD(CIndicators *indicators); bool InitAlligator(CIndicators *indicators); //--- methods of getting data double Main(int ind) { return(m_MACD.Main(ind)); } double Signal(int ind) { return(m_MACD.Signal(ind)); } double DiffMain(int ind) { return(Main(ind+1)!=0 ? Main(ind)-Main(ind+1) : 0); } int AlligatorTrend(int ind); double DiffJaw(int ind) { return(m_JAW.Main(ind+1)!=0 ? m_JAW.Main(ind)/m_JAW.Main(ind+1) : 1); } double DiffTeeth(int ind) { return(m_TEETH.Main(ind+1)!=0 ? m_TEETH.Main(ind)/m_TEETH.Main(ind+1) : 1); } double DiffLips(int ind) { return(m_LIPS.Main(ind+1)!=0 ? m_LIPS.Main(ind)/m_LIPS.Main(ind+1) : 1); }
Durante el siguiente paso, introduciremos los cambios en la función InitIndicators para agregar nuestros indicadores en la librería del EA.
bool CSignalKalman::InitIndicators(CIndicators *indicators) { //--- initialization of indicators and timeseries of additional filters if(!CExpertSignal::InitIndicators(indicators)) return(false); //--- initialize close serias if(CheckPointer(m_close)==POINTER_INVALID) { if(!InitClose(indicators)) return false; } //--- create and initialize MACD oscilator if(!InitMACD(indicators)) return(false); //--- create and initialize Alligator if(!InitAlligator(indicators)) return(false); //--- create and initialize Kalman Filter if(CheckPointer(Kalman)==POINTER_INVALID) Kalman=new CKalman(ci_HistoryBars,ci_ShiftPeriod,m_symbol.Name(),ce_Timeframe); //--- ok return(true); }
Luego introduciremas las adiciones en las funciones de la toma de decisiones. No olvidemos que los indicadores agregados actuan como el filtro. Por eso, vamos a acceder a los indicadores sólo después de obtener la señal principal.
int CSignalKalman::LongCondition(void) { if(!CalculateIndicators()) return 0; int result=0; //--- if(cd_correction>cd_forecast) { if(Signal(1)>Main(1)) result=80; else { switch(AlligatorTrend(1)) { case 1: if(DiffLips(1)>1 && DiffTeeth(1)>1 && DiffJaw(1)<=1) result=80; break; case -1: if(DiffLips(1)>1 || DiffJaw(1)>1) result=80; break; case 0: if(DiffJaw(1)<1) { if(DiffLips(1)>1) result=80; else if(DiffTeeth(1)<1) result=80; } break; } } } return result; }
Introducimos los mismos cambios en la función ShortCondition. Encontrará el código completo de las soluciones comerciales en los archivos adjuntos.
9. Simulación del EA después de introducir los cambios
Después de introducir los cambios en el módulo de las soluciones comerciales, creamos el nuevo EA (la descripción detallada de la creación del EA con el uso del módulo de las señales comerciales se encuentra en el artículo [5]). Vamos a probar el EA recién creado con los parámetros similares a la simulación inicial del apartado 2 de este artículo.
Como han demostrado los resultados de la simulación, sin cambiar los parámetros del EA, el uso de los filtros ha permitido aumentar el factor del beneficio de 0,75 a 1,12. Es decir, hemos conseguido obtener el beneficio con los parámetros no rentables del EA inicial. Recordaré que al principio he cogido los parámetros no optimizados del EA inicial a propósito.
Conclusión
En este artículo, ha sido demostrada la tecnología de la distribución del historial de las transacciones por los indicadores, lo que ha permitido crear un sistema de los filtros a base de los indicadores estándar. En los resultados de la simulación, este sistema ha demostrado un resultado importante de la rentabilidad del EA analizado. El sistema propuesto puede aplicarse no sólo durante la optimización de un sistema comercial existente, sino durante los intentos de crear un sistema nuevo.
Referencias
- Usando el filtro de Kalman en la predicción del precio.
- Gráficos y diagramas en HTML.
- ¿Cuánto dura una tendencia?
- Distintas maneras para averiguar la tendencia en MQL5
- Examinamos en la práctica el método adaptativo del seguimiento del mercado.
Los programas usados en el artículo:
# |
Nombre |
Tipo |
Descripción |
---|---|---|---|
1 | Kalman.mqh | Librería de la clase | Clase del filtro de Kalman |
2 | SignalKalman.mqh | Librería de la clase | Módulo de las señales comerciales por el filtro de Kalman |
3 | SignalKalman+Filters.mqh | Librería de la clase | Módulo de las señales comerciales por el filtro de Kalman después de añadir los filtros de indicadores |
4 | Kalman_expert.mq5 | Asesor Experto | Asesor Experto inicial para la estrategia con el uso del filtro de Kalman |
5 | Kalman+Filters.mq5 | Asesor Experto | Asesor Experto modificado para la estrategia con el uso del filtro de Kalman |
6 | Deals_to_Indicators.mq5 | Asesor Experto | Asesor Experto para distribuir el historial de transaccioens por los indicadores |
7 | Deal.mqh | Librería de la clase | Clase para almacenar la información sobre la transacción |
8 | Parsing.mqh | Librería de la clase | Clase para el parsing del historial de las transacciones del informe de simulación |
9 | Value.mqh | Librería de la clase | Clase para guardar los datos sobre el estado del búfer de indicador |
10 | OneBufferArray.mqh | Librería de la clase | Clase para guardar el historial de datos del indicador con un búfer |
11 | StaticOneBuffer.mqh | Librería de la clase | Clase para recopilar y analizar la estadística del indicador con un búfer |
12 | ADXValue.mqh | Librería de la clase | Clase para guardar los datos sobre el estado del búfer de indicador ADX |
13 | ADX.mqh | Librería de la clase | Clase para guardar el historial de datos del indicador ADX |
14 | StaticADX.mqh | Librería de la clase | Clase para recopilar y analizar la estadística del indicador ADX |
15 | AlligatorValue.mqh | Librería de la clase | Clase para guardar los datos sobre el estado del búfer de indicador Alligator |
16 | Alligator.mqh | Librería de la clase | Clase para guardar el historial de datos del indicador Alligator |
17 | StaticAlligator.mqh | Librería de la clase | Clase para recopilar y analizar la estadística del indicador Alligator |
18 | BWMFIValue.mqh | Librería de la clase | Clase para guardar los datos sobre el estado del búfer de indicador BW MFI |
19 | BWMFI.mqh | Librería de la clase | Clase para guardar el historial de datos del indicador BW MFI |
20 | StaticBWMFI.mqh | Librería de la clase | Clase para recopilar y analizar la estadística del indicador BW MFI |
21 | MACDValue.mqh | Librería de la clase | Clase para guardar los datos sobre el estado del búfer de indicador MACD |
22 | MACD.mqh | Librería de la clase | Clase para guardar el historial de datos del indicador MACD |
23 | StaticMACD.mqh | Librería de la clase | Clase para recopilar y analizar la estadística del indicador MACD |
24 | Reports.zip | Archivo | El archivo comprimido contiene los resultados de la simulación de los EAs en el Probador de estrategias y el informe analítico. |
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/3968





- 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