Full width home advertisement

Post Page Advertisement [Top]

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:

  1. Buy arrows

  2. Sell arrows

  3. Fast MA line

  4. 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 ...

Bottom Ad [Post Page]