Functions & Event Handlers

OnInit, OnCalculate, OnTick — the event-driven architecture that powers MQL5 programs.

Event-Driven Architecture

MQL5 programs are event-driven. Instead of running from top to bottom like a simple script, indicators and EAs respond to events — a new tick arrives, a new bar forms, the user changes settings, the chart timeframe changes, etc.

You define event handler functions that MT5 calls automatically when each event occurs. This is the core pattern of all MQL5 programming.

Event Handlers by Program Type

Scripts have one event handler:

void OnStart()  // Called once when the script is attached to a chart

Indicators have these key handlers:

int OnInit()                          // Called once when indicator is loaded
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[])  // Called on every new tick
void OnDeinit(const int reason)       // Called when indicator is removed

Expert Advisors have these key handlers:

int OnInit()                    // Called once when EA is loaded
void OnTick()                   // Called on every new tick (price change)
void OnDeinit(const int reason) // Called when EA is removed
void OnTrade()                  // Called when a trade event occurs
void OnTimer()                  // Called on timer events

OnInit — Initialization

OnInit() runs once when your program is first attached to a chart, or when the chart timeframe changes, or when input parameters are modified. Use it to:

int OnInit()
{
    // Validate input parameters
    if(MAPeriod < 1)
    {
        Print("Error: MA Period must be >= 1");
        return INIT_PARAMETERS_INCORRECT;
    }

    // Create indicator handles
    maHandle = iMA(_Symbol, PERIOD_CURRENT, MAPeriod, 0, MODE_SMA, PRICE_CLOSE);
    if(maHandle == INVALID_HANDLE)
    {
        Print("Error creating MA indicator");
        return INIT_FAILED;
    }

    // Set indicator properties (for custom indicators)
    SetIndexBuffer(0, mainBuffer, INDICATOR_DATA);
    PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_LINE);
    PlotIndexSetInteger(0, PLOT_LINE_COLOR, clrDodgerBlue);

    return INIT_SUCCEEDED;  // must return this on success
}

OnCalculate — The Indicator Engine

This is the heart of every custom indicator. It runs on every new tick and receives the full price data arrays:

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[])
{
    // rates_total    = total number of bars on the chart
    // prev_calculated = bars already processed (0 on first call)

    // Only process new bars (efficiency optimization)
    int start = (prev_calculated == 0) ? MAPeriod : prev_calculated - 1;

    for(int i = start; i < rates_total; i++)
    {
        // Calculate your indicator value for bar i
        double sum = 0;
        for(int j = 0; j < MAPeriod; j++)
            sum += close[i - j];

        mainBuffer[i] = sum / MAPeriod;
    }

    return rates_total;  // tell MT5 how many bars we processed
}
💡
Performance matters in OnCalculate

This function runs on every tick — potentially dozens of times per second. Only recalculate bars that are new or changed. The prev_calculated parameter tells you where you left off, so you can skip already-processed bars.

OnTick — The EA Engine

OnTick() fires every time a new price quote arrives for the chart symbol. This is where your EA logic lives:

void OnTick()
{
    // Get current prices
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

    // Read indicator values
    double maValues[];
    CopyBuffer(maHandle, 0, 0, 3, maValues);

    // Check for trade signals
    if(bid > maValues[0] && !HasOpenPosition())
    {
        // Price crossed above MA — open buy
        OpenBuy(ask);
    }
    else if(bid < maValues[0] && HasOpenPosition())
    {
        // Price crossed below MA — close position
        CloseAllPositions();
    }
}

OnDeinit — Cleanup

Called when your program is removed from the chart. Use it to release resources:

void OnDeinit(const int reason)
{
    // Release indicator handles
    IndicatorRelease(maHandle);

    // Remove chart objects you created
    ObjectsDeleteAll(0, "MyIndicator_");

    // Log the reason for removal
    Print("Removed. Reason: ", reason);
}

Writing Your Own Functions

Break your code into reusable functions:

// Function with return value
double CalculateLotSize(double riskPercent, double stopLossPips)
{
    double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
    double riskAmount = accountBalance * riskPercent / 100.0;
    double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double lotSize = riskAmount / (stopLossPips * tickValue);
    return NormalizeDouble(lotSize, 2);
}

// Function with no return value
void LogTradeInfo(string action, double price, double lots)
{
    Print(action, " | Price: ", DoubleToString(price, _Digits),
          " | Lots: ", DoubleToString(lots, 2),
          " | Time: ", TimeToString(TimeCurrent()));
}

// Usage
void OnTick()
{
    double lots = CalculateLotSize(1.0, 50);
    LogTradeInfo("BUY", SymbolInfoDouble(_Symbol, SYMBOL_ASK), lots);
}

Pass by Reference

Use the & operator to pass variables by reference — the function can modify the original variable:

bool GetPriceData(double &high, double &low, double &close, int shift)
{
    high  = iHigh(_Symbol, PERIOD_CURRENT, shift);
    low   = iLow(_Symbol, PERIOD_CURRENT, shift);
    close = iClose(_Symbol, PERIOD_CURRENT, shift);
    return (high != 0 && low != 0 && close != 0);
}

// Usage — variables are filled by the function
double h, l, c;
if(GetPriceData(h, l, c, 1))
    Print("Yesterday: H=", h, " L=", l, " C=", c);