
Introducción a MQL5 (Parte 10): Guía de trabajo con indicadores incorporados en MQL5 para principiantes
Introducción
Hoy querría presentarles otro artículo de nuestra serie dedicada a MQL5. Me alegro de verles en esta décima parte, en la que desglosaremos otro aspecto importante del trading algorítmico: el trabajo con indicadores incorporados. Al igual que en artículos anteriores, aquí hallará material útil y práctico: al igual que antes, seguiremos con un enfoque basado en proyectos, para que pueda poner en práctica directamente los conocimientos en sus propias estrategias comerciales.
En este artículo desarrollaremos un asesor experto basado en el Índice de Fuerza Relativa (Relative Strength Index, RSI). Se trata de uno de los indicadores técnicos más utilizados por los tráders. Asimismo, desarrollaremos una herramienta que vigilará las condiciones del mercado y ejecutará automáticamente transacciones basadas en indicadores RSI. Al mismo tiempo, las ideas aquí expuestas serán aplicables a casi todos los indicadores incorporados, ya que el principio de su funcionamiento es similar. Como la serie está dirigida principalmente a principiantes, mi objetivo es que las explicaciones y el código sean lo más sencillos posible. Sé lo importante que es para un principiante comprender cada detalle: por qué se escribe una línea de código concreta, qué hace exactamente este o aquel bloque y cómo se conectan todas las piezas para formar un sistema funcional.
En el desarrollo profesional de MQL5, se aprecia el código sucinto y optimizado. Pero este estilo dificulta con frecuencia su comprensión, sobre todo para los principiantes. Así que en esta serie, tomaremos intencionalmente un enfoque más detallado y metódico para que el lector se sienta cómodo descubriendo el proceso.
En este artículo aprenderá:
- Cómo usar los indicadores incorporados.
- Qué son los manejadores de indicadores y cómo trabajar con ellos.
- Cómo acceder a los búferes de indicador y recuperar de ellos los valores calculados.
- Cómo obtener los datos del RSI y los valores correspondientes de las velas del gráfico.
- Cómo encontrar los extremos del RSI y usarlos para aplicar el concepto de "retirada de liquidez".
- Cómo crear paso a paso de un asesor experto basado en datos de RSI y sus velas.
- Cómo crear objetos para marcar los máximos y mínimos clave del RSI directamente en el gráfico para un análisis claro.
- Cómo establecer el porcentaje de riesgo por transacción considerando los diferentes tamaños de las velas al trabajar con los indicadores incorporados.
Como resultado, usted comprenderá por entero cómo integrar los indicadores incorporados en las estrategias comerciales haciendo hincapié en la gestión de riesgos, la adaptación de la estrategia y las técnicas prácticas en la creación de asesores expertos basados en RSI.
1. Comprendiendo los indicadores incorporados en MQL5
1.1. ¿Qué son los indicadores incorporados?
Los indicadores incorporados en MetaTrader 5 son herramientas listas para analizar del mercado. Permiten evaluar rápidamente la situación actual: el estado de ánimo del mercado, la fuerza del movimiento y la dinámica de los precios. Por ejemplo, las Bandas de Bollinger (Bollinger Bands) muestran el grado de volatilidad, la media móvil ayuda a identificar la tendencia y el RSI señala sobrecompra o sobreventa. Estas herramientas facilitan mucho la negociación y ahorran tiempo.
1.2. Manejadores de los indicadores
En MQL5, el manejador del indicador es un identificador único asignado al indicador al crearlo o inicializarlo. En esencia, se trata de una "referencia" al indicador, con la cual el programa puede acceder a sus datos. Al añadir un indicador a un gráfico, deberá especificar sus parámetros: periodo, tipo de precio y otros ajustes que determinan el comportamiento del indicador.
En el código, la manilla tenemos una función similar: esta permite al programa "comprender" con qué indicador está trabajando y controlar sus parámetros. En otras palabras, el manejador vincula la configuración del indicador a su programa para que pueda utilizarlo en su estrategia comercial.
Al crea un manejador utilizando funciones como iRSI o iBands, el programa se "vincula" a un indicador específico, lo que permite obtener sus datos y trabajar con ellos. Sin el manejador, el programa no podrá distinguir un indicador de otro y no tendrá acceso a los valores calculados de sus búferes. Por ejemplo, si deseamos establecer los parámetros RSI directamente en el código, utilizaremos la función iRSI en la que se especifica el periodo, el tipo de precio y el desplazamiento. Esta función creará un manejador a través del cual podrá trabajar con este indicador.
Sintaxis:
iRSI(symbol, period, rsi_period, applied_price);
Explicación:
- symbol — parámetro define el símbolo (par de divisas, acción, etc.) para el que se calcula el indicador.
- period — periodo para el cálculo del RSI. Determina cuánto tiempo atrás en el pasado considerará el RSI los puntos de datos.
- rsi_period — número de periodos utilizados para el cálculo del RSI. El RSI suele calcularse en 14 periodos, pero este valor puede ajustarse según su estrategia.
- applied_price — tipo de precio utilizado para el cálculo del RSI. El RSI puede leerse en diferentes valores de precio: el precio de cierre, el precio de apertura, el máximo/mínimo.
int rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE);
Explicación:
int rsi_handle:
- Declara una variable entera rsi_handle para almacenar el manejador del indicador RSI (identificador único del indicador).
iRSI(...):
- Función usada para calcular el RSI para un símbolo, un marco temporal y una configuración determinados.
_Symbol:
- Indica el símbolo comercial actual del gráfico. Usa automáticamente el símbolo actual con el que estamos trabajando.
PERIOD_CURRENT:
- Se refiere al marco temporal del gráfico (por ejemplo, horario, diario). Current significa el marco temporal actual del gráfico en el que se está ejecutando el indicador.
14:
- El periodo del RSI determina el número de barras/velas que se usarán en el cálculo (normalmente 14 periodos).
PRICE_CLOSE:
- Indica el cálculo del RSI según los precios de cierre de barra.
De esta manera, se establecen los parámetros del indicador directamente en el código, como si se establecieran manualmente en el gráfico en MetaTrader 5. Con funciones como iRSI, podemos definir el símbolo, el marco temporal, el periodo y el tipo de precio, de forma similar a la configuración de nuestro terminal. Gracias a esto, el código obtiene acceso a los datos del indicador y puede trabajar con ellos, y la estrategia comercial funcionará de acuerdo con estos parámetros establecidos.
Los mismos principios se aplican a otros indicadores incorporados en MetaTrader 5. Cada indicador usa su propia función con un conjunto único de parámetros. Estas son algunas de las funciones más usadas:
- iBands — para el indicador de Bandas de Bollinger, permite establecer el símbolo, el marco temporal, el periodo, la desviación y el precio de cálculo.
- iMA — para la media móvil, aquí se establece el símbolo, el marco temporal, el periodo, el desplazamiento, el método de promediación y el tipo de precio.
- iMACD — para el indicador MACD, define el símbolo, el marco temporal, la EMA rápida y lenta, el periodo de la línea de señal y el tipo de precio.
- iADX — para el indicador Average Directional Index, indica el símbolo, el marco temporal y el periodo.
MQL5 dispone de muchas otras funciones para trabajar con los indicadores incorporados, lo cual proporciona a los tráders un amplio arsenal de herramientas de análisis técnico. Una vez que domine el principio de trabajo con al menos uno de ellos, podrá aplicar fácilmente los mismos planteamientos a los demás. La documentación de MQL5 le ayudará a encontrar los indicadores que mejor se adapten a sus tareas.
1.2. Búferes de indicador
Una vez definido el indicador mediante el manejador, el siguiente paso será extraer los datos. Para ello se utilizan los búferes de indicador, que son arrays especiales en los que se almacenan los valores calculados de los indicadores para cada punto del gráfico. El número de búferes dependerá del tipo de indicador y de los datos que genere:
Media móvil (MA)
Este indicador tiene solo 1 búfer donde se almacenan los valores calculados de la media móvil para cada vela.
Índice de fuerza relativa (Relative Strength Index, RSI).
El RSI también tiene 1 buffer, para almacenar valores RSI
Indicador de Bandas de Bollinger
Utiliza 3 búferes de datos.
- La banda central (índice 0) es la línea de tendencia principal.
- La banda superior (índice 1) representa un posible nivel de sobrecompra.
- La banda inferior (índice 2) representa un posible nivel de sobreventa.
Todos estos búferes pueden obtenerse de forma programática utilizando la función CopyBuffer().
La función CopyBuffer() en MQL5 se utiliza para copiar los datos del búfer del indicador en un array para analizarlos posteriormente o utilizarlos para tomar decisiones comerciales. Una vez hayamos creado un indicador (por ejemplo, a través de iRSI o iBands), utilizaremos CopyBuffer() para obtener los valores calculados.
Sintaxis:int CopyBuffer(indicator_handle, buffer_number, start_position, count, buffer);
Parámetros:
- indicator_handle — identificador (manejador) único del indicador creado anteriormente (por ejemplo, con iRSI o iBands).
- buffer_number — índice del búfer del que obtenemos los datos. Por ejemplo, para las Bandas de Bollinger: 0 - línea central, 1 - banda superior, 2 - banda inferior. Para RSI o media móvil, solo 0, porque tienen el mismo búfer.
- start_pos — posición inicial en el gráfico, donde 0 significa la vela más reciente.
- count — número de valores a extraer.
- buffer[] — array donde se guardan los datos copiados.
int band_handle; // Bollinger Bands handle double upper_band[], mid_band[], lower_band[]; // Buffers for the bands void OnStart() { // Create the Bollinger Bands indicator band_handle = iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE); // Ensure arrays are series for correct indexing ArraySetAsSeries(upper_band, true); ArraySetAsSeries(mid_band, true); ArraySetAsSeries(lower_band, true); // Copy data from buffers (Index 0 = Middle, 1 = Upper, 2 = Lower) CopyBuffer(band_handle, 0, 0, 10, mid_band); // Middle band data CopyBuffer(band_handle, 1, 0, 10, upper_band); // Upper band data CopyBuffer(band_handle, 2, 0, 10, lower_band); // Lower band data // Print the most recent values Print("Middle Band: ", mid_band[0]); Print("Upper Band: ", upper_band[0]); Print("Lower Band: ", lower_band[0]); }
Explicación:
Este ejemplo muestra cómo usar la función CopyBuffer() para recuperar los datos del indicador Bandas de Bollinger. En primer lugar, la función iBands crea un indicador con los parámetros especificados: símbolo (_Symbol), marco temporal (PERIOD_CURRENT), periodo (20), desplazamiento (0), desviación (2.0) y precio de cálculo (PRICE_CLOSE). Este manejador, almacenado en la variable band_handle, actúa como puente entre nuestra aplicación y el indicador de Bandas de Bollinger, ofreciendo acceso a los valores calculados. A continuación, se declaran tres arrays: upper_band, mid_band y lower_band, que contendrán los datos de las bandas superior, media e inferior. Si llamamos a la función ArraySetAsSeries() para estos arrays, su indexación se organizará como una serie de precios: la última vela comenzará desde cero (0). Esto resulta cómodo porque el valor más actual estará siempre en array[0].
Cada uno de los tres búferes del indicador Bandas de Bollinger, 0 para la línea media, 1 para la banda superior y 2 para la banda inferior tendrá su propia llamada a la función CopyBuffer(). Los últimos 11 valores se cargarán en cada array. Por último, el programa imprimirá los últimos valores de las barras central, superior e inferior en el registro usando Print(). La relación entre el precio y las Bandas de Bollinger puede utilizarse para identificar posibles rupturas o cambios de tendencia, así como para otras tareas analíticas dentro del sistema comercial.
2. Desarrollo de un asesor experto basado en el RSI
2.1. Cómo funciona el asesor experto:
El asesor experto analiza los niveles de sobrecompra y sobreventa como señales clave de un posible cambio de tendencia.
2.1.1. Lógica de apertura de una transacción de compra (Buy)
- Comprobamos si el valor del RSI está por debajo del nivel 30.
- Determinamos si el RSI ha formado un mínimo local.
- Buscamos el mínimo de la vela correspondiente en el gráfico de precios.
- Esperamos a que el precio rompa este mínimo (retirada de liquidez).
- Cuando la primera vela alcista cierre por encima del nivel roto después de romper los mínimos, el asesor experto abrirá una transacción de Buy, esperando que el precio suba.
2.1.2. Lógica de apertura de una transacción de venta (Sell)
- Comprobamos si el valor del RSI está por encima del nivel 70.
- Determinamos si el RSI ha formado un máximo local.
- Buscamos el máximo de la vela correspondiente en el gráfico de precios.
- Esperamos a que el precio rompa este máximo, retirando liquidez.
- Después de la ruptura, cuando la primera vela bajista cierre por debajo del nivel roto, el asesor experto abrirá una transacción Sell, esperando que el precio baje.
2.2. Conexión de una biblioteca comercial
El primer paso para crear un asesor experto que abra, cierre o modifique posiciones será conectar la biblioteca comercial. Esta biblioteca ofrece un conjunto de funciones básicas para la apertura de programas y la gestión de transacciones.
Ejemplo:#include <Trade/Trade.mqh> // Include the trade library for trading functions // Create an instance of the CTrade class for trading operations CTrade trade; //magic number input int MagicNumber = 1111; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Set the magic number for the EA's trades trade.SetExpertMagicNumber(MagicNumber); // Return initialization success return(INIT_SUCCEEDED); }
Explicación:
Conexión de una biblioteca comercial
- Para usar la clase CTrade, será necesario importar la biblioteca comercial utilizando la directiva #include<Trade/Trade.mqh>. La clase CTrade simplifica las transacciones comerciales proporcionando funciones para la gestión de take profit y stop loss, la apertura de órdenes de mercado y pendientes, y la realización de otras acciones relacionadas con la negociación.
Creación de una instancia de la clase CTrade
- CTrade trade; — esta línea crea una instancia de la clase CTrade, que se utilizará durante todo el trabajo del asesor experto para abrir y gestionar transacciones.
- La variable MagicNumber se declara como variable de entrada usando la palabra clave Input. Esto permitirá al usuario del asesor experto establecer un identificador único para las transacciones que abra el asesor experto.
¿Por qué esto es tan importante?
- El número mágico permite distinguir las transacciones abiertas por el asesor experto de otras transacciones, incluidas las transacciones manuales.
- Con este, el asesor solo podrá gestionar sus propias posiciones sin interferir en las de los demás.
- Cambiando el número mágico, el usuario podrá ejecutar el asesor experto en diferentes instrumentos o en el mismo instrumento con diferentes marcos temporales y condiciones de mercado.
int OnInit() { // Set the magic number for the EA's trades trade.SetExpertMagicNumber(MagicNumber); // Return initialization success return(INIT_SUCCEEDED); }
Función de inicialización:
La función OnInit() se ejecuta cuando se inicia el asesor experto. Usando el método SetExpertMagicNumber(), el valor Magic Number especificado se asignará al asesor experto para que todas las transacciones abiertas por esta instancia tengan este identificador. Este enfoque ofrece mayor flexibilidad: el usuario puede establecer diferentes valores de Magic Numer para diferentes escenarios comerciales, lo cual resulta cómodo al gestionar múltiples instancias del asesor experto en diferentes instrumentos o estrategias.
2.3. Obtención de los valores de RSI y los datos de velas
Para el correcto funcionamiento del asesor experto, será crucial obtener datos precisos del mercado. La lógica de este asesor experto se basa en el análisis de patrones de velas y el indicador RSI, por lo que será fundamental obtener estos valores de forma fiable. Los datos permiten al asesor experto determinar si los extremos del RSI corresponden a velas específicas, lo cual es importante para señalar los puntos de entrada y salida. Al combinar los valores del RSI con la información de las velas, el asesor experto valora las condiciones del mercado de forma exhaustiva, identificando las condiciones de sobrecompra y sobreventa en el contexto del movimiento de los precios. Este enfoque mejora la precisión y la lógica de las decisiones comerciales.
2.3.1. Obtención de los valores de RSI
Como ya hemos comentado, la obtención de los valores RSI es un paso clave para analizar la situación del mercado. El proceso consta de dos partes: el ajuste de los parámetros de RSI utilizando el manejador creado por la función iRSI, y la recuperación de los valores RSI reales utilizando la función CopyBuffer().
Ejemplo:
#include <Trade/Trade.mqh> CTrade trade; // Magic number input int MagicNumber = 1111; //RSI handle int rsi_handle; double rsi_buffer[]; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Set the magic number (for trading, not relevant to RSI retrieval here) trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Display the most recent RSI value for verification Comment("rsi_buffer[0]: ", rsi_buffer[0]); }
Explicación:
Organización del búfer RSI
- La función OnInit() usa ArraySetAsSeries() para que la indexación en el array rsi_buffer sea como una serie temporal. Esto significa que el valor RSI más reciente estará en el índice 0, lo cual facilitará el acceso a los datos actualizados.
Creación de un manejador de RSI
- En la función OnTick(), se llama a la función iRSI, que inicializará el manejador RSI (rsi_handle). Esta función establecerá las propiedades del indicador: el símbolo, el marco temporal, el periodo y el precio de cálculo.
Parámetros:
- _Symbol — instrumento comercial para el que se calcula el RSI.
- PERIOD_CURRENT – marco temporal actual del gráfico.
- 14 – Periodo RSI (valor por defecto de uso frecuente).
- PRICE_CLOSE – el RSI se calcula a partir de los precios de cierre de las velas.
Copiado de valores de RSI
- La función CopyBuffer() recupera los valores RSI y rellena el array rsi_buffer. El copiado comenzará a partir del último valor (offset 1). Se recuperarán hasta 100 valores a la vez.
Parámetros:
- 0 – índice del búfer de RSI (para la línea principal),
- 1 – posición inicial (segundo valor más reciente), ya que el último valor de RSI aún se está formando y puede ser inestable.
- 100 – número de valores a obtener.
De esta forma, el asesor experto siempre trabajará con datos actualizados de RSI, que serán fundamentales para analizar las condiciones de sobrecompra y sobreventa, así como para tomar decisiones comerciales.
Visualización del valor de RSI
- Para comprobar que la extracción de datos sea correcta, podemos trazar el valor RSI más reciente (rsi_buffer[0]) usando la función Comment().
- Esto garantizará que el asesor tenga acceso a información actualizada para analizar las condiciones del mercado y tomar decisiones.
Resultado:
2.3.2. Obtención de datos sobre las velas
Para analizar la actividad de los precios y compararla con las lecturas de RSI, deberemos obtener los datos de las velas. El asesor experto recibe información clave sobre las velas: la apertura (Open), el cierre (Close), el máximo (High), el mínimo (Low) y las marcas temporales (Timestamp) correspondientes a estos valores. Estos datos resultan esenciales para evaluar las condiciones del mercado y tomar decisiones comerciales.
Ejemplo:
#include <Trade/Trade.mqh> CTrade trade; // Magic number input int MagicNumber = 1111; int rsi_handle; double rsi_buffer[]; double open[]; double close[]; double high[]; double low[]; datetime time[]; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); // Display the most recent candlestick data for verification Comment("open[0]: ", open[0], "\nclose[0]: ", close[0], "\nhigh[0]: ", high[0], "\nlow[0]: ", low[0], "\ntime[0]: ", time[0]); }
Explicación:
Organización de los arrays de velas
- OnInit() usa ArraySetAsSeries() para establecer la indexación de los arrays open, close, high, low y time como una serie temporal.
- En este caso, los datos más recientes se encontrarán en el índice 0, lo cual facilitará el acceso a las últimas velas formadas y su análisis.
Copiado de los datos de velas
- La función OnTick() usa CopyOpen, CopyClose, CopyHigh, CopyLow y CopyTime para recuperar los datos de las velas correspondientes.
Parámetros:
- _Symbol — instrumento comercial actual,
- PERIOD_CURRENT — marco temporal del gráfico (por ejemplo, M1, H1),
- 1 — el copiado comienza a partir de la penúltima vela, ya que la última (índice 0) aún no está completamente formada,
- 100 — se recuperan hasta 100 velas para su análisis.
Cada función rellena su propio array:
- open[] — precios de apertura de las velas,
- close[] — precios de cierre,
- high[] — valores de los precios del máximo,
- low[] — valores de los precios del mínimo,
- time[] — marcas temporales de apertura de las velas para la sincronización con el movimiento del precio.
Visualización de los datos de velas
Para comprobar que la obtención de datos sea correcta, podemos imprimir información sobre la última vela completada (índice 0) en el gráfico utilizando la función Comment():
- open[0] — precio de apertura,
- close[0] — precio de cierre,
- high[0] — máximo de la vela,
- low[0] — mínimo de la vela,
- hora[0] — hora de apertura de la vela.
2.3.3. Determinación de los máximos y mínimos del RSI
El asesor experto debe reconocer los mínimos del RSI cuando el indicador cae por debajo del nivel de sobreventa (30) y los máximos del RSI cuando este sube por encima del nivel de sobrecompra (70). Para identificar posibles zonas de interés, estos puntos del RSI se vinculan a velas concretas del gráfico. Esta etapa supone la base del funcionamiento del asesor experto, ya que son estas etiquetas las que se utilizan para construir la lógica de la retirada de liquidez.
Los máximos y mínimos del RSI se determinan de forma bastante sencilla:
Mínimo del RSI:
Máximo del RSI:
Ejemplo:
// Magic number input int MagicNumber = 1111; // RSI handle and buffer int rsi_handle; double rsi_buffer[]; // Candlestick data arrays double open[]; double close[]; double high[]; double low[]; datetime time[]; // Variables to store high and low levels double max_high = 0; // Maximum high for the candlesticks datetime min_time1 = 0; // Time of the maximum high candlestick double min_low = 0; // Minimum low for the candlesticks datetime min_time2 = 0; // Time of the minimum low candlestick // Variables to store RSI highs and lows datetime time_low = 0; // Time of the RSI low datetime times_high = 0; // Time of the RSI high //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number for the EA trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data (open, close, high, low, time) CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); // Loop to find the maximum high from a bullish candlestick pattern for(int i = 0; i < 12; i++) { // Check for a bullish pattern: current close < open and previous close > open if(close[i] < open[i] && close[i+1] > open[i+1]) { // Calculate the maximum high between the two candlesticks max_high = MathMax(high[i], high[i+1]); // Record the time of the corresponding candlestick min_time1 = MathMin(time[i], time[i+1]); break; } } // Loop to find the minimum low from a bearish candlestick pattern for(int i = 0; i < 12; i++) { // Check for a bearish pattern: current close > open and previous close < open if(close[i] > open[i] && close[i+1] < open[i+1]) { // Calculate the minimum low between the two candlesticks min_low = MathMin(low[i], low[i+1]); // Record the time of the corresponding candlestick min_time2 = MathMin(time[i], time[i+1]); break; } } // Loop to find the RSI low point for(int i = 0; i < 12; i++) { // Check if the RSI is oversold and forms a low point if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { // Record the time of the RSI low time_low = time[i+1]; break; } } // Loop to find the RSI high point for(int i = 0; i < 12; i++) { // Check if the RSI is overbought and forms a high point if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { // Record the time of the RSI high times_high = time[i+1]; break; } } }
Explicación:
Mínimos del RSI (condiciones de sobreventa)
- El ciclo recorre el array RSI y busca los puntos en los que: el valor RSI cae por debajo de 30 (rsi_buffer[i+1] < 30) y luego empieza a crecer (rsi_buffer[i] > rsi_buffer[i+1]).
- Estas condiciones indican que el RSI ha alcanzado un mínimo y está comenzando a revertirse.
- La marca temporal de la vela correspondiente se almacena en el array time_low.
Máximos del RSI (condiciones de sobrecompra)
- El ciclo busca los puntos en los que el valor del RSI sube por encima de 70 (rsi_buffer[i+1] > 70) y luego comienza a bajar (rsi_buffer[i] < rsi_buffer[i+1]).
- Estas condiciones indican que el RSI ha tocado techo y está comenzando a revertirse.
- La marca temporal de la vela correspondiente se almacena en el array times_high.
2.3.4. Marcado de mínimos y máximos en el gráfico
2.3.4.1. Marcado del mínimo en el gráfico
Después de reconocer el mínimo del RSI, el programa analiza los datos de las velas para determinar el nivel mínimo exacto en el gráfico. Como se necesitan dos puntos consecutivos para formar un mínimo del RSI, el asesor experto analiza los mínimos de las dos velas correspondientes para calcular el nivel exacto.
Se trata de un nivel importante, pues establece la zona en la que la asesor experto esperará posibles retiradas de liquidez.
Lógica:
- El RSI forma un mínimo cuando:
- rsi_buffer[i+1] < 30 — RSI cae por debajo del nivel de sobreventa,
- rsi_buffer[i] > rsi_buffer[i+1] — RSI comenzará a crecer tras alcanzar el mínimo.
- Una vez confirmado el mínimo del RSI, el programa determina el valor mínimo utilizando el array low[] de las dos velas correspondientes.
- Este será el nivel del mínimo menor donde el asesor experto espera una potencial reversión o retirada de liquidez.
- Así, el programa extrae del array de mínimos los valores mínimos de las dos velas correspondientes.
- Usando la función MathMin(), se determina el valor más bajo entre estos dos mínimos, que se convertirá en el nivel para controlar la retirada de liquidez.
- Este suelo sirve como punto en el que esperamos un posible retroceso o retirada de liquidez, un factor clave para las decisiones comerciales.
// Loop to find RSI and candlestick lows for(int i = 0; i < 12; i++) { // Check if the RSI is oversold and forms a low point if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { // Record the time of the RSI low time_low = time[i+1]; // Find the minimum low from the two corresponding candlesticks min_low = (double)MathMin(low[i], low[i+1]); // Break the loop once the low is found break; } }
2.3.4.2. Marcado del máximo en el gráfico
Tras reconocer el máximo del RSI, el programa analiza los datos de las velas para determinar el nivel máximo exacto en el gráfico. Como se necesitan dos puntos consecutivos para formar un máximo del RSI, el asesor experto utiliza los máximos de las dos velas correspondientes para calcular el nivel exacto.
Este nivel máximo sirve como punto de referencia para la zona en la que la asesor experto esperará posibles retiradas de liquidez. La lógica aquí resulta similar a la utilizada para los mínimos.
Lógica:
- El RSI forma un máximo cuando:
- rsi_buffer[i+1] > 70 — RSI supera el nivel de sobrecompra,
- rsi_buffer[i] < rsi_buffer[i+1] — RSI empieza a caer tras alcanzar el máximo.
- Una vez confirmado el máximo del RSI, el programa determina el máximo mayor utilizando el array high[] de las dos velas correspondientes.
- Este máximo mayor establece la zona en la que cabría esperar una posible retirada de liquidez.
- Tras identificar el máximo del RSI, el programa recupera los valores del máximo de dos velas del array high[].
- Utilizando la función MathMax(), se determina el mayor valor entre ellos, que se convertirá en el nivel para controlar la retirada de liquidez.
// Loop to find RSI and candlestick highs for(int i = 0; i < 12; i++) { // Check if the RSI is overbought and forms a high point if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { // Record the time of the RSI high times_high = time[i+1]; // Find the maximum high from the two corresponding candlesticks max_high = (double)MathMax(high[i], high[i+1]); // Break the loop once the high is found break; } }
2.3.5. Seguimiento de los máximos y mínimos del RSI con un retardo de 12 velas
Al trabajar con el RSI, puede darse la situación de que se formen varios máximos o mínimos consecutivos en una zona de sobrecompra o sobreventa. Esto hará que se marquen diferentes niveles en el gráfico, introduciendo ruido. Para excluir las actualizaciones frecuentes del nivel, se usa la regla de las 12 velas: después de fijar el máximo o el mínimo del RSI, la actualización del nivel no tendrá lugar hasta que se cierren al menos 12 nuevas velas.
Ejemplo:
// Magic number input int MagicNumber = 1111; int rsi_handle; double rsi_buffer[]; double open[]; double close[]; double high[]; double low[]; datetime time[]; double max_high = 0; datetime min_time1 = 0; double min_low = 0; datetime min_time2 = 0; datetime time_low = 0; datetime times_high = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); static double max_high_static = max_high; static datetime min_time1_static = min_time1; static double min_low_static = min_low; static datetime min_time2_static = min_time2; int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] < open[i] && close[i+1] > open[i+1]) { max_high = (double)MathMax(high[i],high[i+1]); min_time1 = (datetime)MathMin(time[i],time[i+1]); break; } } int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] > open[i] && close[i+1] < open[i+1]) { min_low = (double)MathMin(low[i],low[i+1]); min_time2 = (datetime)MathMin(time[i],time[i+1]); break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { time_low = time[i+1]; break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { times_high = time[i+1]; break; } } if((total_bar_high == 0 || total_bar_high > 12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > 13) { max_high_static = 0; min_time1_static = 0; } if((total_bar_low == 0 || total_bar_low > 12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > 13) { min_low_static = 0; min_time2_static = 0; } }
Explicación:
Determinación del máximo/mínimo del RSI y las velas
- Para determinar si se ha producido un máximo o un mínimo, se analiza la actividad del indicador en relación con los niveles de sobrecompra o sobreventa.
- De ser así, el programa determinará el máximo más alto o el mínimo más bajo entre las velas relacionadas.
- Usando la función Bars(), el programa calcula cuántas velas se han formado desde el último máximo o mínimo.
- Los valores máximo y mínimo solo se actualizarán después de al menos 12 velas, evitando así cambios frecuentes.
- Si han pasado más de 13 velas y ya no se cumple la condición de sobrecompra/sobreventa, se restablecerán los niveles guardados.
- Esto garantizará que el asesor experto solo trabaje con los niveles actuales y no tenga en cuenta señales obsoletas.
Para aumentar la visibilidad, el asesor experto trazará líneas en el gráfico indicando los máximos y mínimos del RSI encontrados. Esto no solo permite al asesor experto usar estos objetos para identificar programáticamente máximos y mínimos, sino que también ofrece a los tráders la posibilidad de realizar un seguimiento manual de los niveles críticos.
#include <Trade/Trade.mqh> CTrade trade; // Magic number input int MagicNumber = 1111; int rsi_handle; double rsi_buffer[]; double open[]; double close[]; double high[]; double low[]; datetime time[]; double max_high = 0; datetime min_time1 = 0; double min_low = 0; datetime min_time2 = 0; datetime time_low = 0; datetime times_high = 0; string high_obj_name = "High_Line"; string low_obj_name = "Low_Line"; long chart_id; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); static double max_high_static = max_high; static datetime min_time1_static = min_time1; static double min_low_static = min_low; static datetime min_time2_static = min_time2; //CHART ID chart_id = ChartID(); int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] < open[i] && close[i+1] > open[i+1]) { max_high = (double)MathMax(high[i],high[i+1]); min_time1 = (datetime)MathMin(time[i],time[i+1]); break; } } int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] > open[i] && close[i+1] < open[i+1]) { min_low = (double)MathMin(low[i],low[i+1]); min_time2 = (datetime)MathMin(time[i],time[i+1]); break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { time_low = time[i+1]; break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { times_high = time[i+1]; break; } } if((total_bar_high == 0 || total_bar_high > 12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > 13) { max_high_static = 0; min_time1_static = 0; } if((total_bar_low == 0 || total_bar_low > 12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > 13) { min_low_static = 0; min_time2_static = 0; } ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,0,min_time1_static,max_high_static,TimeCurrent(),max_high_static); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,3); ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,0,min_time2_static,min_low_static,TimeCurrent(),min_low_static); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,3); }
Explicación:
El código crea dos líneas de tendencia en el gráfico usando la función ObjectCreate(): high_obj_name, que traza el máximo, y low_obj_name, que traza el mínimo. Estas líneas se extienden hasta el presente (TimeCurrent()) y se derivan de los niveles de precios máximos y mínimos calculados (max_high_static y min_low_static) para los momentos correspondientes en el tiempo (min_time1_static y min_time2_static). Esto permite a los tráders monitorear visualmente los máximos y mínimos en el gráfico.
Usando la función ObjectSetInteger(), podemos personalizar la apariencia de estas líneas. La línea para high se muestra en verde y la línea para low (nombre_obj_bajo) se muestra en rojo. El grosor de ambas líneas se fija en 3 para que los niveles sean fáciles de leer. Así, el tráder podrá controlar visualmente las zonas críticas, mientras que el asesor experto podrá utilizarlas para la lógica de entrada del programa.
2.3.7. Condiciones de entrada en la transacción (Liquidity Sweeps)
Las condiciones comerciales deben coincidir exactamente con determinados escenarios de mercado para poder ejecutar la lógica comercial incorporada. Veamos la lógica de ejecución de las transacciones de compra y venta en estos niveles.
// Magic number input int MagicNumber = 1111; int rsi_handle; double rsi_buffer[]; double open[]; double close[]; double high[]; double low[]; datetime time[]; double max_high = 0; datetime min_time1 = 0; double min_low = 0; datetime min_time2 = 0; datetime time_low = 0; datetime times_high = 0; string high_obj_name = "High_Line"; string low_obj_name = "Low_Line"; long chart_id; double take_profit; double ask_price = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); static double max_high_static = max_high; static datetime min_time1_static = min_time1; static double min_low_static = min_low; static datetime min_time2_static = min_time2; //GETTING TOTAL POSITIONS int totalPositions = 0; for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { totalPositions++; } } } ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(totalPositions < 1) { if(((low[0] < min_low_static && close[0] > min_low_static && close[0] > open[0]) || (low[1] < min_low_static && close[0] > min_low_static && close[0] > open[0]) || (low[2] < min_low_static && close[0] > min_low_static && close[0] > open[0] && close[1] < open[1]))) { take_profit = (close[0] - low[0]) * 3 + close[0]; trade.Buy(0.5,_Symbol,ask_price, low[0], take_profit); } else if(((high[0] > max_high_static && close[0] < max_high_static && close[0] < open[0]) || (high[1] > max_high_static && close[0] < max_high_static && close[0] < open[0]) || (high[2] > max_high_static && close[0] < max_high_static && close[0] < open[0] && close[1] > open[1]))) { take_profit = MathAbs((high[0] - close[0]) * 3 - close[0]); // Adjusted take-profit calculation trade.Sell(0.5,_Symbol,ask_price, high[0], take_profit); } } //CHART ID chart_id = ChartID(); int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] < open[i] && close[i+1] > open[i+1]) { max_high = (double)MathMax(high[i],high[i+1]); min_time1 = (datetime)MathMin(time[i],time[i+1]); break; } } int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] > open[i] && close[i+1] < open[i+1]) { min_low = (double)MathMin(low[i],low[i+1]); min_time2 = (datetime)MathMin(time[i],time[i+1]); break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { time_low = time[i+1]; break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { times_high = time[i+1]; break; } } if((total_bar_high == 0 || total_bar_high > 12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > 13) { max_high_static = 0; min_time1_static = 0; } if((total_bar_low == 0 || total_bar_low > 12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > 13) { min_low_static = 0; min_time2_static = 0; } ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,0,min_time1_static,max_high_static,TimeCurrent(),max_high_static); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,3); ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,0,min_time2_static,min_low_static,TimeCurrent(),min_low_static); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,3); }
Explicación:
Evitar posiciones múltiples con totalPositions
Usaremos la variable totalPositions para asegurarnos de que el asesor experto no tenga más de una posición abierta en un momento dado. Este mecanismo comprueba todas las posiciones abiertas, el número mágico (el ID de la transacción del asesor experto) y el símbolo. Si no hay operaciones activas para el símbolo y número mágico actuales (totalPositions < 1), el asesor experto puede considerar una nueva entrada.
Esto eliminará el solapamiento de posiciones y simplificará la gestión de estrategias.
Condiciones para abrir una transacción de compra (Low Liquidity Sweep)
La señal se produce al retirarse liquidez por debajo del mínimo clave (min_low_static) cuando se forma la confirmación alcista.
Condiciones generales
Ruptura del mínimo:
- Al menos una de las tres últimas velas (low[0], low[1], low[2]) ha superado el nivel min_low_static.
Reacción alcista:
- El precio de cierre actual (close[0]) ha vuelto por encima del nivel min_low_static, una señal del sentimiento alcista tras la reversión.
- Además, la vela debe ser alcista (cierre[0] > apertura[0]) y mostrar un impulso alcista.
- Si las dos velas anteriores a la vela actual (close[1] < open[1]) han sido bajistas, se añadirá un componente de reversión como confirmación.
Ejecución de la transacción
Si se cumplen las condiciones, se establecerá una orden de compra:- Take Profit — rango triple desde el cierre hasta el mínimo (tp = (close[0] - low[0]) * 3 + close[0]).
- Stop Loss — mínimo de la vela (low[0]).
Condiciones para abrir una transacción de venta (High Liquidity Sweep)
La lógica de venta refleja la lógica de compra, pero se centra en la determinación de la liquidez en el máximo establecido previamente (max_high_static) con confirmación bajista.
Condiciones generales
Ruptura por encima del máximo:
- Cualquiera de las tres últimas velas (high[0], high[1] o high[2]) deberá superar el máximo encontrado, lo cual indicará que la liquidez está subiendo por encima de este nivel clave.
Reversión bajista:
- El precio de cierre actual (close[0]) deberá caer por debajo del máximo (max_high_static), lo cual indicará una incapacidad para mantener la ruptura.
- Además, la vela deberá ser bajista (close[0] < open[0]), lo cual indicará un impulso bajista.
- Si las dos velas anteriores a la vela actual (close[1] > open[1]) han sido alcistas, esto indicará una posible reversión.
Ejecución de la transacción
Si se cumplen las condiciones requeridas, se colocará una orden de venta:
- Take Profit — en la cantidad del rango triple desde el máximo hasta el cierre (take_profit = MathAbs((high[0] - close[0]) * 3 - close[0])).
- Stop Loss — en el máximo de la vela (high[0]).
Conclusiones
Para tomar decisiones comerciales precisas y limitar los riesgos, analizamos el número total de posiciones totalPositions y las condiciones comerciales:
- Las transacciones de compra se realizarán después de caer por debajo de un mínimo clave con una recuperación alcista.
- Las transacciones de venta se ejecutarán después de una subida por encima de un máximo clave con una reversión bajista.
Además, una de las limitaciones de este método es que el stop loss se fija dinámicamente según el mínimo [0] o del máximo [0] de una vela. Y esto significa que el riesgo por transacción variará en función del tamaño de la vela, lo cual dará lugar a una distribución desigual del riesgo. Para resolver este problema, la estrategia debería permitirnos especificar una cantidad fija de riesgo por transacción como porcentaje del saldo de la cuenta (por ejemplo, 2%). Esto nos permitiría controlar el riesgo mediante el cálculo del tamaño de la posición según la distancia entre el precio de entrada y el stop loss según el porcentaje de riesgo especificado.
2.3.8. Gestión del riesgo y modificación del umbral de rentabilidad
Una de las desventajas de los stops dinámicos es que el riesgo por transacción está directamente relacionado con el tamaño de la vela. Al mismo tiempo, el tamaño de la vela puede variar significativamente, por lo que el riesgo por vela será diferente. Para solucionarlo, podemos especificar un riesgo fijo como porcentaje del depósito. Además, los niveles de stop pueden ajustarse para bloquear con precisión un determinado beneficio si el precio se invierte repentinamente. Se trata de un seguro de ganancias gracias al desplazamiento del stop a la ausencia de pérdidas.
Ejemplo:
//+------------------------------------------------------------------+ //| MQL5INDICATORS_PROJECT4.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "ForexYMN" #property link "crownsoyin@gmail.com" #property version "1.00" #include <Trade/Trade.mqh> CTrade trade; // Magic number input int MagicNumber = 1111; input double account_balance = 1000; // Account Balance input double percentage_risk = 2.0; // How many percent of the account do you want to risk per trade? input bool allow_modify = false; // Do you allow break even modifications? input int rrr = 3; // Choose Risk Reward Ratio int rsi_handle; double rsi_buffer[]; double open[]; double close[]; double high[]; double low[]; datetime time[]; double max_high = 0; datetime min_time1 = 0; double min_low = 0; datetime min_time2 = 0; datetime time_low = 0; datetime times_high = 0; string high_obj_name = "High_Line"; string low_obj_name = "Low_Line"; long chart_id; double take_profit; double ask_price = 0; double lot_size; double risk_Amount; double points_risk; // Risk modification double positionProfit = 0; double positionopen = 0; double positionTP = 0; double positionSL = 0; double modifyLevel = 0.0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int currBars = iBars(_Symbol,_Period); static int prevBars = currBars; if(prevBars == currBars) return; prevBars = currBars; // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); static double max_high_static = max_high; static datetime min_time1_static = min_time1; static double min_low_static = min_low; static datetime min_time2_static = min_time2; //GETTING TOTAL POSITIONS int totalPositions = 0; for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { totalPositions++; } } } ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(totalPositions < 1) { if(((low[0] < min_low_static && close[0] > min_low_static && close[0] > open[0]) || (low[1] < min_low_static && close[0] > min_low_static && close[0] > open[0]) || (low[2] < min_low_static && close[0] > min_low_static && close[0] > open[0] && close[1] < open[1]))) { take_profit = (close[0] - low[0]) * rrr + close[0]; points_risk = close[0] - low[0]; double riskAmount = account_balance * (percentage_risk / 100.0); double minus = NormalizeDouble(close[0] - low[0],5); lot_size = CalculateLotSize(_Symbol, riskAmount, minus); trade.Buy(lot_size,_Symbol,ask_price, low[0], take_profit); } else if(((high[0] > max_high_static && close[0] < max_high_static && close[0] < open[0]) || (high[1] > max_high_static && close[0] < max_high_static && close[0] < open[0]) || (high[2] > max_high_static && close[0] < max_high_static && close[0] < open[0] && close[1] > open[1]))) { take_profit = MathAbs((high[0] - close[0]) * rrr - close[0]); // Adjusted take-profit calculation points_risk = MathAbs(high[0] - close[0]); double riskAmount = account_balance * (percentage_risk / 100.0); double minus = NormalizeDouble(high[0] - close[0],5); lot_size = CalculateLotSize(_Symbol, riskAmount, minus); trade.Sell(lot_size,_Symbol,ask_price, high[0], take_profit); } } //CHART ID chart_id = ChartID(); int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] < open[i] && close[i+1] > open[i+1]) { max_high = (double)MathMax(high[i],high[i+1]); min_time1 = (datetime)MathMin(time[i],time[i+1]); break; } } int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] > open[i] && close[i+1] < open[i+1]) { min_low = (double)MathMin(low[i],low[i+1]); min_time2 = (datetime)MathMin(time[i],time[i+1]); break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { time_low = time[i+1]; break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { times_high = time[i+1]; break; } } if((total_bar_high == 0 || total_bar_high > 12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > 13) { max_high_static = 0; min_time1_static = 0; } if((total_bar_low == 0 || total_bar_low > 12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > 13) { min_low_static = 0; min_time2_static = 0; } ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,0,min_time1_static,max_high_static,TimeCurrent(),max_high_static); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,3); ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,0,min_time2_static,min_low_static,TimeCurrent(),min_low_static); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,3); if(allow_modify) { for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { positionopen = PositionGetDouble(POSITION_PRICE_OPEN); positionTP = PositionGetDouble(POSITION_TP); positionSL = PositionGetDouble(POSITION_SL); positionProfit = PositionGetDouble(POSITION_PROFIT); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionSL - positionopen) - positionopen,4)); if(ask_price <= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionopen - positionSL) + positionopen,4)); if(ask_price >= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } } } } } //+------------------------------------------------------------------+ //| Function to calculate the lot size based on risk amount and stop loss //+------------------------------------------------------------------+ double CalculateLotSize(string symbol, double riskAmount, double stopLossPips) { // Get symbol information double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); // Calculate pip value per lot double pipValuePerLot = tickValue / point; // Calculate the stop loss value in currency double stopLossValue = stopLossPips * pipValuePerLot; // Calculate the lot size double lotSize = riskAmount / stopLossValue; // Round the lot size to the nearest acceptable lot step double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); lotSize = MathFloor(lotSize / lotStep) * lotStep; // Ensure the lot size is within the allowed range double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(lotSize < minLot) lotSize = minLot; if(lotSize > maxLot) lotSize = maxLot; return lotSize; }
Explicación:
input double account_balance = 1000; // Account Balance input double percentage_risk = 2.0; // How many percent of the account do you want to risk per trade? input bool allow_modify = false; // Do you allow break even modifications? input int rrr = 3; // Choose Risk Reward Ratio
Estos son los parámetros clave de la gestión del riesgo en una estrategia comercial. account_balance — define el saldo de cuenta fijo que se utiliza para calcular la cantidad de riesgo por transacción, garantizando la estabilidad. percentage_risk — define el porcentaje del saldo de la cuenta que un tráder está dispuesto a arriesgar en cada transacción. Ayuda a controlar la exposición y a mantener un nivel de riesgo estable. rrr (Risk-Reward Ratio) — establece la relación riesgo-recompensa deseada para que la estrategia busque más beneficios que pérdidas. allow_modify — controla si el stop loss debe moverse al punto de equilibrio si la transacción se mueve en una dirección rentable. Si el parámetro está activado, bloquearemos los beneficios y reduciremos el riesgo en las transacciones positivas. En conjunto, estos parámetros crean un enfoque comercial disciplinado, garantizando un riesgo constante por transacción y protegiendo los beneficios.
take_profit = (close[0] - low[0]) * rrr + close[0]; points_risk = close[0] - low[0]; double riskAmount = account_balance * (percentage_risk / 100.0); double minus = NormalizeDouble(close[0] - low[0],5); lot_size = CalculateLotSize(_Symbol, riskAmount, minus); trade.Buy(lot_size,_Symbol,ask_price, low[0], take_profit);
- take_profit = (close[0] - low[0]) * rrr + close[0]; calcula el nivel de take profit multiplicando la distancia desde el cierre de la vela actual hasta su mínimo por la relación riesgo-recompensa (rrr) y lo añade al precio de cierre.
- points_risk = close[0] - low[0]; define la distancia entre el precio de entrada (close) y el stop loss (low).
- riskAmount = account_balance * (percentage_risk / 100.0); calcula la cantidad de riesgo en términos monetarios usando como base el saldo de la cuenta y el porcentaje de riesgo especificado.
- lot_size = CalculateLotSize(_Symbol, riskAmount, minus); calcula el tamaño del lote en función del valor del riesgo y la distancia al stop loss para que la transacción se corresponda con los parámetros de gestión del riesgo indicados.
Para las transacciones de venta:
- take_profit = MathAbs((high[0] - close[0]) * rrr - close[0]); calcula el take profit usando la distancia desde el cierre de la vela hasta su máximo multiplicado por RRR.
- points_risk = MathAbs(high[0] - close[0]); define la cantidad de riesgo desde el punto de entrada hasta el stop loss (máximo de la vela).
- El resto de la lógica (el cálculo del importe del riesgo y del tamaño del lote) es idéntica a la lógica de las transacciones de compra, lo cual garantiza una gestión unificada del riesgo.
if(allow_modify) { for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { positionopen = PositionGetDouble(POSITION_PRICE_OPEN); positionTP = PositionGetDouble(POSITION_TP); positionSL = PositionGetDouble(POSITION_SL); positionProfit = PositionGetDouble(POSITION_PROFIT); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionSL - positionopen) - positionopen,4)); if(ask_price <= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionopen - positionSL) + positionopen,4)); if(ask_price >= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } } } }
El código también implementa la lógica de cambio de posiciones activas, añadiendo protección a través de la ausencia de pérdidas. Esto es lo que hace este código:
Comprueba el permiso de modificación (allow_modify):
- El bloque if(allow_modify) garantiza que la modificación solo se ejecute si el usuario la ha activado.
- El ciclo for itera todas las transacciones abiertas (PositionsTotal()). Para cada posición, se selecciona su ticket (PositionGetTicket(i)) y se carga con ayuda de PositionSelectByTicket(ticket). Obtención de parámetros de posición Para cada posición seleccionada, el programa obtiene: positionopen - precio de apertura. positionTP - Nivel de Take Profit. positionSL - nivel de Stop Loss. positionProfit - beneficio actual. Modificación de la posición de venta. Se comprueba que la posición sea POSITION_TYPE_SELL. Se calcula como la distancia absoluta entre el precio de apertura y el stop loss. Si el precio Ask actual ha superado dicha distancia, el stop loss se desplazará al punto de apertura (break-even), y el take profit no se modificará:
Selección de posiciones:
- Obtenemos el ticket de cada posición (PositionGetTicket(i)) y lo utilizamos para seleccionar la posición (PositionSelectByTicket(ticket)).
Obtención de los detalles de la posición:
Para cada posición seleccionada, obtenemos los siguientes datos:
- positionopen — precio al que se ha abierto la posición.
- positionTP — nivel de take profit de la posición.
- positionSL — nivel de stop loss de la posición.
- positionProfit — beneficio actual de la posición.
Modificación de la posición de venta:
- El código comprueba que la posición de venta sea (POSITION_TYPE_SELL).
- A continuación, el código calcula el modifyLevel como la diferencia absoluta entre el stop loss (positionSL) y el precio de apertura (positionopen), y luego comprueba si el precio de oferta actual (ask_price) ha alcanzado o superado dicho nivel.
- Si se cumple la condición, el stop loss se modificará al precio de apertura (nivel de ausencia de pérdidas), mientras que el take profit no se modificará (trade.PositionModify(ticket, positionopen, positionTP)).
- De forma similar, para una posición de compra (POSITION_TYPE_BUY), el modifyLevel se calcula como la diferencia absoluta entre el precio de apertura y el Stop Loss, y se comprueba si el precio ha superado este nivel.
- Si se cumple la condición, el stop loss se modificará al precio de apertura (nivel de ausencia de pérdidas), y el take profit no cambiará.
Conclusión
En este artículo, hemos analizado el uso de indicadores incorporados en MQL5 en un ejemplo práctico. Asimismo, hemos desarrollado un asesor experto para el trading automático utilizando como ejemplo RSI: un asesor experto con entradas y salidas basadas en señales de sobrecompra y sobreventa. Además hemos hecho hincapié en puntos importantes de este proceso, como el uso de la relación riesgo-rentabilidad (CRR) para ajustar los objetivos de rentabilidad a los riesgos calculados. También hemos considerado la posibilidad de utilizar un % de riesgo constante para cada transacción. Para que la estrategia sea más fiable y tenga más éxito, también hemos añadido una modificación del nivel de ausencia de pérdidas para bloquear los beneficios dentro de una posición. Esto permite al asesor experto adaptarse a los cambios del mercado.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16514
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- 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
Hola Oluwatosin,
He tomado nota de su solicitud.