![Indicador universal RSI para operar simultáneamente en dos direcciones](https://c.mql5.com/i/articles/overlay.png)
Indicador universal RSI para operar simultáneamente en dos direcciones
Contenido
- Introducción
- Eligiendo el indicador
- Modificando el indicador RSI
- Primera parte. Añadiendo los búferes de señal
- Segunda parte. Añadiendo los búferes de los contadores de señal
- Tercera parte. Aumentando el número de señales, excluyendo las omisiones
- Cuarta parte. Transfiriendo el indicador a la ventana principal del gráfico
- Determinando las barras verdaderas
- Añadiendo el indicador ATR
- Filtrando las señales en el indicador RSI
- Quinta parte. Indicador universal RSI para trabajar simultáneamente en dos direcciones
- Conclusión
Introducción
Al desarrollar algoritmos comerciales, casi siempre topamos con un problema: ¿cómo determinar dónde comienza y dónde termina la tendencia/flat? Resulta difícil encontrar una solución inequívoca. Parece que el objetivo se puede lograr combinando en un algoritmo las estrategias de ambos tipos: tendencia y flat. En este artículo, crearemos un indicador universal en el que se combinarán las señales de diferentes tipos de estrategias. También intentaremos simplificar al máximo la obtención de señales para realizar transacciones comerciales en un experto. Asimismo, mostraremos un ejemplo de combinación de varios indicadores distintos en uno. Para ello, usaremos la programación orientada a objetos, en la que cada indicador o una parte del mismo se implementa como una clase conectada al archivo principal del programa.
Por lo tanto, nuestra tarea será escribir un experto en el que se combinarán dos estrategias comerciales: una para trabajar con la tendencia, y la otra para trabajar en flat. Supongamos que el comercio simultáneo en las dos direcciones resulta más efectivo, y que dicha estrategia será más estable. Entonces, resultará cómodo conectar al experto solo un indicador en el que se generan las señales para ambos tipos de estrategias. Dentro de él, podremos implementar un sistema complejo para determinar las señales de compra y venta. También podría resultarnos necesario combinar en uno solo varios indicadores idénticos con diferentes configuraciones. O incluso incluir en un indicador dos indicadores diferentes: uno principal, y otro de filtrado (auxiliar). Estos esquemas se pueden implementar cómodamente con la ayuda de la POO.
En el esquema se muestra cómo se conectan al archivo principal del indicador (Indicator.mq5) dos clases de indicadores (CIndicator1 y CIndicator2). CIndicator2 es un indicador auxiliar, los resultados de sus cálculos son necesarios para CIndicator1. Aquí usaremos el método para determinar la barra verdadera del que ya hablamos en el artículo sobre la creación de indicadores multidivisa. Para este artículo, hemos escrito una clase aparte, CFirstTrueBar. Este se conectará a todos los indicadores para evitar el cálculo en aquellas barras que son irrelevantes para el periodo de tiempo actual.
Fig. 1. Uno de los posibles esquemas de creación de un indicador utilizando el método de POO.
Eligiendo el indicador
Para generar señales, podemos elegir cualquier indicador del paquete estándar del terminal. La esencia de la mayoría de ellas es la misma, y por lo general, una no tiene ventaja sobre otra. Algunas combinaciones de indicadores y filtros serán efectivas por un cierto periodo de tiempo, otras, durante otro.
Pero para que la investigación resulte más cómoda, será mejor elegir los indicadores de tipo oscilador. Se pueden utilizar para determinar las señales tanto de tendencia, como de flat. Según los datos del oscilador, podemos incluso construir un canal de precios. Como resultado, tendremos la oportunidad de crear un indicador universal, muy cómodo al desarrollar estrategias comerciales de cualquier nivel de complejidad.
Como ejemplo, en esta serie de artículos tomaremos el indicador RSI (Relative Strength Index). Abajo mostramos el resultado de los cálculos de este indicador con un periodo 8 en el gráfico AUDUSD, H1.
Fig. 2. Indicador Relative Strength Index.
A primera vista, parece que las señales de este indicador hacen más fácil lograr beneficios. Pero se trata de una confusión. Necesitaremos mucho trabajo antes de llegar a saber aproximadamente qué dirección seguir. En este caso, además, no hay garantías de que vayamos a lograr nuestros objetivos.
Veamos un caso más simple y obvio: pensamos que es posible obtener beneficios cuando la línea del indicador cruza los niveles predeterminados: 70/30. Si el nivel 70 es cruzado de arriba hacia abajo, estaremos ante una señal de venta. Si el nivel 30 es cruzado de abajo hacia arriba, estaremos ante una señal de compra. Pero vemos muchas señales falsas, y en este caso, además, el precio se irá en la dirección opuesta a la posición abierta.
Vamos a ver otro ejemplo de análisis de las señales de este indicador (ver fig. 3). Se ve que el precio desciende durante mucho tiempo. Con líneas rojas se marcan las señales que se forman cuando el indicador cruza el nivel 30 de abajo hacia arriba. Si en su algoritmo se ha implementado la compra basada en estas señales, entonces se encontrará con reducción flotante. Si usted coloca un stop loss cada vez que abre una posición, entonces registrá pérdidas en varias ocasiones. En este caso, además, el precio ni siquiera ha alcanzado un resultado positivo desde la última señal de compra hasta la señal de venta (línea verde). Como resultado, usted obtendrá pérdidas. Asimismo, esta parte del gráfico sugiere la posibilidad de negociar con la tendencia, es decir, no percibimos nada erróneo.
Este tipo de problemas surgen al utilizar cualquier indicador. Por ello, no importa cuál elijamos para trabajar posteriormente.
Fig. 3. Señales según el indicador RSI.
Modificando el indicador RSI
Es necesario añadir ciertos detalles al indicador elegido, para que luego resulte más cómodo a la hora de trabajar con el experto. Vamos a crear 5 versiones de RSI, moviéndonos paulatinamente de lo simple a lo complejo (para que el lector lo comprenda con mayor facilidad).
Primera parte. Añadiendo los búferes de señal
La versión estándar de RSI se encuentra en el directorio del terminal \MQL5\Indicators\Examples. Hacemos una copia suya y comenzamos a introducir cambios en ella. Añadimos dos búferes de indicador más a la lista de parámetros fijos. Su número total ahora será de 5, mientras que en el gráfico se representarán 3. Dos búferes quedarán reservados para los cálculos auxiliares. Las etiquetas de compra las representaremos en color verde (clrMediumSeaGreen), y las etiquetas de venta, en rojo (clrRed).
//--- Propiedades #property indicator_separate_window #property indicator_minimum 0 #property indicator_maximum 100 #property indicator_buffers 5 #property indicator_plots 3 #property indicator_color1 clrSteelBlue #property indicator_color2 clrMediumSeaGreen #property indicator_color3 clrRed
Determinamos los códigos de las etiquetas de las señales. Si necesitamos representar puntos, usaremos el código 159. Si necesitamos representar las señales en forma de flechas, usaremos los códigos 233 y 234, respectivamente.
//--- Flechas para las señales: 159 - puntos; 233/234 - flechas #define ARROW_BUY 159 #define ARROW_SELL 159
El cruce de los límites de las líneas del indicador puede ser tanto una señal de compra, como de venta. Por lo tanto, para el parámetro externo, vamos a necesitar una enumeración con la que sea posible especificar cómo interpretar las señales del indicador.
- Break in — ruptura de los límites dentro del intervalo. La ruptura del límite inferior de abajo hacia arriba será la señal de compra, y la ruptura del límite superior de arriba hacia abajo será la señal de venta.
- Break in reverse — ruptura de los límites dentro del intervalo (en contra del impulso). Las mismas señales que en el modo Break in, pero las condiciones de compra y venta se invierten.
- Break out — ruptura de los límites desde dentro hacia fuera del intervalo. La ruptura del límite superior de abajo hacia arriba será la señal de compra, y la ruptura del límite inferior de arriba hacia abajo será la señal de venta.
- Break out reverse — ruptura de los límites desde dentro hacia fuera del intervalo (en contra del impulso). Las mismas señales que en el modo Break out, pero las condiciones de compra y venta se invierten.
Mostraremos todos estos modos en el gráfico de abajo.
//--- Enumerando los modos de ruptura de los límites del canal enum ENUM_BREAK_INOUT { BREAK_IN =0, // Break in BREAK_IN_REVERSE =1, // Break in reverse BREAK_OUT =2, // Break out BREAK_OUT_REVERSE =3 // Break out reverse };
En total, en el indicador habrá tres parámetros externos:
- RSI Period — periodo del indcador;
- Signal Level — niveles del indicador;
- Break Mode — modo de ruptura de los niveles.
//--- Parámetros de entrada input int PeriodRSI =8; // RSI Period input double SignalLevel =30; // Signal Level input ENUM_BREAK_INOUT BreakMode =BREAK_OUT; // Mode Break
Las propiedades del indicador las establecemos en la función SetPropertiesIndicator(). Las matrices auxiliares las establecemos en último lugar. Todas las matrices de indicador se inicializan con valores cero en la función ZeroIndicatorBuffers(). A continuación, indicamos que los valores cero no se deben representar en el gráfico, es decir, estos valores estarán vacíos.
//+------------------------------------------------------------------+ //| Estableciendo las propiedades del indicador | //+------------------------------------------------------------------+ void SetPropertiesIndicator(void) { //--- Nombre corto ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS1"); //--- Dígitos decimales ::IndicatorSetInteger(INDICATOR_DIGITS,2); //--- Matrices del indicador ::SetIndexBuffer(0,rsi_buffer,INDICATOR_DATA); ::SetIndexBuffer(1,buy_buffer,INDICATOR_DATA); ::SetIndexBuffer(2,sell_buffer,INDICATOR_DATA); ::SetIndexBuffer(3,pos_buffer,INDICATOR_CALCULATIONS); ::SetIndexBuffer(4,neg_buffer,INDICATOR_CALCULATIONS); //--- Inicializando matrices ZeroIndicatorBuffers(); //--- Estableciendo las etiquetas de texto string plot_label[]={"RSI","buy","sell"}; for(int i=0; i<indicator_plots; i++) ::PlotIndexSetString(i,PLOT_LABEL,plot_label[i]); //--- Estableciendo el grosor para las matrices de indicador for(int i=0; i<indicator_plots; i++) ::PlotIndexSetInteger(i,PLOT_LINE_WIDTH,1); //--- Estebleciendo el tipo para las matrices de indicador ENUM_DRAW_TYPE draw_type[]={DRAW_LINE,DRAW_ARROW,DRAW_ARROW}; for(int i=0; i<indicator_plots; i++) ::PlotIndexSetInteger(i,PLOT_DRAW_TYPE,draw_type[i]); //--- Códigos de las etiquetas ::PlotIndexSetInteger(1,PLOT_ARROW,ARROW_BUY); ::PlotIndexSetInteger(2,PLOT_ARROW,ARROW_SELL); //--- Índice del elemento a partir del cual comienza el cálculo for(int i=0; i<indicator_plots; i++) ::PlotIndexSetInteger(i,PLOT_DRAW_BEGIN,period_rsi); //--- Número de niveles horizontales del indicador ::IndicatorSetInteger(INDICATOR_LEVELS,2); //--- Valores de los niveles horizontales del indicador up_level =100-SignalLevel; down_level =SignalLevel; ::IndicatorSetDouble(INDICATOR_LEVELVALUE,0,down_level); ::IndicatorSetDouble(INDICATOR_LEVELVALUE,1,up_level); //--- Estilo de la línea ::IndicatorSetInteger(INDICATOR_LEVELSTYLE,0,STYLE_DOT); ::IndicatorSetInteger(INDICATOR_LEVELSTYLE,1,STYLE_DOT); //--- Valor vacío para la construcción que no tiene dibujado for(int i=0; i<indicator_buffers; i++) ::PlotIndexSetDouble(i,PLOT_EMPTY_VALUE,0); //--- Desplazamiento en el eje Y if(BreakMode==BREAK_IN_REVERSE || BreakMode==BREAK_OUT_REVERSE) { ::PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,arrow_shift); ::PlotIndexSetInteger(1,PLOT_ARROW_SHIFT,-arrow_shift); } else { ::PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,-arrow_shift); ::PlotIndexSetInteger(1,PLOT_ARROW_SHIFT,arrow_shift); } }
Los cálculos preliminares y básicos de los valores del indicador RSI se muestran para mayor comodidad en las funciones aparte PreliminaryCalculations() y CalculateRSI(). Su contenido es el mismo que en el indicador RSI del paquete estándar. Vamos a analizar solo la función para determinar las señales del indicador CalculateSignals(). Aquí, verificamos en primer lugar las condiciones según el modo establecido en los parámetros externos. Luego, si se cumplen las condiciones, guardamos el valor del indicador RSI en la matriz de indicador correspondiente. De no cumplirse la condición, guardaremos los valores cero, que no se mostrarán en el gráfico.
//+------------------------------------------------------------------+ //| Calculando las señales del indicador | //+------------------------------------------------------------------+ void CalculateSignals(const int i) { bool condition1 =false; bool condition2 =false; //--- Ruptura dentro del canal if(BreakMode==BREAK_IN || BreakMode==BREAK_IN_REVERSE) { condition1 =rsi_buffer[i-1]<down_level && rsi_buffer[i]>down_level; condition2 =rsi_buffer[i-1]>up_level && rsi_buffer[i]<up_level; } else { condition1 =rsi_buffer[i-1]<up_level && rsi_buffer[i]>up_level; condition2 =rsi_buffer[i-1]>down_level && rsi_buffer[i]<down_level; } //--- Representamos las señales, si las condiciones se han cumplido if(BreakMode==BREAK_IN || BreakMode==BREAK_OUT) { buy_buffer[i] =(condition1)? rsi_buffer[i] : 0; sell_buffer[i] =(condition2)? rsi_buffer[i] : 0; } else { buy_buffer[i] =(condition2)? rsi_buffer[i] : 0; sell_buffer[i] =(condition1)? rsi_buffer[i] : 0; } }
En definitiva, el código de las funciones básicas del indicador, tales como OnInit() y OnCalculate(), tendrá un aspecto cuidado y legible:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ void OnInit(void) { //--- Comprobando los valores del parámetro externo if(PeriodRSI<1) { period_rsi=2; Print("Incorrect value for input variable PeriodRSI =",PeriodRSI, "Indicator will use value =",period_rsi,"for calculations."); } else period_rsi=PeriodRSI; //--- Estableciendo las propiedades del indicador SetPropertiesIndicator(); } //+------------------------------------------------------------------+ //| Relative Strength Index | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- Salir, si no hay suficientes datos if(rates_total<=period_rsi) return(0); //--- Cálculos preliminares PreliminaryCalculations(prev_calculated,close); //--- Ciclo principal para los cálculos for(int i=start_pos; i<rates_total && !::IsStopped(); i++) { //--- Calculando el indicador RSI CalculateRSI(i,close); //--- Calculando las señales CalculateSignals(i); } //--- Retornando el último número de elementos calculado return(rates_total); }
Cargamos este indicador en el gráfico para ver qué hemos conseguido. Abajo se muestran los resultados de los cuatro modos de trabajo (parámetro externo Break Mode).
Fig. 4. Demostración del funcionamiento del indicador RSI modificado.
Después de esta modificación del indicador RSI, ya se ve mejor cuántas señales falsas da. Tras analizar los gráficos, llegamos a la conclusión de que según este indicador, a veces es mejor comerciar en flat, y a veces, según la tendencia.
- El modo Break in ha sido pensado para comerciar en flat en la dirección de RSI.
- El modo Break in reverse ha sido pensado para comerciar según la tendencia, en contra de la dirección de RSI.
- El modo Break out ha sido pensado para comerciar en la dirección de RSI.
- El modo Break out reverse ha sido pensado para comerciar en flat en dirección contraria a RSI.
¿Es posible comerciar con las señales de este indicador solo en flat o solo según la tendencia? ¿Cómo podemos aumentar la probabilidad de obtener resultados positivos, así como la precisión de las entradas? Es muy posible que, si la posición no se abre según la primera señal en una serie continua, esto ayude a mejorar el resultado.
Segunda parte. Añadiendo los búferes de los contadores de señal
Vamos a intentar añadir indicadores intermedios, en los que mostraremos el número de señales continuas en una dirección. En cuanto llegue la señal opuesta, el contador del búfer anterior se reiniciará y se activará el contador para la serie de señales actual. Para su implementación, tendremos que completar un tanto el código.
Habrá cambios en parámetros específicos. Indicamos el nuevo número de búferes, las series para el dubujado y establecemos el color para las mismas.
//--- Propiedades ... #property indicator_buffers 7 #property indicator_plots 5 ... #property indicator_color4 clrMediumSeaGreen #property indicator_color5 clrRed
A nivel global, añadimos dos matrices adicionales para mostrar los valores de los contadores y las dos variables auxiliares correspondientes:
//--- Matrices de indicador ... double buy_counter_buffer[]; double sell_counter_buffer[]; //--- Contadores de secuencias de señales continuas int buy_counter =0; int sell_counter =0;
El resto de los cambios están relacionados con la función para determinar las señales. Aquí, si se cumplen las condiciones (aparece la siguiente señal de compra o venta), se activará el contador correspondiente, y el contador de la serie opuesta de señales se reseteará. Para evitar que el contador aumente en la barra actual, hay que omitir su activación en la última barra no formada.
//+------------------------------------------------------------------+ //| Calculando las señales del indicador | //+------------------------------------------------------------------+ void CalculateSignals(const int i,const int rates_total) { int last_index=rates_total-1; //--- bool condition1 =false; bool condition2 =false; //--- Ruptura dentro del canal if(BreakMode==BREAK_IN || BreakMode==BREAK_IN_REVERSE) { condition1 =rsi_buffer[i-1]<down_level && rsi_buffer[i]>down_level; condition2 =rsi_buffer[i-1]>up_level && rsi_buffer[i]<up_level; } else { condition1 =rsi_buffer[i-1]<up_level && rsi_buffer[i]>up_level; condition2 =rsi_buffer[i-1]>down_level && rsi_buffer[i]<down_level; } //--- Representamos las señales, si las condiciones se han cumplido if(BreakMode==BREAK_IN || BreakMode==BREAK_OUT) { buy_buffer[i] =(condition1)? rsi_buffer[i] : 0; sell_buffer[i] =(condition2)? rsi_buffer[i] : 0; //--- Contador solo de las barras formadas if(i<last_index) { if(condition1) { buy_counter++; sell_counter=0; } else if(condition2) { sell_counter++; buy_counter=0; } } } else { buy_buffer[i] =(condition2)? rsi_buffer[i] : 0; sell_buffer[i] =(condition1)? rsi_buffer[i] : 0; //--- Contador solo de las barras formadas if(i<last_index) { if(condition2) { buy_counter++; sell_counter=0; } else if(condition1) { sell_counter++; buy_counter=0; } } } //--- Corriegiendo el último valor (igual al penúltimo) if(i<last_index) { buy_counter_buffer[i] =buy_counter; sell_counter_buffer[i] =sell_counter; } else { buy_counter_buffer[i] =buy_counter_buffer[i-1]; sell_counter_buffer[i] =sell_counter_buffer[i-1]; } }
Después de introducir las adiciones, cargamos el indicador en el gráfico, para ver qué resultados hemos obtenido. Ahora el indicador RSI es más informativo. Los valores de estos contadores se pueden obtener en el experto comercial para formar las condiciones adicionales de compra y venta.
Fig. 5. Indicador RSI modificado con contadores de señales continuas unidireccionales.
Para asegurarnos de que todo funciona como habíamos pensado, vamos a probar el indicador en el simulador y en tiempo real. Resultados del funcionamiento del simulador de estrategias:
Fig. 6. Comprobación del funcionamiento del indicador RSI modificado en el simulador de estrategias.
En la siguiente captura vemos qué muestra el indicador en todos los modos del parámetro Break Mode.
Fig. 7. Indicador en todos los modos del parámetro Break Mode.
En el gráfico podemos ver las situaciones en las que el precio puede moverse una gran distancia entre dos señales unidireccionales (ver fig. 8). Esto sucede con frecuenciia, y con cualquier marco temporal. En este caso, además, se puede cambiar cuanto se desee el parámetro Signal Level, configurando los límites del intervalo. Dicha incertidumbre puede obstaculizar la creación de una lógica comercial clara o complicarla con la necesidad de escribir condiciones adicionales.
Fig. 8. Señales omitidas durante un desplazamiento de precio poco significativo
En la próxima versión eliminaremos estas omisiones, haciendo el indicador todavía más informativo.
Tercera parte. Aumentando el número de señales, excluyendo las omisiones
En esta versión, el número de búferes de indicador sigue siendo el mismo, pero deberemos añadir matrices de niveles de indicador, que se utilizarán para generar señales cuando el indicador cruce la línea.
//--- Valores de los niveles horizontales del indicador y su número double up_level =0; double down_level =0; int up_levels_total =0; int down_levels_total =0; //--- Matrices de los niveles horizontales double up_levels[]; double down_levels[];
Para que no se den las omisiones de señales sobre las que hemos hablado en el apartado anterior, estableceremos los niveles cada 5 puntos. Es decir, si en el parámetro externo Signal Level se ha establecido el nivel 30, para los niveles superiores, se calcularán estos valores: 70, 75, 80, 85, 90, 95.
Para calcular los niveles del indicador, se ha pensado la función GetLevelsIndicator(). En dos ciclos aparte, se calculan los valores de los niveles que se ubican en las matrices. La función retorna el número total de niveles.
//+------------------------------------------------------------------+ //| Retornando los niveles del indicador | //+------------------------------------------------------------------+ int GetLevelsIndicator(void) { int levels_counter=0; double level=down_level; //--- Los niveles inferiores hasta el límite inferior while(level>0 && !::IsStopped()) { int size=::ArraySize(down_levels); ::ArrayResize(down_levels,size+1); down_levels[size]=level; level-=5; levels_counter++; } level=up_level; //--- Los niveles superiores hasta el límite superior while(level<100 && !::IsStopped()) { int size=::ArraySize(up_levels); ::ArrayResize(up_levels,size+1); up_levels[size]=level; level+=5; levels_counter++; } //--- return(levels_counter); }
Los niveles se establecen en la función SetPropertiesIndicator(). Abajo mostramos su versión abreviada. Aquí, en primer lugar se calculan los niveles iniciales para los intervalos inferior y superior y se resetean las matrices de los niveles. A continuación, establecemos el número de niveles del indicador llamando la función GetLevelsIndicator(). Después de ello, establecemos a partir de las matrices los niveles ya calculados de los intervalos inferior y superior.
//+------------------------------------------------------------------+ //| Estableciendo las propiedades del indicador | //+------------------------------------------------------------------+ void SetPropertiesIndicator(void) { ... //--- Calculando los primeros niveles up_level =100-SignalLevel; down_level =SignalLevel; //--- Reseteando las matrices de los niveles ::ArrayFree(up_levels); ::ArrayFree(down_levels); //--- Número de niveles horizontales del indicador ::IndicatorSetInteger(INDICATOR_LEVELS,GetLevelsIndicator()); //--- Valores de los niveles horizontales del indicador del nivel inferior down_levels_total=::ArraySize(down_levels); for(int i=0; i<down_levels_total; i++) ::IndicatorSetDouble(INDICATOR_LEVELVALUE,i,down_levels[i]); //--- Valores de los niveles horizontales del indicador del nivel superior up_levels_total=::ArraySize(up_levels); int total=up_levels_total+down_levels_total; for(int i=down_levels_total,k=0; i<total; i++,k++) ::IndicatorSetDouble(INDICATOR_LEVELVALUE,i,up_levels[k]); ... }
Por consiguiente, debemos introducir los cambios en la función CalculateSignals(). Aquí también se muestra solo la parte modificada de la función. Para comprobar la ejecución de las condiciones en los ciclos, miramos si se cruza aunque sea uno de los niveles en las matrices.
//+------------------------------------------------------------------+ //| Calculando las señales del indicador | //+------------------------------------------------------------------+ void CalculateSignals(const int i,const int rates_total) { int last_index=rates_total-1; //--- bool condition1 =false; bool condition2 =false; //--- Ruptura dentro del canal if(BreakMode==BREAK_IN || BreakMode==BREAK_IN_REVERSE) { if(rsi_buffer[i]<50) { for(int j=0; j<down_levels_total; j++) { condition1=rsi_buffer[i-1]<down_levels[j] && rsi_buffer[i]>down_levels[j]; if(condition1) break; } } //--- if(rsi_buffer[i]>50) { for(int j=0; j<up_levels_total; j++) { condition2=rsi_buffer[i-1]>up_levels[j] && rsi_buffer[i]<up_levels[j]; if(condition2) break; } } } else { for(int j=0; j<up_levels_total; j++) { condition1=rsi_buffer[i-1]<up_levels[j] && rsi_buffer[i]>up_levels[j]; if(condition1) break; } //--- for(int j=0; j<down_levels_total; j++) { condition2=rsi_buffer[i-1]>down_levels[j] && rsi_buffer[i]<down_levels[j]; if(condition2) break; } } //--- Representamos las señales, si las condiciones se han cumplido ... //--- Corriegiendo el último valor (igual al penúltimo) ... }
En la fig. 9 se muestra el aspecto que tiene.
Fig. 9. Formando las señales al cruzarse varios niveles.
Hemos resuelto un problema, pero han surgido dos más. El primero reside en la necesidad de excluir aquellas señales en las que el precio resulta superior a la anterior compra o inferior a la anterior venta. En la fig. 10 se muestra una situación así para una serie de señales de compra: el precio en la última señal de la serie es superior al precio de la señal anterior. Esto es relevante para los modos Break in y Break out reverse, cuando debemos ceñirnos a la idea de comprar más barato y vender más caro.
Fig. 10. Situación cuando el precio de la señal es superior al precio de la señal anterior.
El segundo problema lo representan las señales demasiado frecuentes, en ocasiones en varias barras seguidas. En este caso, además, entre las señales pasa una distancia poco significativa (ver fig. 11).
Fig. 11. Acumulación de señales frecuentes con un movimiento de precio pequeño.
Nos ocuparemos de las soluciones a estos problemas en la próxima versión.
Cuarta parte. Transfiriendo el indicador a la ventana principal del gráfico
Esta versión del indicador la trasladaremos al gráfico principal. Aquí se puede ver mejor cómo funciona el indicador: veremos las señales directamente en el precio. Para controlar la distancia entre señales (en puntos), podemos simplemente establecer un valor fijo en el parámetro externo. Pero vamos a intentar hacer una variante dinámica y vincular esta magnitud al indicador de volatilidad (ATR). Para mayor comodidad, implementaremos los cálculos para los indicadores en clases aparte: CATR y CRsiPlus. Usando este método, podremos combinar cualquier número de indicadores, juntando los resultados de sus cálculos en un programa.
Determinando las barras verdaderas
Se supone que esta es la versión que usaremos en el futuro para crear un experto comercial. Por ello, para eliminar la influencia de los marcos temporales mayores en los datos históricos, cuando las barras del periodo actual no sean suficientes, determinaremos la primera barra verdadera. Ya hemos hablado de ello con detalle en el artículo sobre indicadores multidivisa. Para determinar la primera barra verdadera, escribiremos una clase aparte, CFirstTrueBar. Vamos a ver primero cómo se construye esta clase.
Las definiciones de los miembros y los métodos de la clase CFirstTrueBar se muestran abajo. Vamos a analizarlas brevemente.
//+------------------------------------------------------------------+ //| Clase para determinar la barra verdadera | //+------------------------------------------------------------------+ class CFirstTrueBar { private: //--- Hora de la barra verdadera datetime m_limit_time; //--- Número de la barra verdadera int m_limit_bar; //--- public: CFirstTrueBar(void); ~CFirstTrueBar(void); //--- Retornando (1) la hora y (2) el número de la barra verdadera datetime LimitTime(void) const { return(m_limit_time); } int LimitBar(void) const { return(m_limit_bar); } //--- Determinando la primera barra verdadera bool DetermineFirstTrueBar(void); //--- private: //--- Buscando la primera barra verdadera del periodo actual void GetFirstTrueBarTime(const datetime &time[]); };
Para buscar la barra verdadera se usa el método privado CFirstTrueBar::GetFirstTrueBarTime(). En él se debe transmitir la matriz de hora de las barras de la historia para buscar la primera barra verdadera. Iteramos desde el inicio de la matriz, y en cuanto hallamos una barra que se corresponda con el marco temporal actual, recordamos la hora y el índice de esta barra. Cuando hemos definido una barra verdadera, obtenemos su hora e índice con la ayuda de los métodos CFirstTrueBar::LimitTime() y CFirstTrueBar::LimitBar().
//+------------------------------------------------------------------+ //| Busca la primera barra verdadera del periodo actual | //+------------------------------------------------------------------+ void CFirstTrueBar::GetFirstTrueBarTime(const datetime &time[]) { //--- Obtenemos el tamaño de la matriz int array_size=::ArraySize(time); ::ArraySetAsSeries(time,false); //--- Comprobando cada barra una a una for(int i=1; i<array_size; i++) { //--- Si la barra se corresponde con el marco temporal actual if(time[i]-time[i-1]==::PeriodSeconds()) { //--- Guardando y deteniendo el ciclo m_limit_time =time[i-1]; m_limit_bar =i-1; break; } } }
El método CFirstTrueBar::GetFirstTrueBarTime() se llama en el método CFirstTrueBar::DetermineFirstTrueBar(). Precisamente aquí obtenemos la matriz de hora de las barras, según la cual buscamos la primera barra verdadera a continuación.
//+------------------------------------------------------------------+ //| Determinando la primera barra verdadera | //+------------------------------------------------------------------+ bool CFirstTrueBar::DetermineFirstTrueBar(void) { //--- Matriz de hora de las barras datetime time[]; //--- Obteniendo el número total de barras del símbolo int available_bars=::Bars(_Symbol,_Period); //--- Copiando la matriz de hora de las barras. Si no lo hemos logrado, lo intentamos de nuevo. if(::CopyTime(_Symbol,_Period,0,available_bars,time)<available_bars) return(false); //--- Obteniendo la hora de la primera barra verdadera que se corresponda con el marco temporal actual GetFirstTrueBarTime(time); return(true); }
Añadiendo el indicador ATR
El indicador ATR se calculará de la misma forma que en el paquete estándar. Tomamos el código de aquí: \MQL5\Indicators\Examples. Abajo mostramos la declaración de lo miembros y métodos de la clase CATR. La diferencia con la versión estándar radica en que aquí determinaremos la primera barra verdadera a partir de la cual comenzarán los cálculos.
Incluimos el archivo con la clase CFirstTrueBar y declaramos su ejemplar en el cuerpo de la clase CATR. Preste atención a que las matrices de indicador aquí se declaran con acceso público. Esto es necesario para que quede la posibilidad de establecerlos como búferes de indicador en el archivo principal del indicador.
//+------------------------------------------------------------------+ //| ATR.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "FirstTrueBar.mqh" //+------------------------------------------------------------------+ //| Indicador ATR | //+------------------------------------------------------------------+ class CATR { private: //--- Para determinar la primera barra verdadera CFirstTrueBar m_first_true_bar; //--- Periodo del indicador int m_period; //--- Limitador en los cálculos de los valores del indicador int m_limit; //--- public: //--- Búferes de indicador double m_tr_buffer[]; double m_atr_buffer[]; //--- public: CATR(const int period); ~CATR(void); //--- Periodo del indicador void PeriodATR(const int period) { m_period=period; } //--- Calculando el indicador ATR bool CalculateIndicatorATR(const int rates_total,const int prev_calculated,const datetime &time[],const double &close[],const double &high[],const double &low[]); //--- Reseteando los búferes de indicador void ZeroIndicatorBuffers(void); //--- private: //--- Cálculos preliminares bool PreliminaryCalc(const int rates_total,const int prev_calculated,const double &close[],const double &high[],const double &low[]); //--- Calculando ATR void CalculateATR(const int i,const datetime &time[],const double &close[],const double &high[],const double &low[]); };
La primera barra verdadera, a partir de la cual se calculará en lo sucesivo el indicador, se determina en el método para los cálculos preliminares. Si no ha sido determinada, salimos del método.
//+------------------------------------------------------------------+ //| Cálculos preliminares | //+------------------------------------------------------------------+ bool CATR::PreliminaryCalc(const int rates_total,const int prev_calculated,const double &close[],const double &high[],const double &low[]) { //--- Si el cálculo se da por primera vez o ha habido cambios if(prev_calculated==0) { //--- Definiendo el nombre de la barra verdadera m_first_true_bar.DetermineFirstTrueBar(); //--- Salimos si la barra verdadera no ha sido determinada if(m_first_true_bar.LimitBar()<0) return(false); //--- m_tr_buffer[0] =0.0; m_atr_buffer[0] =0.0; //--- Barra a partir de la cual se dará el cálculo m_limit=(::Period()<PERIOD_D1)? m_first_true_bar.LimitBar()+m_period : m_period; //--- Salir, si estamos fuera del intervalo (no hay barras suficientes) if(m_limit>=rates_total) return(false); //--- Calculando los valores del intervalo verdadero int start_pos=(m_first_true_bar.LimitBar()<1)? 1 : m_first_true_bar.LimitBar(); for(int i=start_pos; i<m_limit && !::IsStopped(); i++) m_tr_buffer[i]=::fmax(high[i],close[i-1])-::fmin(low[i],close[i-1]); //--- Los primeros valores de ATR no se calculan double first_value=0.0; for(int i=m_first_true_bar.LimitBar(); i<m_limit; i++) { m_atr_buffer[i]=0.0; first_value+=m_tr_buffer[i]; } //--- Cálculo del primer valor first_value/=m_period; m_atr_buffer[m_limit-1]=first_value; } else m_limit=prev_calculated-1; //--- return(true); }
Filtrando las señales en el indicador RSI
Aparte de los búferes de indicador calculados más arriba, en este versión de RSI habrá dos más. Se trata de niveles continuos que se construyen aparte según los precios de las señales de compra y venta, teniendo en cuenta el spread. Para que haya la posibilidad de incluir en los cálculos los datos del indicador ATR, debemos obtener el puntero al ejemplar ATR creado en el archivo principal del programa. Por eso aquí declaramos un puntero del tipo CATR y los métodos correspondientes de instalación y obtención.
Para optimizar el código, algunos de sus bloques ahora han sido implementados como métodos aparte. Se trata de la comprobación de las condiciones, el trabajo con los contadores, etc. El único método nuevo entre ellos, que no hemos visto aún, es CRsiPlus::DirectionControl(). Precisamente en este se controla la dirección del movimiento y se criban las señales según la volatilidad actual. Además, existen métodos auxiliares para eliminar las señales sobrantes CRsiPlus::DeleteBuySignal() y CRsiPlus::DeleteSellSignal().
//+------------------------------------------------------------------+ //| RSI.mqh | //| Copyright 2017, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "FirstTrueBar.mqh" #include "ATR.mqh" //--- Enumerando los modos de ruptura de canales enum ENUM_BREAK_INOUT { BREAK_IN =0, // Break in BREAK_IN_REVERSE =1, // Break in reverse BREAK_OUT =2, // Break out BREAK_OUT_REVERSE =3 // Break out reverse }; //+------------------------------------------------------------------+ //| Indicador RSI con filtro de volatilidad | //+------------------------------------------------------------------+ class CRsiPlus { private: //--- Para determinar la primera barra verdadera CFirstTrueBar m_first_true_bar; //--- Puntero a ATR CATR *m_atr; //--- Periodo del indicador int m_period; //--- Nivel de RSI double m_signal_level; //--- Modo de formación de las señales ENUM_BREAK_INOUT m_break_mode; //--- Contadores de las señales unidireccionales int m_buy_counter; int m_sell_counter; //--- Niveles de indicador double m_up_level; double m_down_level; double m_up_levels[]; double m_down_levels[]; int m_up_levels_total; int m_down_levels_total; //--- Limitador en los cálculos de los valores del indicador int m_limit; //--- Para determinar la última barra bool m_is_last_index; //--- public: //--- Búferes de indicador double m_rsi_buffer[]; double m_pos_buffer[]; double m_neg_buffer[]; //--- double m_buy_buffer[]; double m_sell_buffer[]; double m_buy_level_buffer[]; double m_sell_level_buffer[]; double m_buy_counter_buffer[]; double m_sell_counter_buffer[]; //--- public: CRsiPlus(const int period,const double signal_level,const ENUM_BREAK_INOUT break_mode); ~CRsiPlus(void) {} //--- Puntero a ATR void AtrPointer(CATR &object) { m_atr=::GetPointer(object); } CATR *AtrPointer(void) { return(::GetPointer(m_atr)); } //--- Calculando el indicador RSI bool CalculateIndicatorRSI(const int rates_total,const int prev_calculated,const double &close[],const int &spread[]); //--- Inicializando todos los búferes de indicador void ZeroIndicatorBuffers(void); //--- private: //--- Obteniendo los niveles del indicador int GetLevelsIndicator(void); //--- Cálculos preliminares bool PreliminaryCalc(const int rates_total,const int prev_calculated,const double &close[]); //--- Calculando la serie de RSI void CalculateRSI(const int i,const double &price[]); //--- Calculando las señales del indicador void CalculateSignals(const int i,const int rates_total,const double &close[],const int &spread[]); //--- Comprobando las condiciones void CheckConditions(const int i,bool &condition1,bool &condition2); //--- Comprobando los contadores void CheckCounters(bool &condition1,bool &condition2); //--- Aumentando los contadores buy y sell void IncreaseBuyCounter(const bool condition); void IncreaseSellCounter(const bool condition); //--- Control de la dirección del moviento void DirectionControl(const int i,bool &condition1,bool &condition2); //--- Eliminando las señales buy y sell sobrantes void DeleteBuySignal(const int i); void DeleteSellSignal(const int i); //--- Reseteando el elemento indicado de los búferes de indicador void ZeroIndexBuffers(const int index); };
En el método CRsiPlus::DirectionControl() se comprueban las siguientes condiciones, según las cuales se determina si la señal es sobrante:
- Si hay señal, pero el tamaño del impulso es menor que la volatilidad actual
- Si la señal formada no va en la dirección de la serie actual.
Si las condiciones se cumplen, la señal se elimina.
//+------------------------------------------------------------------+ //| Control de la dirección del movimiento | //+------------------------------------------------------------------+ void CRsiPlus::DirectionControl(const int i,bool &condition1,bool &condition2) { double atr_coeff =0.0; double impulse_size =0.0; bool atr_condition =false; //--- bool buy_condition =false; bool sell_condition =false; //--- Si "reverse" está desactivado if(m_break_mode==BREAK_IN || m_break_mode==BREAK_OUT) { buy_condition =condition1 && m_buy_counter>1; impulse_size =::fabs(m_buy_buffer[i]-m_buy_level_buffer[i-1]); atr_condition =impulse_size<m_atr.m_atr_buffer[i]; //--- if((m_buy_counter>1 && atr_condition) || (m_break_mode==BREAK_IN && buy_condition && m_buy_buffer[i]>m_buy_level_buffer[i-1]) || (m_break_mode==BREAK_OUT && buy_condition && m_buy_buffer[i]<m_buy_level_buffer[i-1])) { DeleteBuySignal(i); } //--- sell_condition =condition2 && m_sell_counter>1; impulse_size =::fabs(m_sell_buffer[i]-m_sell_level_buffer[i-1]); atr_condition =impulse_size<m_atr.m_atr_buffer[i]; //--- if((m_sell_counter>1 && atr_condition) || (m_break_mode==BREAK_IN && sell_condition && m_sell_buffer[i]<m_sell_level_buffer[i-1]) || (m_break_mode==BREAK_OUT && sell_condition && m_sell_buffer[i]>m_sell_level_buffer[i-1])) { DeleteSellSignal(i); } } //--- "Reverse" activado else { buy_condition =condition2 && m_buy_counter>1; impulse_size =::fabs(m_buy_buffer[i]-m_buy_level_buffer[i-1]); atr_condition =impulse_size<m_atr.m_atr_buffer[i]; //--- if((m_buy_counter>1 && atr_condition) || (m_break_mode==BREAK_IN_REVERSE && buy_condition && m_buy_buffer[i]<m_buy_level_buffer[i-1]) || (m_break_mode==BREAK_OUT_REVERSE && buy_condition && m_buy_buffer[i]>m_buy_level_buffer[i-1])) { DeleteBuySignal(i); } //--- sell_condition =condition1 && m_sell_counter>1; impulse_size =::fabs(m_sell_buffer[i]-m_sell_level_buffer[i-1]); atr_condition =impulse_size<m_atr.m_atr_buffer[i]; //--- if((m_sell_counter>1 && atr_condition) || (m_break_mode==BREAK_IN_REVERSE && sell_condition && m_sell_buffer[i]>m_sell_level_buffer[i-1]) || (m_break_mode==BREAK_OUT_REVERSE && sell_condition && m_sell_buffer[i]<m_sell_level_buffer[i-1])) { DeleteSellSignal(i); } } }
Ahora vamos a ver cómo está construido el archivo principal del indicador. En esta versión del indicador ya hay 11 búferes, de los cuales 7 son básicos, y 4 son auxiliares.
//--- Propiedades #property indicator_chart_window #property indicator_buffers 11 #property indicator_plots 7 #property indicator_color1 clrMediumSeaGreen #property indicator_color2 clrRed #property indicator_color5 clrMediumSeaGreen #property indicator_color6 clrRed
Para que resulte más cómodo, todos los archivos utilizados con las clases están ubicados en el directorio del indicador, en la carpeta Includes:
Fig. 12. Directorio del indicador.
Por eso, la conexión con el archivo principal tendrá el aspecto que sigue:
//--- Conectando las clases de los indicadores #include "Includes\ATR.mqh" #include "Includes\RsiPlus.mqh"
A los parámetros externos, añadimos otro más para el periodo del indicador ATR.
//--- Parámetros de entrada input int PeriodRSI =8; // RSI period input double SignalLevel =30; // Signal level input ENUM_BREAK_INOUT BreakMode =BREAK_OUT; // Break mode input int PeriodATR =200; // ATR period
Los indicadores se actualizan con la transmisión de los parámetros al constructor.
//--- Ejemplares de los indicadores para el trabajo
CATR atr(PeriodATR);
CRsiPlus rsi(PeriodRSI,SignalLevel,BreakMode);
En la función de inicialización OnInit() no debemos olvidar transmitir al indicador RSI el puntero a ATR.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ void OnInit(void) { //--- Inicializando indicadores rsi.AtrPointer(atr); //--- Estableciendo las propiedades del indicador SetPropertiesIndicator(); }
Dado que las matrices asignadas para los búferes de indicador se declaran públicamente en cada clase de los indicadores, su adición al indicador en el archivo principal parece una conexión normal de matrices dinámicas.
//+------------------------------------------------------------------+ //| Estableciendo las propiedades del indicador | //+------------------------------------------------------------------+ void SetPropertiesIndicator(void) { //--- Nombre corto ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS_CHART"); //--- Dígitos decimales ::IndicatorSetInteger(INDICATOR_DIGITS,::Digits()); //--- Búferes de indicador ::SetIndexBuffer(0,rsi.m_buy_buffer,INDICATOR_DATA); ::SetIndexBuffer(1,rsi.m_sell_buffer,INDICATOR_DATA); ::SetIndexBuffer(2,rsi.m_buy_counter_buffer,INDICATOR_DATA); ::SetIndexBuffer(3,rsi.m_sell_counter_buffer,INDICATOR_DATA); ::SetIndexBuffer(4,rsi.m_buy_level_buffer,INDICATOR_DATA); ::SetIndexBuffer(5,rsi.m_sell_level_buffer,INDICATOR_DATA); ::SetIndexBuffer(6,atr.m_atr_buffer,INDICATOR_DATA); ::SetIndexBuffer(7,rsi.m_rsi_buffer,INDICATOR_CALCULATIONS); ::SetIndexBuffer(8,rsi.m_pos_buffer,INDICATOR_CALCULATIONS); ::SetIndexBuffer(9,rsi.m_neg_buffer,INDICATOR_CALCULATIONS); ::SetIndexBuffer(10,atr.m_tr_buffer,INDICATOR_CALCULATIONS); //--- Inicializando matrices atr.ZeroIndicatorBuffers(); rsi.ZeroIndicatorBuffers(); ... }
Las matrices auxiliares usadas para los cálculos adicionales no se representan en el gráfico y en la ventana de datos Si es necesario, para que los datos se representen en la ventana de datos (pero sin verse en el gráfico), debemos establecer para estas series la propiedad DRAW_NONE.
//+------------------------------------------------------------------+ //| Estableciendo las propiedades del indicador | //+------------------------------------------------------------------+ void SetPropertiesIndicator(void) { ... //--- Estableciendo el tipo para los búferes de indicador ENUM_DRAW_TYPE draw_type[]={DRAW_ARROW,DRAW_ARROW,DRAW_NONE,DRAW_NONE,DRAW_LINE,DRAW_LINE,DRAW_NONE}; for(int i=0; i<indicator_plots; i++) ::PlotIndexSetInteger(i,PLOT_DRAW_TYPE,draw_type[i]); ... }
El contenido de la función OnCalculate() se reduce a la llamada de los dos métodos para el cálculo del indicador ATR y RSI modificado.
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- Calculando el indicador ATR if(!atr.CalculateIndicatorATR(rates_total,prev_calculated,time,close,high,low)) return(0); //--- Calculando el indicador RSI if(!rsi.CalculateIndicatorRSI(rates_total,prev_calculated,close,spread)) return(0); //--- Retornando el último número de elementos calculado return(rates_total); }
Después de compilar y cargar el indicador, en el gráfico se verá un resultado como en la fig. 13. Los niveles que se construyen según las señales del indicador forman un canal peculiar. Según el mismo, es fácil determinar la distancia que ha pasado el precio desde el la última señal opuesta de la serie anterior.
Fig. 13. Resultado del funcionamiento del indicador RSI modificado en el gráfico principal.
Al desarrollar un sistema comercial, podríamos necesitar recibir de forma visual los valores precisos de este o aquel búfer de indicador. Esto se refiere especialmente a los casos en los que en el gráfico no se ven las series de búferes de indicador cuyos valores se salen de la zona visible. Sus valores se pueden ver en la Ventana de datos. Para mayor comodidad, podemos activar el cursor en cruz con la ruleta del ratón. En la fig. 14 se ha añadido al gráfico el indicador ATR del paquete estándar, para que sea posible comparar con lo calculado en el RSI modificado.
Fig. 14. Visualización de valores en la ventana de datos del indicador.
Quinta parte. Indicador universal RSI para trabajar simultáneamente en dos direcciones
¿Y qué sucede si necesitamos un indicador que muestre las señales simultáneamente en dos direcciones? Y es que en MetaTrader 5 existe la posibilidad de abrir cuentas con cobertura, y esto significa que podemos desarrollar un sistema con posiciones en direcciones diferentes. Aquí necesitaríamos un indicador que diese a la vez señales según la tendencia y según el flat.
Vamos a analizar brevemente cómo crear un indicador así. Ya tenemos todo lo necesario, solo tendremos cambios en el archivo principal. En esta versión habrá un total de 20 búferes, de los cuales 15 los usaremos para el dibujado.
#property indicator_buffers 20 #property indicator_plots 15
Las señales según la tendencia las representaremos en forma de flechas, y para la tendencia, como puntos. Así resultará visualmente más comprensible a qué tipo pertenece esta u otra señal.
//--- Flechas para las señales: 159 - puntos; 233/234 - flechas; #define ARROW_BUY_IN 233 #define ARROW_SELL_IN 234 #define ARROW_BUY_OUT 159 #define ARROW_SELL_OUT 159
Conectaremos los mismos archivos que en la versión anterior, y también en el directorio local del indicador. Como consecuencia, podremos ubicar la única copia de estos archivos donde nos sea más cómodo.
//--- Conectando las clases de los indicadores #include "Includes\ATR.mqh" #include "Includes\RsiPlus.mqh"
En los parámetros externos de esta versión del indicador no necesitamos indicar el tipo de señales. Pero vamos a hacer que resulte posible indicar los niveles para las señales según la tendencia (Signal level In) y flat por separado (Signal level Out).
//--- Parámetros de entrada input int PeriodRSI =8; // RSI period input double SignalLevelIn =35; // Signal level In input double SignalLevelOut =30; // Signal level Out input int PeriodATR =100; // ATR period
A continuación, necesitamos dos ejemplares de la clase CRsiPlus: uno para las señales de tendencia, y el segundo para las señales de flat. En ambos casos, se usan las señales de tipo "reverse" (BREAK_IN_REVERSE y BREAK_OUT_REVERSE). Es decir, para el flat, la señal será un impulso desde el canal, mientras que para la tendencia, la entrada se dará en el sentido de la tendencia después del retroceso.
//--- Ejemplares de los indicadores para el trabajo CATR atr(PeriodATR); CRsiPlus rsi_in(PeriodRSI,SignalLevelIn,BREAK_IN_REVERSE); CRsiPlus rsi_out(PeriodRSI,SignalLevelOut,BREAK_OUT_REVERSE);
El puntero al indicador ATR lo debemos transmitir a ambos ejemplares de RSI:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ void OnInit(void) { //--- Transmitiendo el puntero a ATR rsi_in.AtrPointer(atr); rsi_out.AtrPointer(atr); //--- Estableciendo las propiedades del indicador SetPropertiesIndicator(); }
Los búferes de indicador para cada ejemplar están instalados de tal forma que resulte más sencillo orientarse en el código. El orden, por supuesto, tiene su importancia. Usted debe saber según qué número de búfer podrá obtener después en el experto comercial los valores de esta o aquella serie para formar las condiciones.
//+------------------------------------------------------------------+ //| Estableciendo las propiedades del indicador | //+------------------------------------------------------------------+ void SetPropertiesIndicator(void) { //--- Nombre corto ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS2_CHART"); //--- Dígitos decimales ::IndicatorSetInteger(INDICATOR_DIGITS,::Digits()); //--- Búferes de indicador ::SetIndexBuffer(0,rsi_in.m_buy_buffer,INDICATOR_DATA); ::SetIndexBuffer(1,rsi_in.m_sell_buffer,INDICATOR_DATA); ::SetIndexBuffer(2,rsi_in.m_buy_counter_buffer,INDICATOR_DATA); ::SetIndexBuffer(3,rsi_in.m_sell_counter_buffer,INDICATOR_DATA); ::SetIndexBuffer(4,rsi_in.m_buy_level_buffer,INDICATOR_DATA); ::SetIndexBuffer(5,rsi_in.m_sell_level_buffer,INDICATOR_DATA); ::SetIndexBuffer(6,rsi_in.m_rsi_buffer,INDICATOR_CALCULATIONS); ::SetIndexBuffer(7,rsi_in.m_pos_buffer,INDICATOR_CALCULATIONS); ::SetIndexBuffer(8,rsi_in.m_neg_buffer,INDICATOR_CALCULATIONS); //--- ::SetIndexBuffer(9,rsi_out.m_buy_buffer,INDICATOR_DATA); ::SetIndexBuffer(10,rsi_out.m_sell_buffer,INDICATOR_DATA); ::SetIndexBuffer(11,rsi_out.m_buy_counter_buffer,INDICATOR_DATA); ::SetIndexBuffer(12,rsi_out.m_sell_counter_buffer,INDICATOR_DATA); ::SetIndexBuffer(13,rsi_out.m_buy_level_buffer,INDICATOR_DATA); ::SetIndexBuffer(14,rsi_out.m_sell_level_buffer,INDICATOR_DATA); ::SetIndexBuffer(15,rsi_out.m_rsi_buffer,INDICATOR_CALCULATIONS); ::SetIndexBuffer(16,rsi_out.m_pos_buffer,INDICATOR_CALCULATIONS); ::SetIndexBuffer(17,rsi_out.m_neg_buffer,INDICATOR_CALCULATIONS); //--- ::SetIndexBuffer(18,atr.m_atr_buffer,INDICATOR_DATA); ::SetIndexBuffer(19,atr.m_tr_buffer,INDICATOR_CALCULATIONS); //--- Inicializando matrices atr.ZeroIndicatorBuffers(); rsi_in.ZeroIndicatorBuffers(); rsi_out.ZeroIndicatorBuffers(); ... }
Para que no haya confusiones sobre qué nivel de señal se corresponde con qué tipo de señales, para el flat los dibujaremos con una línea punteada.
//+------------------------------------------------------------------+ //| Estableciendo las propiedades del indicador | //+------------------------------------------------------------------+ void SetPropertiesIndicator(void) { ... //--- Estableciendo el estilo de las líneas de los búferes de indicador designados ::PlotIndexSetInteger(13,PLOT_LINE_STYLE,STYLE_DOT); ::PlotIndexSetInteger(14,PLOT_LINE_STYLE,STYLE_DOT); ... }
El código de la función OnCalculate() está limitado solo por la llamada de los métodos para cada ejemplar del indicador.
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- Calculando el indicador ATR if(!atr.CalculateIndicatorATR(rates_total,prev_calculated,time,close,high,low)) return(0); //--- Calculando el indicador RSI if(!rsi_in.CalculateIndicatorRSI(rates_total,prev_calculated,close,spread)) return(0); if(!rsi_out.CalculateIndicatorRSI(rates_total,prev_calculated,close,spread)) return(0); //--- Retornando el último número de elementos calculado return(rates_total); }
En la fig. 15 se muestra qué aspecto tiene el funcionamiento del indicador en el gráfico. Todo esto es solo el resultado del inicio de RSI.
Fig. 15. Indicador RSI universal.
Fig. 16. Indicador RSI universal en el simulador de estrategias.
Así, el indicador muestra los puntos de entrada, además de los puntos en los que se puede incrementar el volumen de la posición. Asimismo, podemos obtener el índice de la señal actual en la serie continua a la que pertenece. Y puesto que aquí tenemos también los niveles de precio, podemos obtener la distancia que ha pasado el precio desde la última señal de la serie anterior. También podemos implementar un algoritmo más complejo. Por ejemplo, al abrir posiciones según la tendencia, podemos cerrarlas parcialmente o por completo según las señales para trabajar con el flat.
Tras analizar los resultados del funcionamiento del indicador en el gráfico, podemos inventar diferentes algoritmos comerciales que primero debemos comprobar en el simulador. Esto también supone un trabajo considerable: las variantes de implementación pueden ser infinitas. Podemos hacer que cada módulo comercial obtenga los resultados del comercio del otro módulo. Es decir, el módulo de comercio según la tendencia puede obtener los resultados comerciales del módulo de comercio en flat, y al revés. Usando como base estos datos, ellos pueden corregir su comportamiento, adecuándose a la situación actual. Cada módulo puede influir en la táctica comercial del otro: cambiar las condiciones de apertura, cierre o acompañamiento de posiciones, o modificar el sistema de gestión de capital (pasándolo de un modelo conservador a uno agresivo, y al contrario). El sistema comercial puede ser muy complejo y adecuarse al comportamiento actual del precio.
Conclusión
Como podemos ver, un indicador sencillo se puede hacer más informativo. Esto, a su vez, simplificará la creación de algoritmos comerciales. De lo contrario, deberíamos hacerlo en el experto comercial, complicando con ello su código. Ahora todo está escondido dentro de un solo programa, y podemos simplemente leer los valores ya calculados en sus matrices.
Usted puede continuar con el desarrollo de esta idea. Por ejemplo, puede introducir búferes adicionales para nuevas características que precisan el estado de la tendencia o el flat. Puede añadir un búfer para el trailig-stop cuyo cálculo se vincule a la volatilidad actual (indicador ATR). Como resultado, todos los cálculos necesarios se implementarán en el indicador, y el experto comercial simplemente recibirá los niveles preparados.
Nombre del archivo | Comentarios |
---|---|
MQL5\Indicators\RSI\RSI_Plus1.mq5 | Nueva versión del indicador RSI modificado |
MQL5\Indicators\RSI\RSI_Plus2.mq5 | Segunda versión del indicador RSI modificado |
MQL5\Indicators\RSI\RSI_Plus3.mq5 | Tercera versión del indicador RSI modificado |
MQL5\Indicators\RSI\ChartRSI_Plus1\ChartRSI_Plus1.mq5 | Cuarta versión del indicador RSI modificado |
MQL5\Indicators\RSI\ChartRSI_Plus2\ChartRSI_Plus2.mq5 | Quinta versión del indicador RSI modificado |
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/4828
![Integración de un experto en MQL y bases de datos (SQL Server, .NET y C#)](https://c.mql5.com/2/25/ForArticle.png)
![Neuroredes profundas (Parte VIII). Aumentando la calidad de la clasificación de los conjuntos bagging](https://c.mql5.com/2/48/Deep_Neural_Networks_08.png)
![Desarrollo de indicadores bursátiles con control de volumen tomando como ejemplo el indicador delta](https://c.mql5.com/2/33/StockVolumeIndicators.png)
![50 000 trabajos ejecutados en la bolsa Freelance de MQL5.com](https://c.mql5.com/2/34/freelance-icon.png)
![MQL5 - Lenguaje de estrategias comerciales para el terminal de cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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