Custom Indicator MQL5: Complete Coding Tutorial for Traders 2026
This page provides a complete step-by-step tutorial for building custom indicators in MQL5, covering buffer setup, drawing styles, input parameters, and multi-timeframe data access. Whether you are a beginner writing your first OnCalculate() function or an experienced coder looking to implement advanced buffer indexing, this guide walks through every concept with working code examples.
How the Supertrend signal works
Bullish flip
N/A — this is a tutorial/indicator reference page. The example indicator demonstrates how to compute and plot a dual-smoothed moving average overlay, which traders can adapt into their own entry signal logic.
Bearish flip
N/A — this is a tutorial/indicator reference page. Exit conditions are not defined here; the focus is on correct MQL5 indicator architecture, buffer registration, and real-time recalculation patterns.
MQL5 Custom Indicator Code
//+------------------------------------------------------------------+
//| DualSmoothMA.mq5 |
//| Custom Indicator MQL5 Tutorial — Pineify.app |
//| Demonstrates: buffers, inputs, OnCalculate, OnInit, OnDeinit |
//+------------------------------------------------------------------+
#property copyright "Pineify.app"
#property link "https://pineify.app"
#property version "1.00"
#property description "Dual-smoothed Moving Average — MQL5 tutorial indicator"
// Indicator drawn in the main chart window
#property indicator_chart_window
// Two indicator buffers: fast MA and slow MA
#property indicator_buffers 2
#property indicator_plots 2
//--- Plot 1: Fast MA
#property indicator_label1 "Fast MA"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
//--- Plot 2: Slow MA
#property indicator_label2 "Slow MA"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrOrangeRed
#property indicator_style2 STYLE_SOLID
#property indicator_width2 2
//--- Input parameters
input int InpFastPeriod = 9; // Fast MA period
input int InpSlowPeriod = 21; // Slow MA period
input int InpSmoothPeriod = 3; // Secondary smoothing period
input ENUM_MA_METHOD InpMAMethod = MODE_EMA; // MA method
input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; // Applied price
//--- Indicator buffers
double FastMABuffer[];
double SlowMABuffer[];
//--- Indicator handles for internal MA calculations
int handleFastMA = INVALID_HANDLE;
int handleSlowMA = INVALID_HANDLE;
int handleSmoothFast = INVALID_HANDLE;
int handleSmoothSlow = INVALID_HANDLE;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Validate input periods
if(InpFastPeriod <= 0 || InpSlowPeriod <= 0 || InpSmoothPeriod <= 0)
{
Print("ERROR: All period inputs must be greater than zero.");
return INIT_PARAMETERS_INCORRECT;
}
if(InpFastPeriod >= InpSlowPeriod)
{
Print("ERROR: Fast period must be less than slow period.");
return INIT_PARAMETERS_INCORRECT;
}
//--- Map arrays to indicator buffers (index matches #property indicator_buffers order)
SetIndexBuffer(0, FastMABuffer, INDICATOR_DATA);
SetIndexBuffer(1, SlowMABuffer, INDICATOR_DATA);
//--- Set empty value for gaps (bars not yet calculated)
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
//--- Shift labels to align with price visually
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, InpFastPeriod + InpSmoothPeriod - 2);
PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, InpSlowPeriod + InpSmoothPeriod - 2);
//--- Create handles for first-pass MA (fast and slow)
handleFastMA = iMA(_Symbol, _Period, InpFastPeriod, 0, InpMAMethod, InpPrice);
if(handleFastMA == INVALID_HANDLE)
{
Print("ERROR: Failed to create fast MA handle. Error=", GetLastError());
return INIT_FAILED;
}
handleSlowMA = iMA(_Symbol, _Period, InpSlowPeriod, 0, InpMAMethod, InpPrice);
if(handleSlowMA == INVALID_HANDLE)
{
Print("ERROR: Failed to create slow MA handle. Error=", GetLastError());
return INIT_FAILED;
}
//--- Create handles for second-pass smoothing (MA of MA)
handleSmoothFast = iMAOnArray(FastMABuffer, 0, InpSmoothPeriod, 0, MODE_SMA, 0);
// Note: For tutorial clarity we smooth inside OnCalculate manually below.
// iMAOnArray is shown here as reference; we release this handle immediately.
if(handleSmoothFast != INVALID_HANDLE)
IndicatorRelease(handleSmoothFast);
//--- Set short name visible in the Data Window
string shortName = StringFormat("DualSmoothMA(%d,%d,%d)", InpFastPeriod, InpSlowPeriod, InpSmoothPeriod);
IndicatorSetString(INDICATOR_SHORTNAME, shortName);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- Release all indicator handles to free terminal resources
if(handleFastMA != INVALID_HANDLE)
{
IndicatorRelease(handleFastMA);
handleFastMA = INVALID_HANDLE;
}
if(handleSlowMA != INVALID_HANDLE)
{
IndicatorRelease(handleSlowMA);
handleSlowMA = INVALID_HANDLE;
}
Print("DualSmoothMA deinitialized. Reason code=", reason);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
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[])
{
//--- Minimum bars needed before we can draw anything
int minBars = InpSlowPeriod + InpSmoothPeriod;
if(rates_total < minBars)
return 0;
//--- Determine the starting bar for this call
// prev_calculated == 0 means a full recalculation is required
int start = (prev_calculated == 0) ? minBars - 1 : prev_calculated - 1;
//--- Copy fast MA values from handle into a temporary buffer
double tempFast[];
double tempSlow[];
ArraySetAsSeries(tempFast, false);
ArraySetAsSeries(tempSlow, false);
if(CopyBuffer(handleFastMA, 0, 0, rates_total, tempFast) <= 0)
{
Print("ERROR: CopyBuffer fast MA failed. Error=", GetLastError());
return prev_calculated;
}
if(CopyBuffer(handleSlowMA, 0, 0, rates_total, tempSlow) <= 0)
{
Print("ERROR: CopyBuffer slow MA failed. Error=", GetLastError());
return prev_calculated;
}
//--- Apply secondary SMA smoothing and write to output buffers
for(int i = start; i < rates_total; i++)
{
//--- Fast: SMA of fast MA values over InpSmoothPeriod bars
double sumFast = 0.0;
double sumSlow = 0.0;
int count = 0;
for(int k = 0; k < InpSmoothPeriod && (i - k) >= 0; k++)
{
sumFast += tempFast[i - k];
sumSlow += tempSlow[i - k];
count++;
}
FastMABuffer[i] = (count > 0) ? sumFast / count : EMPTY_VALUE;
SlowMABuffer[i] = (count > 0) ? sumSlow / count : EMPTY_VALUE;
}
//--- Return the total number of bars processed
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 ATR inputs, alert rules, visual style, or confirmation filter you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Series evaluated on chart updates | Event handlers with explicit buffers |
| Deployment | Runs in TradingView with alerts | Runs in the MT5 terminal or on a VPS |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Strategy Tester for strategy scripts | Strategy Tester for Expert Advisors |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
The calculation can be implemented on either platform, but the runtime model and buffer APIs differ. Read the Pine Script Supertrend reference before porting logic between them.
Frequently Asked Questions
Related MQL5 pages
Risk and testing note
Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.