MQL5 Coding Best Practices: Build Cleaner, Safer EAs
Reliable MQL5 development separates data acquisition, signal decisions, risk checks and execution state. Formatting helps review, but it cannot compensate for comparing indicator handles instead of values or treating an accepted request as a completed trade. The first task is to make the EA's behavior observable and testable.

Understand Events and Reinitialization
OnInit initializes the program and validates inputs. It can run again after reinitialization, not only on the first chart attachment. OnDeinit releases resources and records why the program stopped. OnTimer handles a configured timer, while OnTick handles NewTick events for the chart symbol.
Events are processed sequentially. If a NewTick event is already queued or executing, another is not added to that EA's queue. Therefore, OnTick is not a guarantee that your handler observes every market tick. Disabling AutoTrading does not stop the EA's calculations; it prevents trading requests. See the official OnTick reference.
A once-per-bar entry policy needs explicit state. Position management may still need more frequent evaluation, so do not put every activity behind the same new-bar gate without considering its purpose.
A Complete Closed-Bar Observation Example
This EA logs a bullish EMA crossover. It does not place orders. It demonstrates handle creation, sufficient-history checks, copied-value validation and a per-bar observation gate. The final source passed the MQL5 MetaEditor syntax checker; no trading-performance or runtime test is claimed.
#property strict
input int FastPeriod = 10;
input int SlowPeriod = 50;
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
datetime lastProcessedBar = 0;
int OnInit() {
if(FastPeriod < 1 || SlowPeriod <= FastPeriod) {
return INIT_PARAMETERS_INCORRECT;
}
fastHandle = iMA(_Symbol, _Period, FastPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, SlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE) {
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason) {
if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
}
void OnTick() {
datetime currentBar = iTime(_Symbol, _Period, 0);
if(currentBar == 0 || currentBar == lastProcessedBar) return;
if(BarsCalculated(fastHandle) < SlowPeriod + 2 ||
BarsCalculated(slowHandle) < SlowPeriod + 2) return;
double fastValues[2], slowValues[2];
if(CopyBuffer(fastHandle, 0, 1, 2, fastValues) != 2 ||
CopyBuffer(slowHandle, 0, 1, 2, slowValues) != 2) return;
// Static arrays: [0] is the older bar 2; [1] is closed bar 1.
for(int i = 0; i < 2; i++) {
if(fastValues[i] == EMPTY_VALUE || slowValues[i] == EMPTY_VALUE ||
!MathIsValidNumber(fastValues[i]) ||
!MathIsValidNumber(slowValues[i])) return;
}
lastProcessedBar = currentBar;
bool crossedUp = fastValues[0] <= slowValues[0] &&
fastValues[1] > slowValues[1];
if(crossedUp) {
Print("Closed-bar bullish EMA crossover observed at ",
TimeToString(currentBar));
}
}
MQL5 iMA returns an indicator handle, not the current moving-average price. The example obtains values through CopyBuffer. In these static arrays, index 0 contains the older of the two copied values: bar 2. Index 1 contains completed bar 1. This ordering follows the CopyBuffer reference.
The gate is updated only after successful reads, allowing another tick to retry unavailable data. On first attachment or restart, the example can inspect the most recent completed pair again. It does not persist a trading decision across restarts; an order-placing EA needs additional state reconciliation and duplicate prevention.
Keep Execution Separate from Signal Detection
Before submitting a trade, validate the symbol's volume step and limits, tick size, allowed order types, stops/freeze levels, account permissions and supported filling mode. A free-margin balance alone does not validate a specific request.
Use OrderCheck to inspect the proposed request, then examine the submission response and actual account state. A successful check does not guarantee execution. Likewise, OrderSend returning true does not mean that the requested deal has filled.
Handle rejection, partial outcomes and uncertain responses explicitly. Blind retries can create duplicate exposure. Track orders, deals and positions according to the account's netting or hedging mode, and ensure the EA manages only its intended activity.
Use Units and State in Names
Names such as stopDistancePoints, stopPrice and riskAmount distinguish quantities that must not be mixed. A pip is not universally one point, and rounding to decimal digits alone does not necessarily align a price with the instrument's tick size.
Choose a consistent brace and naming style; camelCase or another readable convention is a project choice, not an MQL5 language requirement. Both MQL4 and MQL5 support object-oriented programming. Split signal, risk and execution responsibilities into focused functions or modules when that makes their contracts easier to inspect.
Test Failure Paths as Well as Signals
| Scenario | Expected evidence |
|---|---|
| Insufficient history or failed buffer copy | No decision from invalid values; diagnostic or retry policy. |
| Multiple ticks in one entry bar | No unintended repeated entry request. |
| Terminal restart with an open position | Existing exposure is reconciled before new action. |
| Broker rejects volume or stop | Error is recorded; no uncontrolled retry loop. |
| Timeframe or input change | Reinitialization creates the intended handles and state. |
Use MetaEditor's debugger for step-by-step inspection and logs for timestamps, inputs, request identifiers and response codes. Avoid flooding the event handler with unnecessary output, and never log credentials.
Begin with a short visual Strategy Tester run, then expand data coverage and cost assumptions. Preserve a later period for evaluation before tuning. Demo observation checks operational behavior, but no fixed two-week or four-week duration proves readiness.
Deployment Is an Operational Process
A VPS can improve availability, but it does not fix faulty code or guarantee a particular latency. Measure the actual connection and monitor disconnects, repeated rejections and unexpected exposure. Define a restart procedure, a version rollback path and a way to disable new entries while reviewing existing positions.
Pineify can help draft or review MQL5 code and use compiler diagnostics during revision. Review its output against the same data, state and execution contracts. No zero-error generation guarantee or comparison benchmark is asserted here.

