MT4/MT5 Event Handling: Master OnTick, OnTimer & Key

Learn when MetaTrader fires OnTick, OnTimer, OnInit, OnDeinit, OnChartEvent, and OnStart in MQL4/MQL5 with real code examples, best practices, and.

mt4-mt5-event-handling-master-ontick-ontimer-key

Introduction: Why Event Handling Defines Your EA's Reliability

Every MetaTrader Expert Advisor or custom indicator is a reactive program. It doesn't run in a loop; it waits for the platform to call a specific event handler function. Understanding these handlers—OnTick(), OnTimer(), OnChartEvent(), OnStart(), OnInit(), and OnDeinit()—is the difference between an EA that behaves predictably and one that misses signals, freezes, or crashes.

In this guide, you will learn exactly when MetaTrader calls each handler, how to write them correctly in MQL4 versus MQL5, and how to avoid the six most common event-handling bugs that plague even experienced developers. By the end, you will be able to structure your code so it reacts precisely when you want—and stays idle when it should.

Prerequisites

  • MetaTrader 4 or MetaTrader 5 (any build, but the latest is recommended).
  • MetaEditor (comes with the platform; Tools → MetaQuotes Language Editor).
  • A demo account with at least one active symbol for testing.
  • Basic familiarity with the MetaEditor interface and how to create a new Expert Advisor or script.

The Core Event Handlers: When and Why They Fire

Below is the complete set of handlers you will use. Note that MQL4 and MQL5 differ in several important ways—especially the event-driven model in MT5.

Handler MT4 MT5 When It Fires
OnInit() Yes Yes Once, when the EA/indicator is loaded onto a chart, or when inputs change.
OnDeinit() Yes Yes When the EA/indicator is removed from the chart, or the terminal closes.
OnTick() Yes Yes Every time a new price tick arrives for the chart's symbol.
OnTimer() Yes Yes At a fixed interval you set (e.g., every 5 seconds). Does not fire until EventSetTimer() is called.
OnChartEvent() Yes Yes Mouse clicks, key presses, chart object clicks, or custom events.
OnStart() Yes Yes Once, when a script is dragged onto a chart.

OnInit() and OnDeinit(): The Startup and Teardown Handlers

Every EA and custom indicator must implement OnInit(). This is where you initialize indicators, set up timers, load files, or validate input parameters. OnInit() returns an int—return INIT_SUCCEEDED to continue, or INIT_FAILED to stop the EA from running.

int OnInit()
{
   // Validate inputs
   if(MyMagicNumber <= 0)
   {
      Print("Invalid Magic Number: ", MyMagicNumber);
      return(INIT_FAILED);
   }
   // Create a timer that fires every 5 seconds
   EventSetTimer(5);
   return(INIT_SUCCEEDED);
}

OnDeinit() is your cleanup handler. Always remove any timer you set, delete chart objects, and close file handles. The reason parameter tells you why the deinitialization happened—useful for debugging.

void OnDeinit(const int reason)
{
   // Remove the timer
   EventKillTimer();
   // Delete all objects created by this EA
   ObjectsDeleteAll(0, "MyEA_");
   Print("Deinitialized. Reason code: ", reason);
}

Edge case: In MT5, if you change EA inputs while the EA is running, OnDeinit() is called first (with reason REASON_PARAMETERS), then OnInit() is called again. Ensure your cleanup code does not assume the EA is being permanently removed—leave some state if needed.

OnTick(): The Heartbeat of Your EA

For EAs, OnTick() is the main loop. It fires on every new tick. In MQL4, you must manually check if a new bar has formed if you only want to act once per candle. In MQL5, you can use the NewBar() helper pattern or the CopyRates() function to check volume changes.

// MQL4 version of a once-per-bar check
datetime lastBarTime = 0;

void OnTick()
{
   if(Time[0] != lastBarTime)
   {
      lastBarTime = Time[0];
      // New bar logic here
      CheckForSignal();
   }
}

In MQL5, the same pattern uses CopyTime():

datetime lastBarTime = 0;

void OnTick()
{
   MqlDateTime dt;
   datetime currentBarTime;
   CopyTime(_Symbol, _Period, 0, 1, currentBarTime);
   if(currentBarTime != lastBarTime)
   {
      lastBarTime = currentBarTime;
      CheckForSignal();
   }
}

Critical tip: Never put heavy calculations or Sleep() inside OnTick(). It blocks the entire platform. Use OnTimer() for periodic tasks instead.

Real-world example: A scalping EA that places orders on every tick must be extremely lightweight. Avoid any file I/O or complex mathematical operations inside OnTick(). Instead, pre-calculate indicator values in OnTimer() and store them in global variables for OnTick() to read.

OnTimer(): Scheduled Execution

Use OnTimer() when you need to run code at a fixed interval regardless of tick activity—for example, updating a dashboard, checking account equity, or polling a web service. You must start the timer in OnInit() with EventSetTimer(seconds). The interval is in seconds and can be as low as 1 second in MT5 (10 seconds minimum in MT4).

void OnTimer()
{
   // Update a display label every 30 seconds
   string label = "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2);
   ObjectSetString(0, "EquityLabel", OBJPROP_TEXT, label);
}

MT4 vs MT5 difference: In MT4, EventSetTimer() accepts only integer seconds with a minimum of 10. In MT5, you can pass a double (e.g., EventSetTimer(0.5) for half-second intervals). This makes MT5 far more suitable for high-frequency timer tasks.

OnChartEvent(): Interacting with the User

This handler lets your EA or indicator respond to mouse clicks, keyboard presses, and chart object clicks. The sparam parameter contains the object name if an object was clicked. This is how you build interactive panels or allow users to click a button to close all positions.

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // Check if a chart object was clicked
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == "CloseAllButton")
      {
         CloseAllPositions();
      }
   }
   // Check for mouse movement (MT5 only)
   if(id == CHARTEVENT_MOUSE_MOVE)
   {
      // lparam = x coordinate, dparam = y coordinate
      Comment("Mouse at: ", lparam, ", ", dparam);
   }
}

In MQL5, you must enable mouse events by setting CHART_EVENT_MOUSE_MOVE in OnInit() with ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true).

Common bug: Forgetting to check the id parameter leads to responding to the wrong event type. Always filter by CHARTEVENT_OBJECT_CLICK or CHARTEVENT_KEYDOWN before acting.

OnStart(): Scripts Only

OnStart() is used exclusively by scripts—programs that run once and then terminate. It has no parameters and no return value. Use it for one-shot tasks like closing all pending orders or exporting history to a file.

void OnStart()
{
   // Example: Delete all pending orders
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS))
      {
         if(OrderType() > 1) // pending orders
            OrderDelete(OrderTicket());
      }
   }
}

Performance note: Scripts execute synchronously. If your script takes more than a few seconds, the MetaTrader interface may become unresponsive. Use ChartRedraw() periodically to refresh the chart if you're modifying objects.

Step-by-Step: Building a Minimal EA with All Handlers

  1. Open MetaEditor (Tools → MetaQuotes Language Editor).
  2. Create a new Expert Advisor (File → New → Expert Advisor). Name it EventDemo.
  3. Implement OnInit() to set a magic number, create a timer, and return INIT_SUCCEEDED.
  4. Implement OnDeinit() to kill the timer and delete objects.
  5. Implement OnTick() with a once-per-bar check. Add a Print() statement to see when it fires.
  6. Implement OnTimer() that prints the current time every 10 seconds.
  7. Implement OnChartEvent()</

Community

Clap for the article and open comments only when you want to read them.

0 claps0 comments

Related articles