Complete .mq5 source code

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.