Creación de un sistema de trading profesional con Heikin Ashi (Parte 1): Desarrollo de un indicador personalizado
Introducción
Los indicadores técnicos son el núcleo del trading algorítmico, y dominar su creación es un paso fundamental en la trayectoria de cualquier desarrollador de MQL5. En este artículo, abordamos de forma práctica el desarrollo de indicadores personalizados mediante la creación de una de las herramientas de suavización de tendencias más populares: el gráfico de Heikin Ashi.
Este artículo es la primera parte de una serie de dos partes. En la primera parte, analizaremos la teoría que subyace a las velas Heikin Ashi, explicaremos cómo se calculan y describiremos paso a paso el proceso para programar un indicador Heikin Ashi desde cero utilizando MQL5. El objetivo no es simplemente copiar y pegar el código fuente, sino comprender realmente el porqué de cada línea del código fuente, para que usted pueda aplicar estas buenas prácticas a la hora de crear sus propios indicadores.
En la segunda parte, desarrollaremos un asesor experto que utilice el indicador Heikin Ashi como base para sus entradas y salidas. Empecemos por entender en qué se diferencian los gráficos Heikin Ashi de los gráficos de velas tradicionales.
Antes de comenzar, hay algunas cosas con las que debería sentirse cómodo. Este artículo da por hecho que usted:
- Tiene un conocimiento sólido del lenguaje de programación MQL5.
- Sabe utilizar MetaTrader 5 y MetaEditor 5.
- Sabe adjuntar indicadores y asesores expertos a un gráfico.
¿En qué se diferencia Heikin Ashi de una vela tradicional?
Para empezar, veamos dos gráficos uno al lado del otro.
Un gráfico de velas tradicional:

Un gráfico Heikin Ashi generado a partir de los mismos datos de precios:

Ambos gráficos se basan en la evolución reciente del precio del oro. Al compararlos, preste atención a cómo se comportan las velas durante los periodos de tendencia. Aunque los gráficos de velas tradicionales se actualizan con cada nueva barra de precios, a menudo pueden verse erráticos, ya que cambian de color con frecuencia incluso cuando la tendencia general se mantiene intacta. Esto puede generar ruido visual y dificultar la percepción del impulso de un solo vistazo. Por el contrario, las velas Heikin Ashi se calculan usando valores suavizados, lo que da como resultado un aspecto mucho más limpio. Durante los periodos de tendencia, el Heikin Ashi suele mostrar largas secuencias de velas del mismo color, lo que facilita la identificación visual de las tendencias alcistas y bajistas.
En términos sencillos, las velas Heikin Ashi suelen mantenerse verdes durante las tendencias alcistas y rojas durante las bajistas, lo que permite filtrar los retrocesos menores que, de otro modo, podrían parecer reversiones en un gráfico tradicional. Esto las convierte en una herramienta muy eficaz para los traders que desean seguir la tendencia sin dejarse distraer por fluctuaciones de corto plazo.
En la siguiente sección, explicaremos con detalle cómo se calculan exactamente estas velas.
Cómo se calculan las velas Heikin Ashi
Antes de ver cómo se calculan las velas Heikin Ashi, veamos primero en qué se diferencian de las velas japonesas normales. En un gráfico de velas tradicional, cada vela se forma a partir de cuatro valores clave del mercado.

- La apertura es el precio al inicio de la formación de la vela.
- El «máximo» es el precio más alto alcanzado durante la formación de la vela.
- El «mínimo» es el precio más bajo alcanzado.
- El «cierre» es el precio final al término de la formación de la vela.
Estos valores reflejan directamente lo que ocurrió durante un periodo de tiempo concreto, como una hora o un día. Sin embargo, las velas Heikin Ashi no utilizan estos valores brutos directamente. En cambio, utilizan un conjunto de valores modificado para suavizar el gráfico y resaltar la tendencia general con mayor claridad.
La vela Heikin Ashi está compuesta por
- La apertura de Heikin Ashi, que es una versión suavizada de la apertura de la vela.
- El máximo de Heikin Ashi, que es el valor más alto de un conjunto que incluye tanto datos reales como datos suavizados.
- El mínimo de Heikin Ashi, que es el valor más bajo de un conjunto de este mismo tipo.
- El cierre de Heikin Ashi, que es el precio medio de la vela real actual.
Miremos ahora cómo se calcula exactamente cada uno de estos valores de las velas Heikin Ashi.
Las fórmulas de Heikin Ashi
Cierre de Heikin Ashi = (Apertura + Máximo + Mínimo + Cierre) / 4.
Este es el precio medio de la vela actual. Ofrece una única cifra equilibrada que refleja la evolución general del precio durante este periodo.
Apertura de Heikin Ashi = (apertura de la vela Heikin Ashi anterior + cierre de la vela Heikin Ashi anterior) / 2.
Esto significa que la apertura de la vela Heikin Ashi actual no es la apertura actual del mercado, sino el punto medio del cuerpo de la vela Heikin Ashi anterior, lo que ayuda a suavizar las transiciones entre velas y produce ese aspecto «fluido».
Máximo de Heikin Ashi = el valor más alto entre el máximo real, la apertura de Heikin Ashi y el cierre de Heikin Ashi.
Mínimo de Heikin Ashi = El valor más bajo entre el mínimo, la apertura de Heikin Ashi y el cierre de Heikin Ashi.
Los dos valores anteriores combinan los máximos y mínimos reales del mercado con los precios de apertura y cierre suavizados, lo que confiere a la vela unas mechas realistas sin dejar de beneficiarse del suavizado.
Resumiendo:
El cierre de Heikin Ashi refleja la media de los precios de la vela actual. La apertura de Heikin Ashi depende de los valores suavizados de la vela anterior. El máximo de Heikin Ashi y el mínimo de Heikin Ashi utilizan tanto los valores brutos como los suavizados para representar los extremos de los precios.
Este método de cálculo es el que confiere a las velas Heikin Ashi su aspecto limpio y centrado en la tendencia. Suelen mantenerse en verde en las tendencias alcistas y en rojo en las bajistas, lo que permite filtrar las fluctuaciones a corto plazo que pueden confundir a la vista.
En las siguientes secciones pondremos en práctica estos conocimientos creando, paso a paso, un indicador Heikin Ashi personalizado mediante MQL5.
Planificación de la lógica del indicador
Antes de ponernos con el código, debemos planificar brevemente la lógica de nuestro indicador Heikin Ashi. En primer lugar, dejemos claro que vamos a crear un indicador personalizado que muestre las velas Heikin Ashi directamente en el gráfico, igual que las velas normales, pero basadas en cálculos de precios suavizados.
Además de la visualización estándar, mejoraremos el gráfico Heikin Ashi coloreando las velas con tres colores distintos: uno para las velas alcistas, otro para las bajistas y un tercero para las neutras.
Para implementar esta lógica visual, nuestro indicador utilizará cinco búferes:
- Apertura de Heikin Ashi. Es un array que almacena el precio de apertura de Heikin Ashi.
- Máximo de Heikin Ashi. Es un array que almacena el precio máximo de Heikin Ashi.
- Mínimo de Heikin Ashi. Es un array que almacena el precio mínimo de Heikin Ashi.
- Cierre de Heikin Ashi. Es un array que almacena el precio de cierre de Heikin Ashi.
- Búfer de color. Es un array que almacena el índice de color de cada vela Heikin Ashi en función de si es alcista, bajista o neutra.
Cada búfer desempeña un papel esencial tanto en el trazado de la vela Heikin Ashi como en la aplicación dinámica del color adecuado.
Para representar estas velas, utilizaremos una única representación gráfica con DRAW_COLOR_CANDLES, lo que nos permite emplear un único ID de trazado y un búfer independiente para gestionar el color. Una cosa que debemos tratar con cuidado es la primera barra del gráfico, ya que no tiene un valor anterior de Heikin Ashi. Inicializaremos manualmente los valores de la primera barra para que el resto de las velas se puedan construir con precisión según la fórmula adecuada.
Por último, para que el código sea claro y fácil de entender para los principiantes, lo escribiremos siguiendo un estilo de programación modular, separando las partes clave de la lógica en funciones pequeñas y manejables. Esto hará que el código sea más fácil de entender, depurar y ampliar en futuros tutoriales.
Guía paso a paso para programar el indicador Heikin Ashi
Para crear nuestro indicador Heikin Ashi personalizado, utilizaremos el lenguaje de programación MQL5. Este es el lenguaje nativo para desarrollar herramientas de trading automatizado en MetaTrader 5.
Dado que damos por hecho que los lectores ya saben cómo utilizar MetaTrader 5 y MetaEditor, y conocen los conceptos básicos para añadir indicadores a un gráfico, nos saltaremos los detalles de la configuración y pasaremos directamente al desarrollo de nuestro indicador Heikin Ashi personalizado.
Más abajo se muestra el código base inicial con el que trabajaremos como base para crear nuestro indicador personalizado. Partiendo de aquí, empezaremos a implementar la lógica principal paso a paso.
//+------------------------------------------------------------------+ //| heikinAshiIndicator.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/chachaian" #property version "1.00" //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time [], const double &open [], const double &high [], const double &low [], const double &close [], const long &tick_volume[], const long &volume [], const int32_t &spread []) { return(rates_total); } //+------------------------------------------------------------------+
Vamos a analizar este código en partes sencillas y lógicas para que resulte más fácil de seguir.
Directivas de propiedades:
... #property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/en/users/chachaian" #property version "1.00" ...
Estas líneas establecen la metainformación sobre el indicador, que aparecerá en el Navegador y en la ventana de descripción de MetaTrader 5.
- #property copyright. Esto declara el titular de los derechos de autor y el desarrollador. Es bueno para la imagen de marca y la titularidad del código
- #property link. Ofrece un enlace clicable a la página de su proyecto o al recurso correspondiente.
- #property version. Esto resulta útil para el control de versiones en futuras actualizaciones.
... //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { return(INIT_SUCCEEDED); } ...
La función de inicialización (OnInit()) se ejecuta únicamente una vez, cuando el indicador se añade por primera vez a un gráfico. Se utiliza para gestionar las tareas de preparación y configuración. Al devolver INIT_SUCCEEDED, se indica a MetaTrader 5 que el indicador se ha cargado correctamente. En los siguientes pasos, utilizaremos OnInit() para registrar los búferes de indicador y definir las propiedades visuales de nuestros trazados gráficos.
Función de cálculo:
... //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int32_t &spread[]) { return(rates_total); } ...
Este es el núcleo de su indicador. Se invoca repetidamente a medida que se forman nuevas velas. Sus parámetros permiten acceder a:
- rates_total. Esto se refiere al número total de barras disponibles.
- prev_calculated. Indica cuántas barras se procesaron durante la última llamada a la función «OnCalculate» (con fines de optimización).
- Las variables time, open, high, low y close, que son básicamente nuestras series temporales históricas de precios.
- volume, tick_volume y spread, que son datos adicionales del mercado.
Un aspecto clave que hay que tener en cuenta es que la función «OnCalculate» se invoca automáticamente cada vez que llegan nuevos datos de tick o cuando se actualizan los datos históricos. Por ejemplo, cuando hay una laguna en los datos históricos y esta se completa. Aquí es donde irá la mayor parte de la lógica de nuestro indicador.
En MetaTrader 5, los indicadores pueden mostrarse de dos formas principales: bien directamente en la ventana principal del gráfico (superpuestos a las velas de precios), bien en una subventana independiente situada debajo del gráfico principal.
Para definir cómo debe mostrarse un indicador, utilizamos #property indicator_chart_window para los indicadores superpuestos al gráfico o #property indicator_sub_window para los indicadores basados en subventanas. Como estamos creando un indicador que se superpone al precio, queremos que aparezca directamente en el gráfico principal.
Teniendo esto en cuenta, vamos a añadir la siguiente línea justo debajo de nuestras directivas #property existentes:
#property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/en/users/chachaian" #property version "1.00" #property indicator_chart_window ...
En MQL5, los indicadores personalizados suelen almacenar los valores calculados en arrays dinámicos especiales denominados «búferes de indicador». Estos búferes se registran mediante una función integrada de MQL5 denominada SetIndexBuffer() y son esenciales para visualizar datos en el gráfico, por ejemplo mediante líneas, histogramas, flechas y otros elemento.
Antes de poder utilizarlos en su código fuente, debe especificar cuántos búferes utilizará su indicador. Esto se hace mediante la directiva #property indicator_buffers N. Aquí, N representa el número total de búferes que necesita su indicador. En nuestro caso, vamos a utilizar 5 búferes, así que lo declararemos así: #property indicator_buffers 5.
Añadamos ahora la siguiente línea justo debajo de las demás directivas que ya hay en nuestro código fuente.
... #property indicator_chart_window #property indicator_buffers 5 ...
Los indicadores suelen utilizar elementos gráficos para mostrar visualmente sus cálculos en el gráfico. Estos elementos visuales, como líneas, histogramas o flechas, también se conocen como trazados gráficos. Ayudan a los traders a interpretar de un vistazo lo que está haciendo el indicador.
El número de trazados gráficos que mostrará un indicador se configura mediante la directiva #property indicator_plots P, donde P es el número de trazados gráficos que mostrará el indicador. En nuestro caso, solo estamos trabajando con una única representación gráfica.
Luego añadimos la siguiente línea de código justo debajo de las demás directivas #property existentes.
... #property indicator_chart_window #property indicator_buffers 5 #property indicator_plots 1 ...
A continuación, pasamos a declarar los arrays que actuarán como búferes de indicador. Normalmente, esto se hace en el ámbito global. El tipo de datos de estos arrays casi siempre se establece como «double», ya que los valores de los indicadores suelen ser números de coma flotante. Vamos a declararlas ahora mismo, justo encima de la función OnInit(), para que estén disponibles en todo el código del indicador.
... #property indicator_plots 1 //Global variables double haOpen []; double haHigh []; double haLow []; double haClose[]; double colorBuffer []; ...
Ahora que hemos declarado nuestros arrays, el siguiente paso consiste en registrar cada una de ellas como búfer de indicador utilizando la función integrada de MQL5 SetIndexBuffer(). Esta función vincula nuestros arrays declarados al sistema de indicadores para que MetaTrader pueda utilizarlas para el trazado y los cálculos. Lo haremos dentro de la función OnInit(), registrando cada array una por una.
... //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { // Registration of indicator buffers if(!SetIndexBuffer(0, haOpen, INDICATOR_DATA)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } if(!SetIndexBuffer(1, haHigh, INDICATOR_DATA)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } if(!SetIndexBuffer(2, haLow, INDICATOR_DATA)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } if(!SetIndexBuffer(3, haClose, INDICATOR_DATA)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } if(!SetIndexBuffer(4, colorBuffer, INDICATOR_COLOR_INDEX)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } } ...
A continuación, vamos a configurar la lógica principal de nuestro indicador dentro de la función OnCalculate(). Esta función se llama cada vez que se actualiza el indicador. Ya sea cuando se adjunta por primera vez a un gráfico, cuando se forma una nueva vela o cuando llega un nuevo tick de precio.
Normalmente estructuramos esta función en tres bloques condicionales principales para gestionar cada uno de esos casos de manera eficiente:
- Cuando se adjunta el indicador al gráfico por primera vez, esto ocurre cuando prev_calculated == 0. Utilizamos este bloque para ejecutar cualquier lógica de inicialización, como rellenar valores históricos o configurar las condiciones iniciales.
- Cuando se abre una nueva vela, esto ocurre cuando prev_calculated != rates_total y prev_calculated != 0. Aquí puede aplicar una lógica que solo debe ejecutarse una vez por cada nueva barra. Resulta útil para reducir los cálculos repetitivos.
- Cuando llega un nuevo tick de precio a la barra actual, esto ocurre cuando prev_calculated == rates_total. En este bloque, actualizamos los valores en tiempo real de la vela actual utilizando los datos de precios más recientes.
Abajo se muestra la estructura básica que incluiremos en nuestra función OnCalculate():
... //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int32_t &spread[]) { // This block is executed when the indicator is initially attached on a chart if(prev_calculated == 0){ } // This block is executed on every new bar open if(prev_calculated != rates_total && prev_calculated != 0){ } // This block is executed on arrival of new price (tick) data if(prev_calculated == rates_total){ } return(rates_total); } ...
A continuación, vamos a definir una paleta de colores para nuestras velas Heikin Ashi utilizando la directiva #property indicator_color1. Esta directiva nos permite especificar los colores predeterminados para la representación gráfica que hemos definido anteriormente. Para nuestros indicadores, usaremos tres colores:
- clrDarkGreen para las velas Heikin Ashi alcistas.
- clrDarkRed para las velas Heikin Ashi bajistas.
- clrYellow para velas neutras.
Añadiremos esta configuración de color justo debajo de nuestras otras directivas #property para que se aplique durante la inicialización del indicador. Después se muestra la línea de código que hay que añadir:
... #property indicator_plots 1 #property indicator_color1 clrDarkGreen, clrDarkRed, clrYellow ...
Ahora vamos a añadir una directiva que asigne nombres descriptivos a cada uno de nuestros búferes de indicador. Esto ayuda a los usuarios a comprender qué representa cada línea al visualizar los datos de la Ventana de datos en MetaTrader. Añada la siguiente línea de código justo debajo de las directivas #property existentes:
... #property indicator_color1 clrDarkGreen, clrDarkRed, clrYellow #property indicator_label1 "HeikinAshiOpen;HeikinAshiHigh;HeikinAshiLow;HeikinAshiClose" ...
Antes de implementar toda la lógica dentro del controlador de eventos OnCalculate, definamos primero algunas funciones auxiliares que se encargarán de los cálculos de Heikin Ashi y de su interpretación visual. Estas funciones de utilidad permitirán:
- Calcular los valores de Heikin Ashi (OHLC) a partir de los datos de precios normales.
- Determinar el color de cada vela Heikin Ashi según su dirección.
- Separar el procesamiento histórico completo de las actualizaciones en tiempo real para mejorar la eficiencia.
Vamos a añadir ahora las siguientes funciones justo debajo de nuestra función OnCalculate(). Una vez implementadas, las llamaremos desde dentro de OnCalculate según sea necesario, paso a paso.
... //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time [], const double &open [], const double &high [], const double &low [], const double &close [], const long &tick_volume[], const long &volume [], const int32_t &spread []) { ... return(rates_total); } // Utility functions //+----------------------------------------------------------------------------------+ //| Calculates Heikin Ashi values for all historical candles using price data arrays.| //+----------------------------------------------------------------------------------+ void GetHeikinAshiValues(const double &open[], const double &high[], const double &low[], const double &close[], const int32_t rates_total) { if(ArraySize(open) < rates_total){ return; } // Run a loop through all historical bars for(int i=0; i<rates_total; i++){ if(i == 0){ haOpen [i] = (open[i] + close[i]) / 2.0; haClose[i] = (open[i] + high[i] + low[i] + close[i]) / 4.0; haHigh [i] = MathMax(high[i], MathMax(open[i], close[i])); haLow [i] = MathMin(low [i], MathMin(open[i], close[i])); }else{ haOpen [i] = (haOpen[i-1] + haClose[i-1]) / 2.0; haClose[i] = (open[i] + high[i] + low[i] + close[i]) / 4.0; haHigh [i] = MathMax(high[i], MathMax(haOpen[i], haClose[i])); haLow [i] = MathMin(low [i], MathMin(haOpen[i], haClose[i])); } } } //+---------------------------------------------------------------------------------------+ //| Calculates Heikin Ashi values for the most recent candle only (for real-time updates).| //+---------------------------------------------------------------------------------------+ void GetCurrentHeikinAshiValue(const double &open[], const double &high[], const double &low[], const double &close[], const int32_t rates_total) { haOpen [rates_total - 1] = (haOpen[rates_total-2] + haClose[rates_total-2]) / 2.0; haClose[rates_total - 1] = (open[rates_total - 1] + high[rates_total - 1] + low[rates_total - 1] + close[rates_total - 1]) / 4.0; haHigh [rates_total - 1] = MathMax(high[rates_total - 1], MathMax(haOpen[rates_total - 1], haClose[rates_total - 1])); haLow [rates_total - 1] = MathMin(low [rates_total - 1], MathMin(haOpen[rates_total - 1], haClose[rates_total - 1])); } //+------------------------------------------------------------------------------------------------------------------+ //| Assigns a color code to each historical Heikin Ashi candle based on its direction (bullish, bearish, or neutral).| //+------------------------------------------------------------------------------------------------------------------+ void GetHeikinAshiColors(const int32_t rates_total){ for(int i=0; i<rates_total; i++){ if(haOpen[i] < haClose[i]){ colorBuffer[i] = 0; } else if(haOpen[i] > haClose[i]){ colorBuffer[i] = 1; } else { colorBuffer[i] = 2; } } } //+-----------------------------------------------------------------------------------------------+ //| Assigns a color code to the latest Heikin Ashi candle only (used for real-time color updates).| //+-----------------------------------------------------------------------------------------------+ void GetCurrentHeikinAshiColor(const int32_t rates_total){ if(haOpen[rates_total - 1] < haClose[rates_total - 1]){ colorBuffer[rates_total - 1] = 0; } else if(haOpen[rates_total - 1] > haClose[rates_total - 1]){ colorBuffer[rates_total - 1] = 1; } else { colorBuffer[rates_total - 1] = 2; } } ...
Ahora vamos a invocar la función GetHeikinAshiValues() dentro del controlador de eventos OnCalculate(), concretamente en las siguientes condiciones:
- Cuando prev_calculated == 0, lo que significa que el indicador se está cargando por primera vez y tenemos que procesar todas las velas históricas.
- Cuando prev_calculated != rates_total y prev_calculated != 0, lo que significa que se han añadido nuevas barras o que se ha actualizado el gráfico, por lo que debemos volver a calcular a partir del punto en el que lo dejamos.
Esta función se encarga de calcular toda la serie de velas Heikin Ashi, generando sus valores de apertura, máximo, mínimo y cierre a partir de los arrays de datos de precios estándar. Garantiza que nuestros búferes se llenen con precisión desde el principio o se actualicen correctamente cuando llegan nuevos datos.
... //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time [], const double &open [], const double &high [], const double &low [], const double &close [], const long &tick_volume[], const long &volume [], const int32_t &spread []) { // This block is executed when the indicator is initially attached on a chart if(prev_calculated == 0){ GetHeikinAshiValues(open, high, low, close, rates_total); } // This block is executed on every new bar open if(prev_calculated != rates_total && prev_calculated != 0){ GetHeikinAshiValues(open, high, low, close, rates_total); } // This block is executed on arrival of new price (tick) data if(prev_calculated == rates_total){ } return(rates_total); } ...
A continuación, llamaremos a la función GetHeikinAshiColors() dentro de los mismos bloques condicionales de OnCalculate(). Es decir:
- Cuando prev_calculated == 0, se colorean todas las velas históricas durante la carga inicial.
- Cuando prev_calculated != rates_total y prev_calculated != 0, actualizar los colores del conjunto de datos actualizado.
Esta función asigna un código numérico de color a cada vela Heikin Ashi en función de la relación entre su precio de apertura y su precio de cierre:
- 0 para las velas alcistas.
- 1 para las velas bajistas.
- 2 para las velas neutras.
Estos códigos de color se asignarán posteriormente a colores reales en el gráfico, lo que nos permitirá diferenciar visualmente entre la acción del precio alcista, bajista y neutral en la visualización final del indicador.
... //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time [], const double &open [], const double &high [], const double &low [], const double &close [], const long &tick_volume[], const long &volume [], const int32_t &spread []) { // This block is executed when the indicator is initially attached on a chart if(prev_calculated == 0){ ... GetHeikinAshiColors(rates_total); } // This block is executed on every new bar open if(prev_calculated != rates_total && prev_calculated != 0){ ... GetHeikinAshiColors(rates_total); } // This block is executed on arrival of new price (tick) data if(prev_calculated == rates_total){ } return(rates_total); } ...
Por último, llamaremos a las funciones GetCurrentHeikinAshiValue() y GetCurrentHeikinAshiColor() dentro del bloque if(prev_calculated == rates_total){}. Este bloque se ejecuta cuando el indicador se actualiza en tiempo real. Es decir, cuando llega un nuevo tick pero aún no se ha formado ninguna vela nueva.
Al llamar a:
- Con la función GetCurrentHeikinAshiValue(), nos aseguramos de que los valores más recientes de Heikin Ashi se vuelvan a calcular para la vela actual utilizando los datos de ticks más recientes.
- Con la función GetCurrentHeikinAshiColor(), asignamos inmediatamente el código de color correspondiente a la vela Heikin Ashi más reciente.
Esta configuración hace que el indicador responda de inmediato y garantiza que la vela más reciente siempre se muestre con precisión en el gráfico, incluso mientras se está formando.
... //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time [], const double &open [], const double &high [], const double &low [], const double &close [], const long &tick_volume[], const long &volume [], const int32_t &spread []) { ... // This block is executed on arrival of new price (tick) data if(prev_calculated == rates_total){ GetCurrentHeikinAshiValue(open, high, low, close, rates_total); GetCurrentHeikinAshiColor(rates_total); } return(rates_total); } ...
Ahora vamos a configurar nuestra representación gráfica utilizando la función PlotIndexSetInteger(), una opción tras otra, para que nuestro indicador Heikin Ashi se muestre al iniciarse. Lo haremos opción por opción para definir cómo se representarán nuestros búferes de indicador en el gráfico, incluido su tipo de dibujo.
Estos ajustes son esenciales para que el terminal MetaTrader 5 sepa cómo trazar los valores que hemos calculado. Una vez completado este paso, nuestro indicador Heikin Ashi se activará visualmente y aparecerá en el gráfico al aplicarlo. Repasemos ahora estas configuraciones. Empezaremos configurando nuestro trazado gráfico llamando a la función PlotIndexSetInteger() para establecer el tipo de dibujo. Esta línea en concreto indica a MetaTrader que renderice nuestro trazado utilizando velas de colores. Lo colocaremos justo debajo de nuestras llamadas a SetIndexBuffer() dentro del controlador de eventos OnInit(). Si esta configuración falla, se imprimirá un mensaje de error en el registro de Expertos con fines de depuración.
... //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { ... if(!SetIndexBuffer(4, colorBuffer, INDICATOR_COLOR_INDEX)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } // Configuration of Graphic Plots if(!PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_CANDLES)){ Print("Error while configuring graphic plots: ", GetLastError()); return INIT_FAILED; } } ...
Ahora, debemos asegurarnos de que los datos de Heikin Ashi aparezcan en la ventana de datos cuando pase el ratón por encima del gráfico. Para ello, usaremos la función PlotIndexSetInteger() con PLOT_SHOW_DATA establecido en true. Esta línea también debería ir justo después de la última dentro de la función OnInit(). Si no funciona, se imprimirá un mensaje de error.
... //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { ... // Configuration of Graphic Plots if(!PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_CANDLES)){ Print("Error while configuring graphic plots: ", GetLastError()); return INIT_FAILED; } if(!PlotIndexSetInteger(0, PLOT_SHOW_DATA, true)){ Print("Error while configuring graphic plots: ", GetLastError()); return INIT_FAILED; } } ...
A continuación, vamos a configurar algunos ajustes generales del indicador. El primer ajuste definirá el número de decimales (dígitos) que deben tener los valores del indicador, utilizando la función IndicatorSetInteger() con INDICATOR_DIGITS. Esto ayuda a garantizar que los valores del indicador se ajusten a la precisión del símbolo. Si surge algún problema, mostramos un mensaje de error para facilitar la depuración.
Ahora agregaremos las siguientes líneas de código debajo de las funciones PlotIndexSetInteger() en la sección OnInit().
... //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { ... if(!PlotIndexSetInteger(0, PLOT_SHOW_DATA, true)){ Print("Error while configuring graphic plots: ", GetLastError()); return INIT_FAILED; } // Configure Indicator if(!IndicatorSetInteger(INDICATOR_DIGITS, Digits())){ Print("Error while setting indicator values accuracy: ", GetLastError()); return INIT_FAILED; } } ...
A continuación, le daremos un nombre corto a nuestro indicador utilizando la función IndicatorSetString(). Este nombre, «HeikinAshi», aparecerá en la ventana de datos y en el gráfico para ayudar a los usuarios a identificar fácilmente el indicador. Si no se puede establecer el nombre, mostraremos un mensaje de error para saber qué ha fallado.
... //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { ... // Configure Indicator if(!IndicatorSetInteger(INDICATOR_DIGITS, Digits())){ Print("Error while setting indicator values accuracy: ", GetLastError()); return INIT_FAILED; } if(!IndicatorSetString(INDICATOR_SHORTNAME, "HeikinAshi")){ Print("Error while setting indicator shortname: ", GetLastError()); return INIT_FAILED; } return INIT_SUCCEEDED; } ...
Llegados a este punto, ya hemos escrito con éxito todos los componentes principales de nuestro indicador Heikin Ashi personalizado. Ahora, haga clic en el botón «Compilar» de MetaEditor. Si todo se ha hecho correctamente, el indicador debería compilarse sin errores y estar listo para su uso en su gráfico. Si ha seguido todos los pasos correctamente, su código fuente completo debería tener ahora este aspecto. En caso de que algo haya salido mal o le aparezcan errores de compilación, puede comparar su código con la versión que aparece a continuación para detectar y corregir cualquier error.
//+------------------------------------------------------------------+ //| heikinAshiIndicator.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/chachaian" #property version "1.00" #property indicator_chart_window #property indicator_buffers 5 #property indicator_plots 1 #property indicator_color1 C'38,166,154', C'239,83,80', clrYellow #property indicator_label1 "HeikinAshiOpen;HeikinAshiHigh;HeikinAshiLow;HeikinAshiClose" //Global variables double haOpen []; double haHigh []; double haLow []; double haClose []; double colorBuffer []; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Registration of indicator buffers if(!SetIndexBuffer(0, haOpen, INDICATOR_DATA)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } if(!SetIndexBuffer(1, haHigh, INDICATOR_DATA)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } if(!SetIndexBuffer(2, haLow, INDICATOR_DATA)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } if(!SetIndexBuffer(3, haClose, INDICATOR_DATA)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } if(!SetIndexBuffer(4, colorBuffer, INDICATOR_COLOR_INDEX)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } //--- Configuration of graphic plots if(!PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_CANDLES)){ Print("Error while configuring graphic plots: ", GetLastError()); return INIT_FAILED; } if(!PlotIndexSetInteger(0, PLOT_SHOW_DATA, true)){ Print("Error while configuring graphic plots: ", GetLastError()); return INIT_FAILED; } //--- General indicator configurations if(!IndicatorSetInteger(INDICATOR_DIGITS, Digits())){ Print("Error while setting indicator values accuracy: ", GetLastError()); return INIT_FAILED; } if(!IndicatorSetString(INDICATOR_SHORTNAME, "HeikinAshi")){ Print("Error while setting indicator shortname: ", GetLastError()); return INIT_FAILED; } return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time [], const double &open [], const double &high [], const double &low [], const double &close [], const long &tick_volume[], const long &volume [], const int32_t &spread []) { //--- This block is executed when the indicator is initially attached on a chart if(prev_calculated == 0){ GetHeikinAshiValues(open, high, low, close, rates_total); GetHeikinAshiColors(rates_total); } //--- This block is executed on every new bar open if(prev_calculated != rates_total && prev_calculated != 0){ GetHeikinAshiValues(open, high, low, close, rates_total); GetHeikinAshiColors(rates_total); } //--- This block is executed on arrival of new price (tick) data if(prev_calculated == rates_total){ GetCurrentHeikinAshiValue(open, high, low, close, rates_total); GetCurrentHeikinAshiColor(rates_total); } return(rates_total); } //--- Utility functions //+----------------------------------------------------------------------------------+ //| Calculates Heikin Ashi values for all historical candles using price data arrays.| //+----------------------------------------------------------------------------------+ void GetHeikinAshiValues(const double &open[], const double &high[], const double &low[], const double &close[], const int32_t rates_total) { if(ArraySize(open) < rates_total){ return; } //--- Run a loop through all historical bars for(int i=0; i<rates_total; i++){ if(i == 0){ haOpen [i] = (open[i] + close[i]) / 2.0; haClose[i] = (open[i] + high[i] + low[i] + close[i]) / 4.0; haHigh [i] = MathMax(high[i], MathMax(open[i], close[i])); haLow [i] = MathMin(low [i], MathMin(open[i], close[i])); }else{ haOpen [i] = (haOpen[i-1] + haClose[i-1]) / 2.0; haClose[i] = (open[i] + high[i] + low[i] + close[i]) / 4.0; haHigh [i] = MathMax(high[i], MathMax(haOpen[i], haClose[i])); haLow [i] = MathMin(low [i], MathMin(haOpen[i], haClose[i])); } } } //+---------------------------------------------------------------------------------------+ //| Calculates Heikin Ashi values for the most recent candle only (for real-time updates).| //+---------------------------------------------------------------------------------------+ void GetCurrentHeikinAshiValue(const double &open[], const double &high[], const double &low[], const double &close[], const int32_t rates_total) { haOpen [rates_total - 1] = (haOpen[rates_total-2] + haClose[rates_total-2]) / 2.0; haClose[rates_total - 1] = (open[rates_total - 1] + high[rates_total - 1] + low[rates_total - 1] + close[rates_total - 1]) / 4.0; haHigh [rates_total - 1] = MathMax(high[rates_total - 1], MathMax(haOpen[rates_total - 1], haClose[rates_total - 1])); haLow [rates_total - 1] = MathMin(low [rates_total - 1], MathMin(haOpen[rates_total - 1], haClose[rates_total - 1])); } //+------------------------------------------------------------------------------------------------------------------+ //| Assigns a color code to each historical Heikin Ashi candle based on its direction (bullish, bearish, or neutral).| //+------------------------------------------------------------------------------------------------------------------+ void GetHeikinAshiColors(const int32_t rates_total) { for(int i=0; i<rates_total; i++){ if(haOpen[i] < haClose[i]){ colorBuffer[i] = 0; } if(haOpen[i] > haClose[i]){ colorBuffer[i] = 1; } if(haOpen[i] == haClose[i]){ colorBuffer[i] = 2; } } } //+-----------------------------------------------------------------------------------------------+ //| Assigns a color code to the latest Heikin Ashi candle only (used for real-time color updates).| //+-----------------------------------------------------------------------------------------------+ void GetCurrentHeikinAshiColor(const int32_t rates_total) { if(haOpen[rates_total - 1] < haClose[rates_total - 1]){ colorBuffer[rates_total - 1] = 0; } else if(haOpen[rates_total - 1] > haClose[rates_total - 1]){ colorBuffer[rates_total - 1] = 1; } else { colorBuffer[rates_total - 1] = 2; } } //+------------------------------------------------------------------+
Pruebas y ajuste visual
Antes de empezar a probar visualmente el indicador Heikin Ashi, es recomendable despejar el gráfico para que todo se vea con claridad. Vamos a definir una pequeña función que haga precisamente eso. Ajusta la configuración del fondo, la cuadrícula y los colores para que el gráfico quede ordenado y las velas se vean con claridad. Aquí está el código:
... //+-----------------------------------------------------------------------------------------------+ //| Assigns a color code to the latest Heikin Ashi candle only (used for real-time color updates).| //+-----------------------------------------------------------------------------------------------+ void GetCurrentHeikinAshiColor(const int32_t rates_total){ if(haOpen[rates_total - 1] < haClose[rates_total - 1]){ colorBuffer[rates_total - 1] = 0; } else if(haOpen[rates_total - 1] > haClose[rates_total - 1]){ colorBuffer[rates_total - 1] = 1; } else { colorBuffer[rates_total - 1] = 2; } } //+-------------------------------------------------+ //| This function configures the chart's appearance.| //+-------------------------------------------------+ bool ConfigureChartAppearance() { if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){ Print("Error while setting chart background, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){ Print("Error while setting chart grid, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_MODE, CHART_LINE)){ Print("Error while setting chart mode, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){ Print("Error while setting chart foreground, ", GetLastError()); return false; } return true; }
Piense en esta función como el estilista personal de su gráfico. Hace lo siguiente:
- Establece un fondo blanco para mayor claridad.
- Elimina la cuadrícula para que todo se vea más ordenado.
- Cambia el tipo de gráfico a un gráfico de líneas para que no interfiera con sus velas personalizadas.
- Garantiza que el primer plano sea negro para lograr un buen contraste.
Si alguna de estas acciones falla, se muestra un mensaje de error en el terminal para facilitar la depuración. Ahora llamaremos a la función ConfigureChartAppearance() dentro del controlador de eventos OnInit() para que ajuste automáticamente el gráfico cuando se cargue el indicador. A continuación le mostramos cómo hacerlo:
... //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { if(!ConfigureChartAppearance()){ Print("Error while configuring chart appearance: ", GetLastError()); return INIT_FAILED; } // Registration of indicator buffers if(!SetIndexBuffer(0, haOpen, INDICATOR_DATA)){ Print("Error while registering an indicator buffer: ", GetLastError()); return INIT_FAILED; } ... } ...
Hemos completado con éxito la creación de nuestro indicador Heikin Ashi personalizado. Todo está listo, desde el cálculo de las velas Heikin Ashi hasta su representación clara en el gráfico con un aspecto bien configurado. Ahora es el momento de adjuntar el indicador a un gráfico y verlo en acción.

Ya hemos adjuntado nuestro indicador personalizado Heikin Ashi al gráfico de oro en el marco temporal H1, y funciona a la perfección. Todo se muestra tal y como se esperaba, lo que significa que nuestro código funciona correctamente y que el gráfico está listo para el ajuste visual y pruebas adicionales.
Conclusión
En esta parte, hemos creado con éxito un indicador personalizado Heikin Ashi totalmente funcional en MQL5. Además, hemos recorrido cada paso: desde configurar los búferes y calcular los valores hasta personalizar el aspecto del gráfico y, por último, adjuntar el indicador al gráfico de oro en el marco temporal H1. Hemos comprobado que todo funciona según lo previsto. Para ayudarle a seguir el tutorial o a resolver posibles problemas, hemos incluido tanto el código fuente completo como la versión compilada junto con este tutorial.
En la siguiente parte de esta serie, daremos un paso más y crearemos un asesor experto que utilice nuestro indicador Heikin Ashi para tomar decisiones de trading. No se pierda las próximas novedades.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19260
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.
Utilizando redes neuronales en MetaTrader
Superando las limitaciones del aprendizaje automático (Parte 3): Una nueva perspectiva sobre el error irreducible
Particularidades del trabajo con números del tipo double en MQL4
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 38): VWAP con búfer de ticks y motor de desequilibrio de ventana corta
- 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