MQL4 Expert Advisors operate on an event-driven architecture rather than traditional linear execution. Understanding the precise lifecycle of each event function is fundamental to building robust, production-grade trading systems. Poor event handling leads to memory leaks, redundant calculations, and missed trading opportunities.
1. OnInit(): The Single-Execution Gateway
The
OnInit() function executes exactly once when an EA attaches to a chart or when parameters change. Its return value determines whether the EA runs at all.``
cpp
// Complete OnInit implementation with validation
int OnInit() {
Print("EA initializing on symbol: ", Symbol(), " period: ", Period());
// Validate critical inputs
if(AccountBalance() <= 0) {
Print("ERROR: Invalid account balance");
return INIT_FAILED;
}
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
if(LotSize < minLot || LotSize > maxLot) {
Print("Lot size out of range. Min: ", minLot, " Max: ", maxLot);
return INIT_PARAMETERS_INCORRECT;
}
// Initialize persistent objects
if(!ObjectCreate(0, "StatusLabel", OBJ_LABEL, 0, 0, 0)) {
Print("Failed to create status label");
return INIT_FAILED;
}
ObjectSetInteger(0, "StatusLabel", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, "StatusLabel", OBJPROP_YDISTANCE, 30);
// Start timer for periodic tasks (60 seconds)
if(!EventSetTimer(60)) {
Print("Failed to set timer");
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
`
2. OnTick(): The Heartbeat of Your EA
OnTick() fires on every price update from the broker. In active markets, this can exceed 500 calls per minute. Poorly optimized OnTick() logic kills backtest performance and causes execution lag.
`cpp
// Optimized OnTick with state management
datetime lastBarTime = 0;
datetime lastExecutionTime = 0;
int tickCounter = 0;
void OnTick() {
tickCounter++;
// Rate limiting: skip if last execution less than 100ms ago
datetime now = GetTickCount();
if(now - lastExecutionTime < 100) return;
lastExecutionTime = now;
// Detect new bar (critical for avoiding redundant signals)
static datetime s_lastBarTime = 0;
if(Time[0] == s_lastBarTime) return; // Same bar, skip strategy evaluation
s_lastBarTime = Time[0];
// New bar confirmed - execute strategy logic
EvaluateTradingConditions();
// Periodic status update every 1000 ticks
if(tickCounter >= 1000) {
UpdateStatusDisplay();
tickCounter = 0;
}
}
void EvaluateTradingConditions() {
// Only use closed bar data (index 1, not 0)
double maFast = iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
double maSlow = iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
if(maFast > maSlow && !PositionExists(OP_BUY)) {
OpenBuyOrder();
}
}
`
3. OnTimer(): Precision Control Without Tick Dependency
Timer events provide deterministic execution intervals, perfect for daily reports, risk checks, or trailing stop updates.
`cpp
// Timer implementation with multiple interval support
void OnTimer() {
static datetime lastDailyCheck = 0;
datetime currentTime = TimeCurrent();
// Daily report at 00:01 server time
MqlDateTime dt;
TimeToStruct(currentTime, dt);
if(dt.hour == 0 && dt.min == 1 && currentTime != lastDailyCheck) {
GenerateDailyReport();
lastDailyCheck = currentTime;
}
// Hourly risk assessment
if(dt.min == 0) {
CheckDrawdownAndAdjustRisk();
}
// Trailing stop update every 30 seconds
static datetime lastTrailUpdate = 0;
if(currentTime - lastTrailUpdate >= 30) {
UpdateTrailingStops();
lastTrailUpdate = currentTime;
}
}
void UpdateTrailingStops() {
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderMagicNumber() != MagicNumber) continue;
double newSL = 0;
if(OrderType() == OP_BUY) {
newSL = Bid - TrailingPips * Point;
if(newSL > OrderStopLoss()) {
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
}
}
}
}
`
4. OnDeinit(): Cleanup That Prevents Crashes
Proper resource cleanup prevents memory leaks and orphaned objects that accumulate across EA reloads.
`cpp
void OnDeinit(const int reason) {
// Log the unload reason for debugging
switch(reason) {
case REASON_REMOVE: Print("EA removed manually"); break;
case REASON_RECOMPILE: Print("EA unloaded for recompilation"); break;
case REASON_CHARTCHANGE: Print("Symbol or period changed"); break;
case REASON_PARAMETERS: Print("Input parameters changed"); break;
case REASON_ACCOUNT: Print("Account switched"); break;
default: Print("Unknown deinit reason: ", reason);
}
// Stop timer if active
EventStopTimer();
// Delete all objects created by this EA
ObjectsDeleteAll(0, "StatusLabel"); // Delete specific objects
ObjectsDeleteAll(0, "Signal_"); // Delete by prefix
// Close any open file handles
int fileHandle = FileOpen("log.txt", FILE_READ|FILE_WRITE);
if(fileHandle != INVALID_HANDLE) FileClose(fileHandle);
Print("EA cleanup complete. Total objects remaining: ", ObjectsTotal(0));
}
`
5. Complete Event-Driven EA Template
`cpp
//+------------------------------------------------------------------+
//| Production Event-Driven EA Template |
//+------------------------------------------------------------------+
input double LotSize = 0.1;
input int MagicNumber = 12345;
input int TrailingPips = 20;
datetime lastBarTime;
int ticketCounter;
int OnInit() {
if(LotSize <= 0) return INIT_PARAMETERS_INCORRECT;
EventSetTimer(30); // 30-second timer
lastBarTime = Time[0];
Print("EA initialized on ", Symbol());
return INIT_SUCCEEDED;
}
void OnTick() {
// New bar detection only
if(Time[0] == lastBarTime) return;
lastBarTime = Time[0];
// Bar-closed logic here
ExecuteStrategy();
}
void OnTimer() {
// Periodic tasks: trailing stops, status updates, risk checks
UpdateTrailingStops();
UpdateDashboard();
}
void OnDeinit(const int reason) {
EventStopTimer();
ObjectsDeleteAll(0);
Print("EA terminated. Reason: ", reason);
}
void ExecuteStrategy() {
double maFast = iMA(Symbol(), 0, 10, 0, MODE_SMA, PRICE_CLOSE, 1);
double maSlow = iMA(Symbol(), 0, 30, 0, MODE_SMA, PRICE_CLOSE, 1);
if(maFast > maSlow && !PositionExists()) {
OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, 0, 0, "EventEA", MagicNumber, 0, clrGreen);
}
}
bool PositionExists() {
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
if(OrderMagicNumber() == MagicNumber)
return true;
}
return false;
}
``Reference: MQL4 Documentation (docs.mql4.com), “Event Handling Functions”; Young, Andrew. “Expert Advisor Programming” (2019).