Heikin Ashi MQL5 Indicator: Candle Buffers, EMA Overlay and Alerts
This custom indicator calculates Heikin Ashi OHLC, draws per-bar colors and optionally overlays an EMA of ordinary closing prices. HA close averages the current OHLC; HA open recursively averages the prior HA open and close. These synthetic values are not a trade ledger or executable price feed. The code offers three-completed-bar direction alerts, not an automated entry/exit system or a verified performance improvement.
How the indicator signal works
Calculation and signal conditions
The indicator computes from oldest to newest data. HA close is (O+H+L+C)/4; subsequent HA open is the mean of prior HA open and close, seeded by the first regular open/close mean. HA high and low include both the regular extreme and HA body. An alert condition requires three completed HA bodies strictly bullish or strictly bearish. It does not require wickless candles, a 200 EMA filter, or orders; the optional EMA is only a regular-price overlay.
Interpretation and limitations
Mixed or doji bodies reset the alert trend state. The current candle can change until close; recursive values also depend on the available starting history. Smoothing does not imply an exact one-bar delay or proven reduction in false signals. This indicator has no stop, target, trailing exit or partial close. HA highs/lows can coincide with ordinary extremes, but a synthetic displayed level does not imply a fill at that price. Any EA must use a separately defined and tested execution model.
MQL5 Custom Indicator Code
//+------------------------------------------------------------------+
//| HeikinAshi_MT5.mq5 |
//| Heikin Ashi MQL5 indicator — smoothed candlestick charting |
//| with trend direction alerts and optional EMA overlay. |
//| For educational purposes only. Not financial advice. |
//| No verified strategy performance is supplied. |
//+------------------------------------------------------------------+
#property copyright "Pineify — pineify.app"
#property link "https://pineify.app"
#property version "1.00"
//--- Indicator draws HA candles in the main chart window
#property indicator_chart_window
#property indicator_buffers 6
#property indicator_plots 2
//--- Plot 0: four OHLC buffers plus one color-index buffer
#property indicator_label1 "HA Candle"
#property indicator_type1 DRAW_COLOR_CANDLES
#property indicator_color1 clrGreen,clrRed
#property indicator_style1 STYLE_SOLID
#property indicator_width1 1
//--- Plot 1: Optional EMA overlay line
#property indicator_label2 "HA EMA"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrDodgerBlue
#property indicator_style2 STYLE_SOLID
#property indicator_width2 1
//--- Input parameters (adjustable without recompiling)
input int InpEMAPeriod = 0; // EMA Period (0=disable overlay)
input bool InpAlerts = true; // Enable desktop alerts
input bool InpPushAlerts = false; // Enable mobile push alerts
input color InpBullColor = clrGreen; // Bullish candle body color
input color InpBearColor = clrRed; // Bearish candle body color
//--- Indicator buffers
double HA_Open[]; // Plot 0 — HA open
double HA_High[]; // Plot 0 — HA high
double HA_Low[]; // Plot 0 — HA low
double HA_Close[]; // Plot 0 — HA close
double HA_Color[]; // Plot 0 color indexes
double HA_EMA[]; // Plot 1 — optional EMA line
//--- Global handle for iMA
int g_maHandle = INVALID_HANDLE;
//--- Alert state (avoid duplicate alerts on the same bar)
datetime g_lastAlertTime = 0;
int g_prevTrend = 0; // 1=bullish, -1=bearish, 0=unknown
//+------------------------------------------------------------------+
//| OnInit — bind buffers, create iMA handle, assign colors |
//+------------------------------------------------------------------+
int OnInit()
{
if(InpEMAPeriod<0) return INIT_PARAMETERS_INCORRECT;
//--- Bind OHLC, color index, then EMA buffers
SetIndexBuffer(0, HA_Open, INDICATOR_DATA);
SetIndexBuffer(1, HA_High, INDICATOR_DATA);
SetIndexBuffer(2, HA_Low, INDICATOR_DATA);
SetIndexBuffer(3, HA_Close, INDICATOR_DATA);
SetIndexBuffer(4, HA_Color, INDICATOR_COLOR_INDEX);
SetIndexBuffer(5, HA_EMA, INDICATOR_DATA);
ArraySetAsSeries(HA_Open,false); ArraySetAsSeries(HA_High,false);
ArraySetAsSeries(HA_Low,false); ArraySetAsSeries(HA_Close,false);
ArraySetAsSeries(HA_Color,false); ArraySetAsSeries(HA_EMA,false);
PlotIndexSetInteger(0,PLOT_COLOR_INDEXES,2);
//--- Assign bull and bear candle colors via PlotIndexSetInteger
// Color index 0 = bullish body, index 1 = bearish body
PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, InpBullColor);
PlotIndexSetInteger(0, PLOT_LINE_COLOR, 1, InpBearColor);
//--- Create EMA handle if period is greater than 0
if(InpEMAPeriod > 0)
{
g_maHandle = iMA(_Symbol, _Period, InpEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(g_maHandle == INVALID_HANDLE)
{
Print("ERROR: iMA handle creation failed. Error code: ", GetLastError());
return INIT_FAILED;
}
PlotIndexSetString(1, PLOT_LABEL,
StringFormat("Close EMA(%d)", InpEMAPeriod));
}
else
{
//--- Hide the EMA plot when disabled
PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);
}
//--- Set the indicator short name (appears in the DataWindow)
IndicatorSetString(INDICATOR_SHORTNAME,
StringFormat("HeikinAshi(%s)", _Symbol));
IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
//--- Bars with no value are not drawn
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
Print("Heikin Ashi initialised on ", _Symbol, " / ", EnumToString(_Period));
Print("DISCLAIMER: For educational purposes only. Not financial advice.");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| OnDeinit — release the indicator handle |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(g_maHandle != INVALID_HANDLE)
{
IndicatorRelease(g_maHandle);
g_maHandle = INVALID_HANDLE;
}
Print("Heikin Ashi removed. Reason: ", reason);
}
//+------------------------------------------------------------------+
//| OnCalculate — main computation loop for Heikin Ashi values |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
//--- Need at least 2 bars for HA calculation
if(rates_total < 2)
return 0;
ArraySetAsSeries(time,false); ArraySetAsSeries(open,false);
ArraySetAsSeries(high,false); ArraySetAsSeries(low,false); ArraySetAsSeries(close,false);
//--- Determine the starting index
int start;
if(prev_calculated == 0 || prev_calculated > rates_total)
{
ArrayInitialize(HA_EMA,EMPTY_VALUE);
g_prevTrend=0; g_lastAlertTime=0;
//--- First run: seed bar 0 with regular candle data
// haOpen seed = (regular open + regular close) / 2
HA_Open[0] = (open[0] + close[0]) / 2.0;
HA_High[0] = high[0];
HA_Low[0] = low[0];
HA_Close[0] = (open[0] + high[0] + low[0] + close[0]) / 4.0;
HA_Color[0] = HA_Close[0]>=HA_Open[0] ? 0 : 1;
start = 1;
}
else
{
//--- Subsequent ticks: recalculate from the last modified bar
start = prev_calculated - 1;
}
if(start < 1)
start = 1;
//--- Copy EMA data from the iMA handle if active
double emaValues[];
if(g_maHandle != INVALID_HANDLE)
{
if(CopyBuffer(g_maHandle, 0, 0, rates_total, emaValues) != rates_total)
{
Print("ERROR: CopyBuffer from iMA returned no data");
return 0;
}
}
if(g_maHandle!=INVALID_HANDLE && rates_total>=InpEMAPeriod)
HA_EMA[0]=InpEMAPeriod==1 ? emaValues[0] : EMPTY_VALUE;
//--- Main Heikin Ashi calculation loop
for(int i = start; i < rates_total; i++)
{
//--- HA formulas (forward iteration, oldest bar first)
double haClose = (open[i] + high[i] + low[i] + close[i]) / 4.0;
double haOpen = (HA_Open[i-1] + HA_Close[i-1]) / 2.0;
HA_Close[i] = haClose;
HA_Open[i] = haOpen;
HA_Color[i] = haClose>=haOpen ? 0 : 1;
//--- haHigh = max(actual high, haOpen, haClose)
HA_High[i] = MathMax(high[i], MathMax(haOpen, haClose));
//--- haLow = min(actual low, haOpen, haClose)
HA_Low[i] = MathMin(low[i], MathMin(haOpen, haClose));
//--- Write EMA overlay (or EMPTY_VALUE when disabled)
if(g_maHandle != INVALID_HANDLE && i>=InpEMAPeriod-1)
HA_EMA[i] = emaValues[i];
else
HA_EMA[i] = EMPTY_VALUE;
}
//--- Alert on sustained trend change (last completed bar only)
if((InpAlerts || InpPushAlerts) && prev_calculated > 0 && rates_total >= 4)
{
int last = rates_total - 1; // current forming bar
int p1 = rates_total - 2; // last completed bar
int p2 = rates_total - 3; // second-last completed
int p3 = rates_total - 4; // third-last completed
//--- Check 3 completed bars for consistent direction
bool bullP1 = (HA_Close[p1] > HA_Open[p1]);
bool bullP2 = (HA_Close[p2] > HA_Open[p2]);
bool bullP3 = (HA_Close[p3] > HA_Open[p3]);
//--- Require 3 consecutive same-direction HA candles
// before declaring a trend flip
int newTrend = 0;
if(bullP1 && bullP2 && bullP3)
newTrend = 1;
else if(HA_Close[p1]<HA_Open[p1] && HA_Close[p2]<HA_Open[p2] && HA_Close[p3]<HA_Open[p3])
newTrend = -1;
//--- Fire alert only when sustained trend changes
if(newTrend != 0 && newTrend != g_prevTrend &&
time[last] != g_lastAlertTime)
{
string direction = (newTrend == 1) ? "BULLISH" : "BEARISH";
string msg = StringFormat("%s %s | HA trend: %s @ %.5f",
_Symbol, EnumToString(_Period),
direction, close[p1]);
if(InpAlerts) Alert(msg);
if(InpPushAlerts && !SendNotification(msg))
Print("Push notification failed: ",GetLastError());
g_lastAlertTime = time[last];
g_prevTrend = newTrend;
}
//--- Reset trend state when HA candles become mixed
if(newTrend==0)
g_prevTrend = 0;
}
return rates_total;
}
//+------------------------------------------------------------------+
Copy this code into MetaEditor, save it in the MQL5/Indicators folder, and compile with F7.
Change the settings or add your own signal filter
Describe the inputs, buffers, alerts, visual style, or confirmation filter you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.
Frequently Asked Questions
Related MQL5 pages
Technical references
Risk and testing note
These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.