
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_FAILED
Ya 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 profanidad 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 por defecto
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)) //Verificamos la inicialización del libro de ordenes para el símbolo actual { Print("Error Open Market Book: ", _Symbol, " LastError: ", _LastError); //Imprimimos un error en caso de fallo use_market_book = false; //Marcamos la variable use_market_book como falseen caso de fallo }
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)) //Verificamos si el cierre fue exitoso Print("Libro de ordenes cerrado con éxito para: " , _Symbol); //Imprimimos mensaje de éxito si lo fue else Print("Libro de ordenes cerrado con errores para: " , _Symbol , " Ultimo error: " , GetLastError()); //En caso no lo fue imprimimos el mensaje de error además del código de error
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; // Si no se cumplen las condiciones, salimos del evento
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; // Definir el array para almacenar los datos del Market Book MqlBookInfo book_info[]; // Obtener los datos del Market Book bool book_count = MarketBookGet(_Symbol,book_info); // Verificar si se obtuvieron datos if(book_count == true) { // Iterar sobre los datos del Market Book for(int i = 0; i < ArraySize(book_info); i++) { // Verificar si el registro es una oferta de compra (BID) if(book_info[i].type == BOOK_TYPE_BUY || book_info[i].type == BOOK_TYPE_BUY_MARKET) { buy_volume[0] += book_info[i].volume; } // Verificar si el registro es una oferta de venta (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 se han obtenido datos del Market Book."); } }
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); // Mantiene el tamaño de buy_volume en 30 ArrayResize(sell_volume, 30); // Mantiene el tamaño de sell_volume en 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("Volumen de compra de la última vela: ", buy_volume[1]); Print("Volumen de venta de la última vela: ", 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; } // Si el tamaño del array es mayor o igual a 30, elimina el último elemento if(ArraySize(buy_volume) >= 30) { ArrayResize(buy_volume, 30); // Asegurar que el tamaño de buy_volume no exceda 30 ArrayResize(sell_volume, 30); // Asegurar que el tamaño de sell_volume no exceda 30 } 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]; } // Reiniciar el volumen para la nueva vela 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:
En 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 Encotrado"); datetime time1 = Time[furthestAlcista]; double price2 = openArray[furthestAlcista]; double price1 = lowArray[furthestAlcista]; //asiganmos las variables mencionadas ala estructura 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 = "Order Block Alcista Book " + TimeToString(newVela_Order_block_Book.time1); AddIndexToArray_alcistas(newVela_Order_block_Book);
Caso bajista:
Print("Case Book Encotrado"); datetime time1 = Time[furthestBajista]; double price1 = closeArray[furthestBajista]; double price2 = lowArray[furthestBajista]; //asiganmos las variables mencionadas ala estructura 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); //llamamos ala funcoin "FindFurthestAlcista" para saber si anterior a "one vela" hay velas alcistas if(furthestBajista > 0) // haiga o no haiga furthestAlcista sera mayor a 0 ya que si no hay retorna la vela anterior a "one vela". { Print("Case Book Encotrado"); datetime time1 = Time[furthestBajista]; double price1 = closeArray[furthestBajista]; double price2 = lowArray[furthestBajista]; //asiganmos las variables mencionadas ala estructura 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); } } } //-------------------- Alcista -------------------- 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); //llamamos ala funcoin "FindFurthestAlcista" para saber si anterior a "one vela" hay velas alcistas if(furthestAlcista > 0) // haiga o no haiga furthestAlcista sera mayor a 0 ya que si no hay retorna la vela anterior a "one vela". { Print("Case Book Encotrado"); datetime time1 = Time[furthestAlcista]; //asiganmos el tiempo del indice de furthestAlcista ala variable time1 double price2 = openArray[furthestAlcista]; //asiganmos el open de furthestAlcista como precio 2 (recodar que lo dibujamos en una vela bajista al mayoria de veces) double price1 = lowArray[furthestAlcista]; //asignamos el low de furthestAlcista como precio 1 //asiganmos las variables mencionadas ala estructura 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 = "Order Block Alcista 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.
//--- Definir los buffers double buyOrderBlockBuffer[]; // Buffer para bloques de órdenes de compra double sellOrderBlockBuffer[]; // Buffer para bloques de órdenes de venta
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.
//--- Asignar buffers de datos al indicador 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); // Inicializar a EMPTY_VALUE ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Inicializar a 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; // Estilo de TP y SL: por ATR o puntos fijos
En esta sección, tp_sl_style permitirá al usuario elegir si desea calcular TP y SL basándonos en elATR (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; // Multiplicador para TP input double Atr_Multiplier_2 = 2.0; // Multiplicador para 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; // Puntos fijos para TP input int SL_POINT = 275; // Puntos fijos para 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("El indicador de ema no esta disponible Fallo: ", _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 para compra static bool buscar_oba = true; static datetime time_ = 0; //Variables para venta 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.
//caso alcista if(buscar_oba == true)
//caso bajista if(buscar_obb == true)
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.
// Caso Bajista 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("El order block bajista está siendo mitigado: ", TimeToString(ob_bajistas[i].time1)); buscar_obb = false; // Pausa la búsqueda time_b = iTime(_Symbol,_Period,1); // Registra el tiempo de mitigación Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name); break; } } // Caso Alcista 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("El order block alcista está siendo mitigado: ", TimeToString(ob_alcistas[i].time1)); time_ = iTime(_Symbol,_Period,0); Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name); buscar_oba = false; // Pausa la búsqueda break; } }
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.
// Compra if(buscar_oba == false && time_ > 0 && new_vela) { /* Código de compra */ } // Venta if(buscar_obb == false && time_b > 0 && new_vela) { /* Código de venta */ }
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.
// Compra 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) { // Código de compra... } // Venta 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) { // Código de venta... }
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:
// Reinicio para compra if(iTime(_Symbol,_Period,0) > max_time_espera) { time_ = 0; buscar_oba = true; } // Reinicio para venta if(iTime(_Symbol,_Period,0) > max_time_espera) { time_b = 0; buscar_obb = true; }
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.
void GetTP_SL(double price_open_position, ENUM_POSITION_TYPE type, double &tp1, double &tp2, double &sl1, double &sl2)
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.
double atr[]; ArraySetAsSeries(atr, true); CopyBuffer(atr_i, 0, 0, 1, atr);
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 - (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); }
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.
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); }
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 TrendCreate(long chart_ID, // ID del gráfico string name, // Nombre de la línea int sub_window, // índice de subventana datetime time1, // hora del primer punto double price1, // precio del primer punto datetime time2, // hora del segundo punto double price2, // precio del segundo punto color clr, // color de la línea ENUM_LINE_STYLE style, // estilo de la línea int width, // grosor de la línea bool back, // al fondo bool selection // seleccionar para mover ) { ResetLastError(); if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2)) { Print(__FUNCTION__, ": ¡Fallo al crear la línea de tendencia! Código del error = ",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); }
Para los textos:
bool TextCreate(long chart_ID, // ID del gráfico string name, // nombre del objeto int sub_window, // número de subventana datetime time, // hora del punto de anclaje double price, // precio del punto de anclaje string text, // el texto string font, // fuente int font_size, // tamaño de la fuente color clr, // color double angle, // inclinación del texto ENUM_ANCHOR_POINT anchor, // modo de anclaje bool back=false, // al fondo bool selection=false) // seleccionar para mover { //--- anulamos el valor del error ResetLastError(); //--- creamos el objeto "Texto" if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price)) { Print(__FUNCTION__, ": ¡Fallo al crear el objeto \"Texto\"! Código del error = ",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); }
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.
void DrawTP_SL( double tp1, double tp2, double sl1, double sl2)
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.
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 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 + " 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);2. Take Profit 2 (tp2):
- Se dibuja otra línea verde punteada en tp2 y una etiqueta de texto "TP2".
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);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 + " 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);4. Stop Loss 2 (sl2):
- De manera similar, se dibuja otra línea roja en sl2 y una etiqueta de texto "SL2".
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:
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); }
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_label3 "Take Profit 1" #property indicator_label4 "Take Profit 2" #property indicator_label5 "Stop Loss 1" #property indicator_label6 "Stop Loss 2"
Además, aumentaremos los plots y buffers de 2 a 6.
#property indicator_buffers 6 #property indicator_plots 6
Creamos el array de los buffers:
double tp1_buffer[]; double tp2_buffer[]; double sl1_buffer[]; double sl2_buffer[];
Los inicializamos con fill y los ponemos en modo serie:
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); // Inicializar a EMPTY_VALUE ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Inicializar a EMPTY_VALUE ArraySetAsSeries(tp1_buffer, true); ArraySetAsSeries(tp2_buffer, true); ArrayFill(tp1_buffer, 0, 0, EMPTY_VALUE); // Inicializar a EMPTY_VALUE ArrayFill(tp2_buffer, 0, 0, EMPTY_VALUE); // Inicializar a EMPTY_VALUE ArraySetAsSeries(sl1_buffer, true); ArraySetAsSeries(sl2_buffer, true); ArrayFill(sl1_buffer, 0, 0, EMPTY_VALUE); // Inicializar a EMPTY_VALUE ArrayFill(sl2_buffer, 0, 0, EMPTY_VALUE); // Inicializar a EMPTY_VALUE
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:
datetime tiempo_ultima_vela_1;
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. 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); }
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 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)) //Verificamos si el cierre fue exitoso Print("Libro de ordenes cerrado con éxito para: " , _Symbol); //Imprimimos mensaje de éxito si lo fue else Print("Libro de ordenes cerrado con errores para: " , _Symbol , " Ultimo error: " , GetLastError()); //En caso no lo fue imprimimos el mensaje de error además del código de error }
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.void Eliminar_Objetos() { for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // iteramos por el array de los order blocks alcistas { ObjectDelete(ChartID(),ob_alcistas[i].name); // eliminamos el objeto usando el nombre del order block } for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // iteramos por el array de los order blocks bajistas { ObjectDelete(ChartID(),ob_bajistas[n].name); // eliminamos el objeto usando el nombre del order block } //Eliminación de líneas del tp y sl ObjectsDeleteAll(0," TP",-1,-1); ObjectsDeleteAll(0," SL",-1,-1); }
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.
string short_name = "Order Block Indicator"; IndicatorSetString(INDICATOR_SHORTNAME,short_name); // Configuración de propiedades de precisión de los datos (número de dígitos) // Asignar etiquetas a cada uno de los plots para la ventana de datos 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");
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.
//Compras 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; //Ventas 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;
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 bajistas 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.





- 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