A lot of traders and developers ask how to create a custom indicator instead of relying on built-in tools. In this guide, we build a moving average crossover indicator in MQL4 from scratch, then validate it properly using Forex Tester Online.
You will understand the logic, see how the code works, and learn why testing matters more than compilation.
What We Are Building
We are creating a Simple Moving Average crossover indicator, one of the most common foundations in technical analysis.
Strategy Logic
-
Fast MA: 9-period
Detects short-term momentum -
Slow MA: 50-period
Confirms overall trend
Signals
-
Buy Signal:
Fast MA crosses above Slow MA → Green arrow -
Sell Signal:
Fast MA crosses below Slow MA → Red arrow
Simple logic. Clear signals. Easy to extend later.
Watch Step-by-step video tutorial:
Creating the Indicator in MetaEditor
Open MetaEditor and follow these steps.
Step 1: Create a New Indicator
-
Click New
-
Select Custom Indicator
-
Give it a name
-
Choose OnCalculate
-
Click Finish
Indicator Structure Explained
At the top, we define:
-
#property indicator_chart_window
Ensures signals appear on the price chart
Key Functions
-
OnInit()
Runs once when the indicator loads
Used to configure buffers and styles -
OnCalculate()
Runs on every tick or new bar
Contains the core logic
#property indicator_chart_window
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 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[])
{
return(rates_total);
}
Defining Indicator Buffers
We use four buffers:
-
Buy arrows
-
Sell arrows
-
Fast MA line
-
Slow MA line
This allows us to draw both lines and signals on the chart.
Visual Properties
-
MA lines are drawn as solid lines
-
Clear colors and width for visibility
Input Parameters
We expose these values so they can be adjusted easily:
-
Fast MA period (default 9)
-
Slow MA period (default 50)
This makes the indicator flexible without changing code.
#property indicator_buffers 4
#property indicator_color3 Lime
#property indicator_color4 Red
// --- Drawing properties for the MA Lines
#property indicator_label1 "FastMA"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrYellow
#property indicator_style1 STYLE_SOLID
#property indicator_width1 5
#property indicator_label2 "SlowMA"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrGreen
#property indicator_style2 STYLE_SOLID
#property indicator_width2 5
//--- input parameters
input int FastMAPeriod = 9;
input int SlowMAPeriod = 50;
//--- indicator buffers
double BuyBuffer[];
double SellBuffer[];
double FastMA_Buffer[];
double SlowMA_Buffer[];
Initializing Buffers
Inside OnInit():
-
Declare four buffers
-
Assign each buffer using
SetIndexBuffer -
Define which buffers draw arrows and which draw lines
Once initialized, the indicator knows where to store and display data.
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
IndicatorBuffers(4);
SetIndexBuffer(0, BuyBuffer);
SetIndexBuffer(1, SellBuffer);
SetIndexStyle(0, DRAW_ARROW, EMPTY, 5, Lime);
SetIndexArrow(0, 233); // Up arrow
SetIndexStyle(1, DRAW_ARROW, EMPTY, 5, Red);
SetIndexArrow(1, 234); // Down arrow
SetIndexBuffer(2, FastMA_Buffer);
SetIndexBuffer(3, SlowMA_Buffer);
//---
return(INIT_SUCCEEDED);
}
Writing the Crossover Logic
Now the real logic begins.
Step 1: Validate Available Bars
Before calculations, we check that enough bars exist to avoid errors.
int counted_bars = IndicatorCounted();
if (counted_bars < 0) return (-1);
if (counted_bars > 0) counted_bars--;
int limit = Bars - counted_bars;
if (limit < 2) return(0);
Step 2: Loop Through Bars
We loop through available bars using rates_total.
For each bar, we calculate:
-
Fast MA (current bar)
-
Slow MA (current bar)
-
Fast MA (previous bar)
-
Slow MA (previous bar)
This comparison is essential to detect crossovers correctly.
Step 3: Draw MA Lines
For each bar:
-
Assign Fast MA value to Fast MA buffer
-
Assign Slow MA value to Slow MA buffer
This draws both moving averages on the chart.
int counted_bars = IndicatorCounted();
if (counted_bars < 0) return (-1);
if (counted_bars > 0) counted_bars--;
int limit = Bars - counted_bars;
if (limit < 2) return(0);
for(int i=1;i< rates_total;i++)
{
double fastMA_prev = iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, i+1);
double slowMA_prev = iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, i+1);
double fastMA_curr = iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, i);
double slowMA_curr = iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, i);
FastMA_Buffer[i] = fastMA_curr;
SlowMA_Buffer[i] = slowMA_curr;
}
Detecting Buy and Sell Signals
Buy Condition
-
Fast MA (previous) < Slow MA (previous)
-
Fast MA (current) > Slow MA (current)
When this happens:
-
Draw a buy arrow
-
Hide sell arrow
Sell Condition
-
Fast MA (previous) > Slow MA (previous)
-
Fast MA (current) < Slow MA (current)
When this happens:
-
Draw a sell arrow
-
Hide buy arrow
All Other Cases
Both buffers remain empty to avoid false signals.
Compile and Visual Test
Compile the code.
No errors.
Load the indicator in MT4.
You now see:
-
Fast and slow MA lines
-
Buy and sell arrows at crossover points
At this stage, the indicator works visually.
Full code:
//+------------------------------------------------------------------+
//| Simple MA Crossover.mq4 |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_color3 Lime
#property indicator_color4 Red
// --- Drawing properties for the MA Lines
#property indicator_label1 "FastMA"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrYellow
#property indicator_style1 STYLE_SOLID
#property indicator_width1 5
#property indicator_label2 "SlowMA"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrGreen
#property indicator_style2 STYLE_SOLID
#property indicator_width2 5
//--- input parameters
input int FastMAPeriod = 9;
input int SlowMAPeriod = 50;
//--- indicator buffers
double BuyBuffer[];
double SellBuffer[];
double FastMA_Buffer[];
double SlowMA_Buffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
IndicatorBuffers(4);
SetIndexBuffer(0, BuyBuffer);
SetIndexBuffer(1, SellBuffer);
SetIndexStyle(0, DRAW_ARROW, EMPTY, 5, Lime);
SetIndexArrow(0, 233); // Up arrow
SetIndexStyle(1, DRAW_ARROW, EMPTY, 5, Red);
SetIndexArrow(1, 234); // Down arrow
SetIndexBuffer(2, FastMA_Buffer);
SetIndexBuffer(3, SlowMA_Buffer);
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 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[])
{
//---
int counted_bars = IndicatorCounted();
if (counted_bars < 0) return (-1);
if (counted_bars > 0) counted_bars--;
int limit = Bars - counted_bars;
if (limit < 2) return(0);
for(int i=1;i< rates_total;i++)
{
double fastMA_prev = iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, i+1);
double slowMA_prev = iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, i+1);
double fastMA_curr = iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, i);
double slowMA_curr = iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, i);
FastMA_Buffer[i] = fastMA_curr;
SlowMA_Buffer[i] = slowMA_curr;
if(fastMA_prev < slowMA_prev && fastMA_curr > slowMA_curr)
{
BuyBuffer[i] = Low[i] - (Point * 10);
SellBuffer[i] = EMPTY_VALUE;
}
else if(fastMA_prev > slowMA_prev && fastMA_curr < slowMA_curr)
{
SellBuffer[i] = High[i] + (Point * 10);
BuyBuffer[i] = EMPTY_VALUE;
}
else
{
BuyBuffer[i] = EMPTY_VALUE;
SellBuffer[i] = EMPTY_VALUE;
}
}
//--- return value of prev_calculated for next call
return(rates_total);
}
//+------------------------------------------------------------------+
But that is not enough.
Why Visual Testing Is Not Enough
Here is the harsh truth.
A compiling indicator is not validated.
MT4’s built-in tester:
-
Uses low-resolution data
-
Produces misleading results
-
Often hides real-world failures
To trust your logic, you must replay the market candle by candle.
Watch a short clip:
Backtesting with Forex Tester Online
This is where real validation begins.
Step 1: Create a New Project
-
Select symbol
-
Name the project
-
Start the simulation
Step 2: Load the Indicator
-
Upload the indicator (JS format required)
-
Set Fast and Slow MA periods
-
Apply it to the chart
Note: You can convert .mq4 format to .js format yourself using Cursor AI or request Forex Tester Online team by clicking here: https://forextester.com/en/support/
Step 3: Replay the Market
You can:
-
Move one candle at a time
-
Jump directly to the next signal
-
Watch entries and exits without guessing
This gives you clarity that screenshots never will.
Trade Simulation and Analytics
-
Open trades at buy signals
-
Close trades at sell signals
-
Review performance using Smart Analytics
You get insights like:
-
Risk per trade adjustments
-
Stop-loss recommendations
-
Profit optimization suggestions
This is real testing, not assumptions.
Get 10% Discount
If you want to test the indicator on real data, use Forex Tester Online.
🔗 Forex Tester Online: https://bit.ly/fto-discount-10
🎟️ Discount Code (-10%): REEL10
Final Thoughts
You now have:
-
A fully coded MA crossover indicator
-
Clean visual signals
-
Proper validation using professional tools
This indicator is not a guess.
It is observed, tested, and analyzed.










No comments:
Post a Comment
Share your thoughts ...