Summary: Advanced deep dive into MQL4 event-driven architecture for professional EA development. Covers OnInit-OnDeinit lifecycle, tick processing optimization, timer implementation, and resource cleanup strategies with runnable code examples.




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