Desarrollo de Sistemas Avanzados de Trading ICT: Implementación de señales en un indicador de Order Blocks
- Detección de bloques de órdenes (Order Blocks) basada en la profundidad del mercado
- Inicialización y finalización del evento de Profundidad de Mercado y Creación de Arrays
- Recopilación de datos de Profundidad de Mercado para la detección de volúmenes
- Estrategia para localizar Order Blocks usando la profundidad del mercado
- Creación de Buffers para el Indicador
- Modificación de la función OnInit para Configurar Buffers
- Implementación de Buffers en el indicador (2)
- Actualización de los parámetros de entrada (Inputs)
- Lógica para generación de señales del indicador
- Implementación de la Estrategia de Trading
- Configuración de niveles de Take Profit (TP) y Stop Loss (SL)
- Visualización de niveles de TP y SL en el gráfico
- Adición de Buffers para niveles de TP y SL (4)
- Finalizacion del codigo principal y limpieza
Introducción
¡Bienvenido a nuestro artículo sobre MQL5! En esta ocasión, nos enfocaremos en agregar los buffers y las señales de entrada a nuestro indicador, completando así las funcionalidades necesarias para su uso en estrategias de trading automatizadas.
Si eres nuevo en esta serie, te recomendamos revisar la primera parte, donde explicamos cómo crear el indicador desde cero y cubrir los conceptos fundamentales.
Detección de bloques de órdenes (Order Blocks) basada en la profundidad de Mercado
Nuestra lógica para identificar bloques de órdenes basados en la profundidad del mercado será la siguiente.
- Array Creation: Crearemos dos arrays para almacenar el volumen de cada vela en la profundidad de mercado. Esto nos permitirá organizar y analizar los datos de volumen de manera eficiente.
- Market Depth Data Collection: En el evento.
void OnBookEvent( )
Capturaremos cada cambio en la profundidad del mercado, registrando el nuevo volumen para mantener actualizados nuestros datos en tiempo real.
3. Rules to validate Order Blocks: Una vez almacenado el volumen en los arrays, utilizaremos estos datos junto con reglas de acción del precio para validar un bloque de órdenes (Order Block).
Reglas para el order block por profundidad del mercado
Inicialmente, al crear nuestro indicador, buscábamos bloques de órdenes (Order Blocks) en un rango de velas determinado. Sin embargo, en el caso de la profundidad de mercado, no efectuaremos la búsqueda en un rango "x". En su lugar, enfocaremos la búsqueda específicamente en la tercera vela (siendo la vela 0 la vela actual).
| Reglas | Order Block Alcista | Order Block Bajista |
|---|---|---|
| Pico de Volumen en la Vela 3: | El volumen de compra de la vela 3 debe ser mayor, en un determinado ratio, a el volumen de compra y venta de las velas 2 y 4. | Buscaremos que el volumen de venta de la vela 3 sea mayor por un ratio a el volumen de compra y venta de la vela 2 y 4. |
| 3 velas Consecutivas: | Deben aparecer 3 velas alcistas consecutivas. (velas 1, 2 y 3) | Deben aparecer 3 velas bajistas consecutivas. (velas 1, 2 y 3) |
| Cuerpo de la Vela 3: | El mínimo de la vela 2 debe ser mayor a la mitad del cuerpo de la vela 3. | El máximo de la vela 2 debe ser menor a la mitad del cuerpo de la vela 3. |
| Alto o Bajo de la Vela 3: | El máximo de la 3 vela debe ser menor al cierre de la 2 vela. | El mínimo de la vela 3 debe ser mayor al cierre de la vela 2. |
Con estas reglas, nos aseguraremos de lo siguiente:
- Imbalance de compras o ventas: Verificamos que se haya producido un desbalance significativo de compras o ventas en una vela específica, donde las órdenes de compra o venta fueron mayores, en un determinado ratio, que en la vela anterior y posterior.
- Control del cuerpo de la Vela en Imbalance: Nos aseguramos de que las órdenes no ejecutadas debido a la sobredemanda o sobreoferta no hayan sido absorbidas por la vela posterior, validando así la persistencia del bloque de órdenes.
- Movimiento Alcista o Bajista Fuerte: Confirmamos que el patrón representa un movimiento marcado, ya sea alcista o bajista, reflejando la intensidad del desbalance en la acción del precio.
Ahora con todo esto en mente pasaremos todo lo aprendido a código.
Inicialización y finalización del evento de Profundidad de Mercado y Creación de Arrays
Creacion de Arrays
Antes de utilizar el libro de órdenes, es necesario crear los arrays dinámicos que almacenarán los datos de volumen. Estas arrays serán del tipo:
long Y servirán para guardar el volumen de compra y de venta, respectivamente.
- Dirígete a la sección global del programa y declara los arrays dinámicos.
long buy_volume[]; long sell_volume[];2. Dentro del evento OnInit, redimensionaremos los arrays para que tengan un tamaño inicial de 1. Además, asignaremos el valor 0 al índice 0 de cada array.
ArrayResize(buy_volume,1); ArrayResize(sell_volume,1); buy_volume[0] = 0.0; sell_volume[0] = 0.0;
Inicialización y finalización del evento de Profundidad de Mercado:
Antes de iniciar la profundidad de mercado, crearemos una variable global que indicará si esta función está disponible. Esto nos permitirá evitar el uso de:
INIT_FAILEDYa que no todos los símbolos en algunos brókers proporcionan volumen negociado en la profundidad de mercado. Así, el indicador no dependerá de un bróker que necesariamente ofrezca esta función. - Para que usted sepa si el símbolo que quiere operar tiene la profundidad del mercado con volumen negociado, puede seguir los siguientes pasos:
1. Haga clic en la esquina superior izquierda de su gráfico, en el siguiente cuadro: ![]()
2. Verifique si su símbolo tiene disponible el volumen para la profundidad de mercado, le saldrá algo como las siguientes imágenes.
Símbolo con volumen en la profundidad de mercado:
Símbolo sin volumen en la profundidad de mercado:
El volumen en la profundidad de mercado, como dije, no está disponible en todos los símbolos; esto también dependerá del bróker con el que trabajes.
Pasemos con la inicialización y finalización de la profundidad de mercado.
1. Variable Global de Control
Definimos una variable booleana global para marcar el estado de disponibilidad de la profundidad de mercado:
bool use_market_book = true; //true by default
Esta variable se inicializa en true, pero podremos cambiarla si la apertura de la profundidad del mercado falla.
2. Inicialización de la profundidad de Mercado
Para inicializar la profundidad de mercado, utilizamos la función:
MarketBookAdd() Que abre la profundidad de mercado (Depth of Market) para un símbolo especificado. La función requiere el símbolo actual:
_Symbol Como argumento.
En el evento OnInit, verificamos si la inicialización es exitosa.
if(!MarketBookAdd(_Symbol)) //Verify initialization of the order book for the current symbol { Print("Error Open Market Book: ", _Symbol, " LastError: ", _LastError); //Print error in case of failure use_market_book = false; //Mark use_market_book as false if initialization fails }
3. Finalización de la profundidad de Mercado
En el evento OnDeinit, liberamos la profundidad de mercado usando:
MarketBookRelease() Verificamos el cierre y mostramos un mensaje según el resultado:
//--- if(MarketBookRelease(_Symbol)) //Verify if closure was successful Print("Order book successfully closed for: " , _Symbol); //Print success message if so else Print("Order book closed with errors for: " , _Symbol , " Last error: " , GetLastError()); //Print error message with code if not
Recopilación de datos de la profundidad del Mercado para la detección de volúmenes de Arrays
Con la profundidad de mercado iniciada, podemos comenzar a recopilar datos relevantes. Para ello, crearemos el evento OnBookEvent, el cual se ejecuta cada vez que ocurre un cambio en la profundidad de mercado.
- Creación del evento OnBookEvent:
void OnBookEvent(const string& symbol)2. Verificación del símbolo y disponibilidad de Profundidad de Mercado:
if(symbol !=_Symbol || use_market_book == false) return; // Exit the event if conditions are not met
Con esta verificación lista, presentamos el código completo del evento OnBookEvent:
void OnBookEvent(const string& symbol) { if(symbol !=_Symbol || use_market_book == false) return; // Define array to store Market Book data MqlBookInfo book_info[]; // Retrieve Market Book data bool book_count = MarketBookGet(_Symbol,book_info); // Verify if data was successfully obtained if(book_count == true) { // Iterate through Market Book data for(int i = 0; i < ArraySize(book_info); i++) { // Check if the record is a buy order (BID) if(book_info[i].type == BOOK_TYPE_BUY || book_info[i].type == BOOK_TYPE_BUY_MARKET) { buy_volume[0] += book_info[i].volume; } // Check if the record is a sell order (ASK) if(book_info[i].type == BOOK_TYPE_SELL || book_info[i].type == BOOK_TYPE_SELL_MARKET) { sell_volume[0] += book_info[i].volume; } } } else { Print("No Market Book data retrieved."); } }
Este código realiza lo siguiente:
- Obtención de Volumen: Cada vez que ocurre un cambio en la profundidad de mercado, OnBookEvent recopila el volumen de la última orden registrada.
- Actualización de Arrays: Suma el volumen de compra y venta en el índice 0 de los arrays buy_volume y sell_volume, respectivamente.
Para que el array acumule el volumen de profundidad de mercado en cada nueva vela y guarde el historial en serie, el código debe ajustar el volumen actual en el índice 0 y mover el resto de los datos hacia adelante, asegurando que los datos no se acumulen en exceso y se mantenga un tamaño constante (por ejemplo, 30 elementos).
1. Verificación de nueva vela y validación del Contador de Aperturas de Velas (mayor a 1)
Para evitar falsos positivos al iniciar el programa y asegurarnos de que solo se actualicen los arrays cuando se abre una nueva vela (y después de al menos una apertura), implementaremos la verificación de la variable counter junto con new_vela. Esto garantiza que la actualización de arrays ocurra únicamente cuando haya realmente nueva información.
Declaración de Variables EstáticasDeclaramos counter como una variable estática para que persista entre las llamadas de OnCalculate. La variable new_vela debería indicar si se ha abierto una nueva vela.
static int counter = 0;
Condición de Verificación de Nueva Vela y Contador
Verificamos que el counter sea mayor a 1, que new_vela sea true y el uso del mercado sea válido. Solo si se cumplen las condiciones, redimensionaremos el array y a desplazar los elementos. Este control evita redimensionamientos prematuros y asegura que el array se actualice únicamente cuando haya datos válidos y el market book disponga volumen de negociación para el símbolo actual.
if(counter > 1 && new_vela == true && use_market_book == true)
Actualización del Contador
Aumentamos el contador en 1 cada vez que se detecta una nueva vela.
counter++;
2. Control de Tamaño del Array
Verificamos que el array no exceda un tamaño máximo de 30 elementos. Si es así, lo redimensionamos a 30 para eliminar el elemento más antiguo:
if(ArraySize(buy_volume) >= 30) { ArrayResize(buy_volume, 30); // Keep buy_volume size at 30 ArrayResize(sell_volume, 30); // Keep sell_volume size at 30 }
3. Redimensionar para ingresar nuevos valores
Añadimos un espacio extra al array para almacenar el nuevo volumen en la posición inicial.
ArrayResize(buy_volume, ArraySize(buy_volume) + 1); ArrayResize(sell_volume, ArraySize(sell_volume) + 1);
4. Desplazamiento de Elementos
Movemos todos los elementos del array en una posición hacia adelante. Esto asegura que los datos nuevos siempre se guarden en el índice 0, y los más antiguos se vayan desplazando hacia los índices más altos.
for(int i = ArraySize(buy_volume) - 1; i > 0; i--) { buy_volume[i] = buy_volume[i - 1]; sell_volume[i] = sell_volume[i - 1]; }
5. Verificación de volúmenes
Imprimimos el volumen de compra y venta en la posición 1 del array para verificar el volumen de la última vela.
Print("Buy volume of the last candle: ", buy_volume[1]); Print("Sell volume of the last candle: ", sell_volume[1]);
6. Reinicio de Volúmenes
Restablecemos el índice 0 de ambos arrays a 0, para que comience a acumular el volumen de la nueva vela.
buy_volume[0] = 0; sell_volume[0] = 0;
7. Condición para evitar errores en caso de datos inconsistentes en el market book
He añadido esta condición para desactivar use_market_book automáticamente si los valores de buy_volume y sell_volume en las posiciones recientes (índices 3, 2 y 1) son todos ceros. Este ajuste es necesario porque, aunque el símbolo pueda tener un market book en el mercado en vivo, al ejecutarse en el probador de estrategias, también se detecta como si tuviera market book. Sin embargo, el array puede no llenarse correctamente debido a la falta de cambios en la profundidad de mercado en el modo de prueba, lo que da como resultado en ceros en el array y puede llevar a que el indicador almacene información incorrecta.
Esta verificación evita que el indicador procese datos sin sentido y asegura que solo se utilice use_market_book cuando el market book contiene valores válidos.
if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4) { if(buy_volume[3] == 0 && sell_volume[3] == 0 && buy_volume[2] == 0 && sell_volume[2] == 0 && buy_volume[1] == 0 && sell_volume[1] == 0) use_market_book = false; }
Integrado, el código quedaría así:
if(counter > 1 && new_vela == true && use_market_book == true) { if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4) { if(buy_volume[3] == 0 && sell_volume[3] == 0 && buy_volume[2] == 0 && sell_volume[2] == 0 && buy_volume[1] == 0 && sell_volume[1] == 0) use_market_book = false; } // If array size is greater than or equal to 30, resize to maintain a fixed length if(ArraySize(buy_volume) >= 30) { ArrayResize(buy_volume, 30); // Ensure buy_volume does not exceed 30 elements ArrayResize(sell_volume, 30); // Ensure sell_volume does not exceed 30 elements } ArrayResize(buy_volume,ArraySize(buy_volume)+1); ArrayResize(sell_volume,ArraySize(sell_volume)+1); for(int i = ArraySize(buy_volume) - 1; i > 0; i--) { buy_volume[i] = buy_volume[i - 1]; sell_volume[i] = sell_volume[i - 1]; } // Reset volumes at index 0 to begin accumulating for the new candlestick buy_volume[0] = 0; sell_volume[0] = 0; }
Estrategia para localizar Order Blocks usando la profundidad de mercado
La estrategia seguirá la misma lógica que usamos anteriormente, pero con una diferencia importante: еn lugar de utilizar bucles, realizaremos las verificaciones directamente sobre la vela 3. La lógica general se mantiene, verificamos ciertas condiciones, identificamos la vela más cercana (según el tipo de bloque de órdenes) y luego asignamos los valores correspondientes a la estructura y agregamos el bloque de órdenes al array. Aquí aplicaremos el mismo proceso, pero de forma más simple.
Comencemos creando las estructuras que almacenarán la información de los bloques de órdenes:
OrderBlocks newVela_Order_block_Book_bajista; OrderBlocks newVela_Order_block_Book;
1. Condiciones Iniciales
Primero, verificamos que el tamaño de los arrays buy_volume y sell_volume sea de al menos 5 elementos. Esto garantiza que tenemos suficiente historial para el análisis. También nos aseguramos de que use_market_book esté activo para procesar la profundidad de mercado.
if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
2. Definición de Variables de Control
Definimos la variable case_book para señalar si se cumple una condición particular de volumen. Establecemos el ratio en (1,4), que servirá como un factor de comparación para detectar aumentos significativos en el volumen de compra.
bool case_book = false; double ratio = 1.4;
3. Condición de Volumen de Compra (Case Book)
Aquí verificamos si el volumen de compra en el índice 3 es significativamente mayor al volumen en los índices 2 y 4, tanto en el lado de compra como de venta, utilizando el ratio como multiplicador. Si esta condición se cumple, se activa case_book.
Caso Alcista:
if(buy_volume[3] > buy_volume[4] * ratio && buy_volume[3] > buy_volume[2] * ratio && buy_volume[3] > sell_volume[4] * ratio && buy_volume[3] > sell_volume[2] * ratio) { case_book = true; }Caso Bajista:
if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio && sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio) { case_book = true; }
4. Cálculo del cuerpo de la Vela
Calculamos el tamaño del cuerpo de la vela (body_tree) en el índice 3, restando su precio de apertura al precio de cierre.
double body_tree = closeArray[3] - openArray[3];
double body_tree = openArray[3] - closeArray[3]; 5. Verificación de Condiciones de Precios para Configuración Alcista
Evaluamos las condiciones mencionadas al inicio (revisar la tabla del inicio).
Caso Alcista:
if(lowArray[2] > ((body_tree * 0.5) + openArray[3]) && highArray[3] < closeArray[2] && closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])
Caso Bajista:
if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] && closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])
6. Identificación de velas Alcistas Anteriores
Llamamos a la función FindFurthestAlcista, que busca la vela alcista más lejana dentro de un rango de 20 velas desde el índice 3. Esto nos ayuda a encontrar una vela de referencia para una configuración alcista sólida. Si se encuentra una vela alcista, el índice es mayor a 0, lo que nos permite continuar.
Caso Alcista:
int furthestAlcista = FindFurthestAlcista(Time[3], 20); if(furthestAlcista > 0)
7. Asignación de Valores para Bloque de Orden
Si se cumplen todas las condiciones, definimos el bloque de orden (newVela_Order_block_Book o newVela_Order_block_Book_bajista) con los valores de la vela encontrada.
Caso Alcista:
Print("Case Book Found"); datetime time1 = Time[furthestAlcista]; double price2 = openArray[furthestAlcista]; double price1 = lowArray[furthestAlcista]; //Assign the above variables to the structure newVela_Order_block_Book.price1 = price1; newVela_Order_block_Book.time1 = time1; newVela_Order_block_Book.price2 = price2; newVela_Order_block_Book.mitigated = false; newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1); AddIndexToArray_alcistas(newVela_Order_block_Book);
Caso bajista:
Print("Case Book Found"); datetime time1 = Time[furthestBajista]; double price1 = closeArray[furthestBajista]; double price2 = lowArray[furthestBajista]; //Assign the above variables to the structure newVela_Order_block_Book_bajista.price1 = price1; newVela_Order_block_Book_bajista.time1 = time1; newVela_Order_block_Book_bajista.price2 = price2; newVela_Order_block_Book_bajista.mitigated = false; newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1); AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);
Código Completo:
if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true) { bool case_book = false; double ratio = 1.4; if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio && sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio) { case_book = true; } double body_tree = openArray[3] - closeArray[3]; if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] && closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1]) { int furthestBajista = FindFurthestBajista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlesticks before "one candle" if(furthestBajista > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle". { Print("Case Book Found"); datetime time1 = Time[furthestBajista]; double price1 = closeArray[furthestBajista]; double price2 = lowArray[furthestBajista]; //Assign the above variables to the structure newVela_Order_block_Book_bajista.price1 = price1; newVela_Order_block_Book_bajista.time1 = time1; newVela_Order_block_Book_bajista.price2 = price2; newVela_Order_block_Book_bajista.mitigated = false; newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1); AddIndexToArray_bajistas(newVela_Order_block_Book_bajista); } } } //-------------------- Bullish -------------------- if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true) { bool case_book = false; double ratio = 1.4; if(buy_volume[3] > buy_volume[4]*ratio && buy_volume[3] > buy_volume[2]*ratio && buy_volume[3] > sell_volume[4]*ratio && buy_volume[3] > sell_volume[2]*ratio) { case_book = true; } double body_tree = closeArray[3] - openArray[3]; if(lowArray[2] > ((body_tree * 0.5)+openArray[3]) && highArray[3] < closeArray[2] && closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1]) { int furthestAlcista = FindFurthestAlcista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlessticks before "one candle" if(furthestAlcista > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle". { Print("Case Book Found"); datetime time1 = Time[furthestAlcista]; //assign the index time of furthestAlcista to the variable time1 double price2 = openArray[furthestAlcista]; //assign the open of furthestAlcista as price 2 (remember that we draw it on a bearish candlestick most of the time) double price1 = lowArray[furthestAlcista]; //assign the low of furthestAlcista as price 1 //Assign the above variables to the structure newVela_Order_block_Book.price1 = price1; newVela_Order_block_Book.time1 = time1; newVela_Order_block_Book.price2 = price2; newVela_Order_block_Book.mitigated = false; newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1); AddIndexToArray_alcistas(newVela_Order_block_Book); } } }
Creación de Buffers para el indicador
Para crear y configurar los buffers de nuestro indicador de bloques de órdenes (order blocks) en MQL5, iniciaremos definiendo dos buffers y dos gráficos (plots) globales para almacenar y mostrar los niveles de precios de los bloques alcistas y bajistas.
1. Declaración de Buffers y Plots
Vamos a declarar dos buffers en la parte global del programa, para que puedan almacenar los datos de precio de los bloques de órdenes. Además, añadimos dos gráficos (plots) que servirán para visualizar los bloques de órdenes en el gráfico.
#property indicator_buffers 2 #property indicator_plots 2 #property indicator_label1 "Bullish Order Block" #property indicator_label2 "Bearish Order Block"
2. Crear Arrays Dinámicos para los Buffers
Declaramos dos arrays dinámicos, buyOrderBlockBuffer y sellOrderBlockBuffer, para almacenar los precios correspondientes a los bloques de órdenes de compra (alcistas) y venta (bajistas), respectivamente. Estos arrays estarán asignados a los buffers, permitiendo que los datos de los bloques de órdenes se visualicen en el gráfico.
//--- Define the buffers double buyOrderBlockBuffer[]; // Buffer for bullish order blocks double sellOrderBlockBuffer[]; // Buffer for bearish order blocks
Descripción:
- buyOrderBlockBuffer: Almacena los niveles de precios de los bloques de órdenes de compra (bullish order blocks) y está diseñado para los puntos alcistas donde el precio podría encontrar soporte.
- sellOrderBlockBuffer: Almacena los niveles de precios de los bloques de órdenes de venta (bearish order blocks) y representa los puntos bajistas donde el precio podría encontrar resistencia.
Modificación de la Función OnInit para Configurar Buffers
En esta sección, ajustaremos la función OnInit para configurar los buffers del indicador, asignando los arrays de bloques de órdenes alcistas y bajistas como buffers del indicador. Esto permitirá que el indicador almacene y muestre los datos en el gráfico correctamente.
Pasos a seguir:
1. Asignación de Buffers de Datos con SetIndexBuffer
Primero, en OnInit, asignamos las arrays buyOrderBlockBuffer y sellOrderBlockBuffer a los buffers del indicador usando SetIndexBuffer. Esto asegura que estos arrays puedan almacenar y representar datos en el gráfico.
//--- Assign data buffers to the indicator SetIndexBuffer(0, buyOrderBlockBuffer, INDICATOR_DATA); SetIndexBuffer(1, sellOrderBlockBuffer, INDICATOR_DATA)
2. Configuración de los Buffers en Serie y Relleno con Valores Vacíos
Para que los datos se muestren en orden cronológico inverso (como una serie temporal), configuramos las arrays como series. Además, inicializamos ambos buffers con EMPTY_VALUE para evitar que muestren datos incorrectos hasta que se calculen los valores reales.
ArraySetAsSeries(buyOrderBlockBuffer, true); ArraySetAsSeries(sellOrderBlockBuffer, true); ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
Implementación de Buffers en el Indicador (2)
En esta sección, asignaremos los precios de los bloques de órdenes alcistas y bajistas a los buffers del indicador. Estos buffers permiten que los datos estén disponibles en cada índice correspondiente al momento (time1) de cada bloque de órdenes.1. Asignación de Precios para Bloques de Órdenes Alcistas
Dentro del ciclo donde evaluamos cada bloque alcista en el array ob_alcistas, añadimos la siguiente línea de código para almacenar el precio price2 en el buffer buyOrderBlockBuffer. Utilizamos la función iBarShift para obtener el índice exacto en el gráfico donde time1 coincide con el tiempo del bloque de órdenes.
buyOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_alcistas[i].time1)] = ob_alcistas[i].price2;
Aquí, el valor de price2 del bloque alcista se asigna al índice correspondiente en buyOrderBlockBuffer, de modo que el buffer refleje el nivel de precio del bloque en el gráfico.
2. Asignación de Precios para Bloques de Órdenes Bajistas
De forma similar, asignamos el precio price2 de cada bloque bajista al buffer sellOrderBlockBuffer. Esto se logra iterando a través del array ob_bajistas y estableciendo el valor del precio en el índice correspondiente.
sellOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_bajistas[i].time1)] = ob_bajistas[i].price2;Resumiendo:
- iBarShift se utiliza para localizar el índice exacto en el que el tiempo del bloque coincide con la posición en el gráfico.
- buyOrderBlockBuffer y sellOrderBlockBuffer reciben los valores de price2, permitiendo que los precios se registren en el momento adecuado y estén disponibles para su uso en el gráfico y en cálculos adicionales del indicador.
Actualización de los parámetros de entrada (Inputs)
En esta sección, configuraremos los parámetros de entrada (inputs) para que el usuario pueda personalizar el estilo de cálculo del Take Profit (TP) y Stop Loss (SL) de acuerdo a sus preferencias. Para lograr esto, crearemos una enumeración que permite seleccionar entre dos opciones: ATR (Average True Range) o POINT (puntos fijos).
Enumeración ENUM_TP_SL_STYLE
La enumeración ENUM_TP_SL_STYLE permite que el usuario elija entre dos modos de cálculo para TP y SL.
- ATR: Define los valores de TP y SL según el rango promedio de movimiento del precio, ajustándolos automáticamente de acuerdo con la volatilidad actual del mercado.
- POINT: Define los valores de TP y SL en puntos fijos, según un valor definido por el usuario.
enum ENUM_TP_SL_STYLE
{
ATR,
POINT
};
Explicación:
-
ATR: Al seleccionar esta opción, el usuario establece un multiplicador que determinará la distancia del TP y SL en relación con el ATR. Un valor más alto en el multiplicador aumentará la distancia del TP y SL en función de la volatilidad actual.
-
POINT: En esta opción, el usuario define manualmente el TP y SL en puntos fijos. Esto permite establecer niveles estáticos de TP y SL, independientemente de la volatilidad.
Ahora, siguiendo con los parámetros del indicador, estructuraremos los parámetros de entrada del indicador mediante el uso de sinput y agruparemos las configuraciones en secciones. Esto permitirá una visualización más clara y organizada de los parámetros en la interfaz, facilitando la configuración para el usuario.
1. Sección de estrategia
Primero, creamos una sección de estrategia que agrupa la opción de estilo para el cálculo de Take Profit (TP) y Stop Loss (SL):
sinput group "-- Strategy --" input ENUM_TP_SL_STYLE tp_sl_style = POINT; // TP and SL style: ATR or fixed points
En esta sección, tp_sl_style permitirá al usuario elegir si desea calcular TP y SL basándonos en el ATR (Average True Range) o a puntos fijos.
2. Configuración de TP y SL según el Método Seleccionado
Para acomodar las configuraciones específicas de cada método, añadimos dos grupos adicionales: uno para el método ATR y otro para puntos fijos.
Grupo ATR: Aquí incluimos dos variables input del tipo double que permiten al usuario especificar los multiplicadores de ATR, ajustando así el rango de TP y SL en función de la volatilidad.
sinput group " ATR " input double Atr_Multiplier_1 = 1.5; // Multiplier for TP input double Atr_Multiplier_2 = 2.0; // Multiplier for SL
Grupo POINT: En este grupo, añadimos dos variables input del tipo int para definir los puntos fijos de TP y SL, lo que permite un control manual y específico de estas distancias.
sinput group " POINT " input int TP_POINT = 500; // Fixed points for TP input int SL_POINT = 275; // Fixed points for SL
Con esta organización, los parámetros quedan bien ordenados y clasificados, lo que facilita su uso y mejora la claridad en la configuración del indicador. El usuario podrá ajustar el estilo de TP y SL de manera intuitiva, eligiendo entre configuraciones automáticas basadas en ATR o configuraciones manuales en puntos.
Código Completo de los parámetros:
sinput group "--- Order Block Indicator settings ---" sinput group "-- Order Block --" input int Rango_universal_busqueda = 500; input int Witdth_order_block = 1; input bool Back_order_block = true; input bool Fill_order_block = true; input color Color_Order_Block_Bajista = clrRed; input color Color_Order_Block_Alcista = clrGreen; sinput group "-- Strategy --" input ENUM_TP_SL_STYLE tp_sl_style = POINT; sinput group " ATR " input double Atr_Multiplier_1 = 1.5; input double Atr_Multiplier_2 = 2.0; sinput group " POINT " input int TP_POINT = 500; input int SL_POINT = 275;
Lógica para generación de señales del indicador
Para la generación de señales de compra o venta, se utilizan dos variables estáticas:
| Variable | Descripción |
|---|---|
| time_ y time_b | Almacena el tiempo en el que se mitiga el order block y le suma 5 velas (en segundos) para su vencimiento. |
| buscar_oba y buscar_obb | Controla la búsqueda de nuevos order blocks mitigados. Se activa o desactiva según las condiciones. |
Proceso de generación de Señales
Detección de un Order Block mitigado:- Cuando un order block se mitiga, time_ se asigna con el tiempo actual más un margen de 5 velas.
- El buscador se establece en falso para detener la búsqueda mientras se validan las condiciones de la señal.
- Las condiciones de compra o venta se evalúan en función de la media móvil exponencial (EMA) y el tiempo de mitigación de time_.
| Tipo de señal | Condiciones de EMA | Condiciones de tiempo |
|---|---|---|
| Compra | La EMA de 30 periodos debe estar por debajo del cierre de la vela 1. | time_ debe ser mayor al tiempo actual. |
| Venta | La EMA de 30 periodos debe estar por encima del cierre de la vela 1. | time_b debe ser mayor al tiempo actual. |
Nota: Estas condiciones aseguran que la señal se genere dentro de un margen de tiempo de 5 velas tras la mitigación del order block.
Acciones en caso de cumplimiento o incumplimiento:
| Estado | Acción |
|---|---|
| Cumplimiento | Se llenan los buffers de take profit (TP) y stop loss (SL) para ejecutar la operación correspondiente. |
| Incumplimiento | Buscador se restablece a true y (time_ y time_b) se reinician a 0, permitiendo que la búsqueda de nuevos order blocks se reanude (en caso el tiempo máximo es superado). |
Mapa de flujo:
Compras

Ventas

Implementación de la Estrategia de Trading
Antes de empezar, crearemos el manejador de la media móvil exponencial.
Creamos las variables de alcance global (array y manejador):
int hanlde_ma; double ma[];
En OnInit inicializamos el manejador y comprobamos si se le asignó un valor.
hanlde_ma = iMA(_Symbol,_Period,30,0,MODE_EMA,PRICE_CLOSE); if(hanlde_ma == INVALID_HANDLE) { Print("The EMA indicator is not available. Failure: ", _LastError); return INIT_FAILED; }Declaramos variables estáticas para controlar el estado de la búsqueda y el tiempo de activación de OB, diferenciando entre escenarios de compra y venta.
//Variables for buy static bool buscar_oba = true; static datetime time_ = 0; //Variables for sell static bool buscar_obb = true; static datetime time_b = 0;
Luego haces el bucle para encontrar order blocks mitigados (similar como hicimos en el anterior artículo para las alertas):
Empezaremos añadiendo las condiciones.
//Bullish case if(buscar_oba == true) //Bearish case if(buscar_obb == true)
// Bearish case for(int i = 0; i < ArraySize(ob_bajistas); i++) { if(ob_bajistas[i].mitigated == true && !Es_Eliminado_PriceTwo(ob_bajistas[i].name, pricetwo_eliminados_obb) && ObjectFind(ChartID(), ob_bajistas[i].name) >= 0) { Alert("The bearishorder block is being mitigated: ", TimeToString(ob_bajistas[i].time1)); buscar_obb = false; // Pause search time_b = iTime(_Symbol,_Period,1); // Record the mitigation time Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name); break; } } // Bullish case for(int i = 0; i < ArraySize(ob_alcistas); i++) { if(ob_alcistas[i].mitigated == true && !Es_Eliminado_PriceTwo(ob_alcistas[i].name, pricetwo_eliminados_oba) && ObjectFind(ChartID(), ob_alcistas[i].name) >= 0) { Alert("The bullish order block is mitigated: ", TimeToString(ob_alcistas[i].time1)); time_ = iTime(_Symbol,_Period,0); Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name); buscar_oba = false; // Pause search break; } }
El siguiente paso es detectar si un OB ha sido mitigado, o sea, si el precio ha interactuado con él. Si se encuentra un OB mitigado, se registra su tiempo y se pausa la búsqueda. Esto se hace para ambos escenarios, alcista y bajista.
// Buy if(buscar_oba == false && time_ > 0 && new_vela) { /* Code for Buy */ } // Sell if(buscar_obb == false && time_b > 0 && new_vela) { /* Code for Sell */ }
Esta sección asegura que el sistema deje de buscar una vez que detecta una mitigación, evitando duplicación de señales.
Condición Inicial para realizar una operación
La estrategia usa condiciones específicas para activar la búsqueda de señales de compra o venta, una vez que se ha mitigado un OB y mientras no se haya superado el tiempo máximo de espera.
// Buy double close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits); datetime max_time_espera = time_ + (PeriodSeconds() * 5); if(close_ > ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) { // Code for Buy... } // Sell close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits); max_time_espera = time_b + (PeriodSeconds() * 5); if(close_ < ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) { // Code for Sell... }
En estas condiciones:
- buscar_oba o buscar_obb deben estar en falso (confirmando una mitigación previa).
- time_ o time_b deben tener un valor mayor a 0, indicando que hay un tiempo registrado.
- new_vela verifica que se esté en una nueva vela, ayudando a evitar decisiones repetitivas.
Validación de condiciones de compra o venta
Para establecer las condiciones necesarias, primero necesitamos una variable que almacene el tiempo máximo de espera. Luego, es esencial conocer el valor de cierre de la vela 1 y su EMA (media móvil exponencial). Para obtener el cierre, utilizaremos la función iClose, y almacenaremos los valores de la EMA en un array que contenga toda la serie histórica de la media móvil.
// Reset for Buy if(iTime(_Symbol,_Period,0) > max_time_espera) { time_ = 0; buscar_oba = true; } // Reset for Sell if(iTime(_Symbol,_Period,0) > max_time_espera) { time_b = 0; buscar_obb = true; }
Reinicio de búsqueda de Order Blocks
Finalmente, si el tiempo de espera máximo se supera sin que las condiciones se cumplan, el código reinicia la búsqueda para permitir la detección de nuevos OB:
void GetTP_SL(double price_open_position, ENUM_POSITION_TYPE type, double &tp1, double &tp2, double &sl1, double &sl2)
Ahora nos faltaría una función para dibujar los tp y sl además de agregarlo a buffers eso haremos ahora, luego terminaremos este código actual.
En las siguientes secciones veremos eso.
Configuración de niveles de Take Profit (TP) y Stop Loss (SL)
En esta sección, desarrollaremos la función GetTP_SL, que calculará los valores de TP y SL en función de dos métodos:
Usando el ATR (Average True Range) o puntos fijos, tal como mencionamos anteriormente en la configuración de los inputs.
1: Definición de la función
La función GetTP_SL recibirá como parámetros el precio de apertura de la posición, el tipo de posición (ENUM_POSITION_TYPE), y referencias para los niveles de TP y SL (tp1, tp2, sl1 y sl2), donde se almacenarán los valores calculados.
double atr[]; ArraySetAsSeries(atr, true); CopyBuffer(atr_i, 0, 0, 1, atr);
2: Obtención del ATR
Para calcular los niveles basados en ATR, primero necesitamos un array que almacene el valor del ATR de la última vela. Usamos CopyBuffer para llenar el array atr con el valor actual.
if (type == POSITION_TYPE_BUY) { sl1 = price_open_position - (atr[0] * Atr_Multiplier_1); sl2 = price_open_position - (atr[0] * Atr_Multiplier_2); tp1 = price_open_position + (atr[0] * Atr_Multiplier_1); tp2 = price_open_position + (atr[0] * Atr_Multiplier_2); } if (type == POSITION_TYPE_SELL) { sl1 = price_open_position + (atr[0] * Atr_Multiplier_1); sl2 = price_open_position + (atr[0] * Atr_Multiplier_2); tp1 = price_open_position - (atr[0] * Atr_Multiplier_1); tp2 = price_open_position - (atr[0] * Atr_Multiplier_2); }
3: Cálculo de TP y SL basado en ATR
Cuando tp_sl_style esté configurado en ATR, calcularemos los niveles de TP y SL multiplicando el valor de ATR por los multiplicadores definidos (Atr_Multiplier_1 y Atr_Multiplier_2). Luego, sumamos o restamos estos valores al precio de apertura, dependiendo del tipo de posición.
if (type == POSITION_TYPE_BUY) { sl1 = price_open_position - (SL_POINT * _Point); sl2 = price_open_position - (SL_POINT * _Point * 2); tp1 = price_open_position + (TP_POINT * _Point); tp2 = price_open_position + (TP_POINT * _Point * 2); } if (type == POSITION_TYPE_SELL) { sl1 = price_open_position + (SL_POINT * _Point); sl2 = price_open_position + (SL_POINT * _Point * 2); tp1 = price_open_position - (TP_POINT * _Point); tp2 = price_open_position - (TP_POINT * _Point * 2); }
4: Cálculo de TP y SL basado en puntos
Cuando tp_sl_style esté configurado en POINT, sumaremos o restaremos los puntos especificados (TP_POINT y SL_POINT), multiplicados por el valor de un punto del símbolo actual (_Point), al precio de apertura. Esta es una alternativa sencilla al cálculo basado en ATR.
bool TrendCreate(long chart_ID, // Chart ID string name, // Line name int sub_window, // Subwindow index datetime time1, // Time of the first point double price1, // Price of the first point datetime time2, // Time of the second point double price2, // Price of the second point color clr, // Line color ENUM_LINE_STYLE style, // Line style int width, // Line width bool back, // in the background bool selection // Selectable form moving ) { ResetLastError(); if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2)) { Print(__FUNCTION__, ": ¡Failed to create trend line! Error code = ",GetLastError()); return(false); } ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr); ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style); ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width); ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection); ChartRedraw(chart_ID); return(true); }
Visualización de niveles de TP y SL en el gráfico
Crearemos una función que con los valores del tp y sl los dibuje en el gráfico.
Para esto necesitaremos crear líneas y textos.
Para las líneas:
bool TextCreate(long chart_ID, // Chart ID string name, // Object name int sub_window, // Subwindow index datetime time, // Anchor time double price, // Anchor price string text, // the text string font, // Font int font_size, // Font size color clr, // color double angle, // Text angle ENUM_ANCHOR_POINT anchor, // Anchor point bool back=false, // font bool selection=false) // Selectable for moving { //--- reset error value ResetLastError(); //--- create "Text" object if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price)) { Print(__FUNCTION__, ": ¡Failed to create object \"Text\"! Error code = ",GetLastError()); return(false); } ObjectSetString(chart_ID,name,OBJPROP_TEXT,text); ObjectSetString(chart_ID,name,OBJPROP_FONT,font); ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size); ObjectSetDouble(chart_ID,name,OBJPROP_ANGLE,angle); ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,anchor); ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr); ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection); ChartRedraw(chart_ID); return(true); }
Para los textos:
void DrawTP_SL( double tp1, double tp2, double sl1, double sl2)
Ahora sí, pasaremos a crear la función.
Paso 1: Parámetros de entrada
La función recibe los siguientes parámetros.
- tp1 y tp2: Los valores para los dos niveles de Take Profit.
- sl1 y sl2: Los valores para los dos niveles de Stop Loss.
string curr_time = TimeToString(iTime(_Symbol, _Period, 0)); datetime extension_time = iTime(_Symbol, _Period, 0) + (PeriodSeconds(PERIOD_CURRENT) * 15); datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);
Paso 2: Preparación de Tiempos
Primero, se crea una string curr_time que almacena la fecha y hora actual de la vela en el gráfico. Luego, se calcula extension_time, que se extiende 15 periodos hacia adelante desde la hora actual, para proyectar las líneas de TP y SL a la derecha en el gráfico. text_time se usa para ajustar la posición de las etiquetas de texto, extendiéndolo un poco más allá de extension_time.
TrendCreate(ChartID(), curr_time + " TP1", 0, iTime(_Symbol, _Period, 0), tp1, extension_time, tp1, clrGreen, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " TP1 - Text", 0, text_time, tp1, "TP1", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);
Paso 3: Dibujar líneas y etiquetas de TP y SL
- Take Profit 1 (tp1):
- Se crea una línea verde punteada (STYLE_DOT) en el nivel tp1 con la función TrendCreate.
- A continuación, TextCreate añade una etiqueta con el texto "TP1" en la posición de tp1.
TrendCreate(ChartID(), curr_time + " TP2", 0, iTime(_Symbol, _Period, 0), tp2, extension_time, tp2, clrGreen, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " TP2 - Text", 0, text_time, tp2, "TP2", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);2. Take Profit 2 (tp2):
- Se dibuja otra línea verde punteada en tp2 y una etiqueta de texto "TP2".
TrendCreate(ChartID(), curr_time + " SL1", 0, iTime(_Symbol, _Period, 0), sl1, extension_time, sl1, clrRed, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " SL1 - Text", 0, text_time, sl1, "SL1", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);3. Stop Loss 1 (sl1):
- Se dibuja una línea roja punteada en el nivel sl1, junto con una etiqueta de texto "SL1".
TrendCreate(ChartID(), curr_time + " SL2", 0, iTime(_Symbol, _Period, 0), sl2, extension_time, sl2, clrRed, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " SL2 - Text", 0, text_time, sl2, "SL2", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);4. Stop Loss 2 (sl2):
- De manera similar, se dibuja otra línea roja en sl2 y una etiqueta de texto "SL2".
void DrawTP_SL(double tp1, double tp2, double sl1, double sl2) { string curr_time = TimeToString(iTime(_Symbol,_Period,0)); datetime extension_time = iTime(_Symbol,_Period,0) + (PeriodSeconds(PERIOD_CURRENT) * 15); datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2); TrendCreate(ChartID(),curr_time+" TP1",0,iTime(_Symbol,_Period,0),tp1,extension_time,tp1,clrGreen,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" TP1 - Text",0,text_time,tp1,"TP1","Arial",8,clrGreen,0.0,ANCHOR_CENTER); TrendCreate(ChartID(),curr_time+" TP2",0,iTime(_Symbol,_Period,0),tp2,extension_time,tp2,clrGreen,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" TP2 - Text",0,text_time,tp2,"TP2","Arial",8,clrGreen,0.0,ANCHOR_CENTER); TrendCreate(ChartID(),curr_time+" SL1",0,iTime(_Symbol,_Period,0),sl1,extension_time,sl1,clrRed,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" SL1 - Text",0,text_time,sl1,"SL1","Arial",8,clrRed,0.0,ANCHOR_CENTER); TrendCreate(ChartID(),curr_time+" SL2",0,iTime(_Symbol,_Period,0),sl2,extension_time,sl2,clrRed,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" SL2 - Text",0,text_time,sl2,"SL2","Arial",8,clrRed,0.0,ANCHOR_CENTER); }
Código completo:
#property indicator_label3 "Take Profit 1" #property indicator_label4 "Take Profit 2" #property indicator_label5 "Stop Loss 1" #property indicator_label6 "Stop Loss 2"
Adición de Buffers para niveles de TP y SL (4)
Como hicimos antes para la creación de los 2 buffers que guardan el price2, haremos lo mismo, nos dirigiremos a la parte global del programa donde escribiremos:
#property indicator_buffers 6 #property indicator_plots 6
Además, aumentaremos los plots y buffers de 2 a 6.
double tp1_buffer[]; double tp2_buffer[]; double sl1_buffer[]; double sl2_buffer[];
Creamos el array de los buffers:
SetIndexBuffer(2, tp1_buffer, INDICATOR_DATA); SetIndexBuffer(3, tp2_buffer, INDICATOR_DATA); SetIndexBuffer(4, sl1_buffer, INDICATOR_DATA); SetIndexBuffer(5, sl2_buffer, INDICATOR_DATA); ArraySetAsSeries(buyOrderBlockBuffer, true); ArraySetAsSeries(sellOrderBlockBuffer, true); ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArraySetAsSeries(tp1_buffer, true); ArraySetAsSeries(tp2_buffer, true); ArrayFill(tp1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(tp2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArraySetAsSeries(sl1_buffer, true); ArraySetAsSeries(sl2_buffer, true); ArrayFill(sl1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(sl2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
Los inicializamos con fill y los ponemos en modo serie:
datetime tiempo_ultima_vela_1; Con esto ya tendríamos los buffers.
Finalización del código principal y limpieza
Para finalizar el desarrollo del indicador, es esencial implementar un código de limpieza y optimización. Esto permitirá que el indicador se ejecute más rápido en los backtests y que se liberen los recursos de los arrays, como los de tipo OrderBlocks, una vez que ya no son necesarios.
1. Limpieza de los Arrays
Dentro de OnCalculate, controlaremos la creación de una nueva vela, pero en esta ocasión en temporalidad diaria. Para esto, utilizaremos una variable global que almacenará el tiempo de la última vela:
if(tiempo_ultima_vela_1 != iTime(_Symbol,PERIOD_D1, 0)) { Eliminar_Objetos(); ArrayFree(ob_bajistas); ArrayFree(ob_alcistas); ArrayFree(pricetwo_eliminados_oba); ArrayFree(pricetwo_eliminados_obb); tiempo_ultima_vela_1 = iTime(_Symbol,PERIOD_D1, 0); }Cada vez que se abra una nueva vela diaria, liberaremos la memoria de los arrays, evitando la acumulación de datos antiguos y optimizando el rendimiento.
void OnDeinit(const int reason) { Eliminar_Objetos(); ArrayFree(ob_bajistas); ArrayFree(ob_alcistas); ArrayFree(pricetwo_eliminados_oba); ArrayFree(pricetwo_eliminados_obb); if(atr_i != INVALID_HANDLE) IndicatorRelease(atr_i); if(hanlde_ma != INVALID_HANDLE) //EMA IndicatorRelease(hanlde_ma); ResetLastError(); if(MarketBookRelease(_Symbol)) //Verify if closure was successful Print("Order book successfully closed for: " , _Symbol); //Print success message if so else Print("Order book closed with errors for: " , _Symbol , " Last error: " , GetLastError()); //Print error message with code if not }
2. Modificación de OnDeinit
En OnDeinit, liberaremos el manejador del indicador de la EMA y limpiaremos los arrays. Esto asegura que no queden recursos en memoria al desactivar el indicador.
void Eliminar_Objetos() { for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // iterate through the array of bullish order blocks { ObjectDelete(ChartID(),ob_alcistas[i].name); // delete the object using the order block's name } for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // iterate through the array of bearish order blocks { ObjectDelete(ChartID(),ob_bajistas[n].name); // delete the object using the order block's name } //Delete all TP and SL lines ObjectsDeleteAll(0," TP",-1,-1); ObjectsDeleteAll(0," SL",-1,-1); }
3. Función de eliminación de Objetos:
La función Eliminar_Objetos ha sido optimizada para eliminar también las líneas de Take Profit (TP) y Stop Loss (SL) junto con los rectángulos de los bloques de orden. Así, nos aseguramos de que el gráfico quede limpio.string short_name = "Order Block Indicator"; IndicatorSetString(INDICATOR_SHORTNAME,short_name); // Set data precision for digits // Assign labels for each plot PlotIndexSetString(0, PLOT_LABEL, "Bullish Order Block"); PlotIndexSetString(1, PLOT_LABEL, "Bearish Order Block"); PlotIndexSetString(2, PLOT_LABEL, "Take Profit 1"); PlotIndexSetString(3, PLOT_LABEL, "Take Profit 2"); PlotIndexSetString(4, PLOT_LABEL, "Stop Loss 1"); PlotIndexSetString(5, PLOT_LABEL, "Stop Loss 2");
4. Configuración Inicial en OnInit:
En OnInit, configuramos el nombre corto del indicador y las etiquetas de los gráficos (plots). De esta manera, los elementos se etiquetan adecuadamente en la ventana de datos.
//Buy double ask= NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); double tp1; double tp2; double sl1; double sl2; GetTP_SL(ask,POSITION_TYPE_BUY,tp1,tp2,sl1,sl2); DrawTP_SL(tp1,tp2,sl1,sl2); tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1; tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2; sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1; sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2; time_ = 0; buscar_oba = true; //Sell double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); double tp1; double tp2; double sl1; double sl2; GetTP_SL(bid,POSITION_TYPE_SELL,tp1,tp2,sl1,sl2); DrawTP_SL(tp1,tp2,sl1,sl2); tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1; tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2; sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1; sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2; time_b = 0; buscar_obb = true;
5. Configuración de niveles de TP y SL al abrir Operaciones:
Finalmente, configuramos los niveles de Take Profit y Stop Loss para las operaciones de compra y venta. Para las compras, tomamos el precio ask y calculamos los niveles correspondientes para las ventas, Tomamos el precio bid. Luego, los dibujamos en el gráfico para hacer el seguimiento de los niveles.
undefined
| Paso | Compras | Ventas |
|---|---|---|
| Precio: | Se obtiene y normaliza el precio del ask. | Se obtiene y normaliza el precio del bid. |
| Variables: | Se inicializan las variables para almacenar los valores de Take Profit y Stop Loss. (tp1, tp2, sl1 y sl2). | Las mismas variables se utilizan para almacenar los niveles de Take Profit y Stop Loss. (tp1, tp2, sl1 y sl2). |
| Cálculo: | GetTP_SL calcula los niveles de TP y SL basándose en el precio ask para una operación de compra. | GetTP_SL calcula los niveles de TP y SL basándose en el precio bid para una operación de venta. |
| Dibujo: | DrawTP_SL dibuja visualmente los niveles TP y SL en el gráfico para la operación de compra. | DrawTP_SL dibuja visualmente los niveles TP y SL en el gráfico para la operación de venta. |
| Buffer: | Se usa iBarShift para encontrar el índice de la barra actual y almacenar TP y SL en los buffers. (tp1_buffer, tp2_buffer, sl1_buffer y sl2_buffer). | Se usa iBarShift para almacenar TP y SL en los mismos buffers. (tp1_buffer, tp2_buffer, sl1_buffer y sl2_buffer). |
| Variables Estáticas: | Se reinician las variables estáticas para buscar nuevos bloques de órdenes alcistas en la siguiente iteración. (Variables estáticas: "time_" y "buscar_oba"). | Se reinician las variables estáticas para buscar nuevos bloques de órdenes bajistas en la siguiente iteración. (Variables estáticas:"time_b" y "buscar_obb"). |
Conclusión
En este artículo, exploramos cómo crear un indicador de Order Blocks basado en el volumen de la profundidad de mercado y optimizamos su funcionalidad al agregar buffers adicionales al indicador original.
Nuestro resultado final:

Con esta sección, concluimos el desarrollo de nuestro indicador de Order Blocks. En las próximas entregas, abordaremos la creación de una clase de gestión de riesgos desde cero y desarrollaremos un bot de trading que integrará esta gestión de riesgos, aprovechando los buffers de señal de nuestro indicador para tomar decisiones de manera más precisa y automatizada.
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.
Cómo ver las transacciones directamente en el gráfico sin tener que perderse en el historial de transacciones
Desarrollo de un sistema de repetición (Parte 68): Ajuste del tiempo (I)
Desarrollo de un sistema de repetición (Parte 69): Ajuste del tiempo (II)
Desarrollo de un sistema de repetición (Parte 67): Refinando el indicador de control
- 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
https://www.mql5.com/es/articles/16268
5. Establecer niveles de TP y SL al abrir operaciones
Por último, establecemos los niveles de Take Profit y Stop Loss para las operaciones de compra y venta. Para las operaciones de compra, utilice el precio Ask; para las operaciones de venta, utilice el precio Bid. A continuación, dibuje las líneas TP y SL en el gráfico para su seguimiento.
Esto parece que podría simplificarse un poco.