TrendEA.mq5 + Trend.mqh Explained: Line-by-Line Walkthrough (MT5) Part 2

Published: 2026/02/11 Updated: 2026/02/18 Permalink

This is Part 2 of the walkthrough. In Part 1, we covered Trend.mqh — the strategy module that produces a clean, closed-bar BUY/SELL decision with explicit “block reasons.”

Now we move to the file that decides whether that signal is allowed to become a real trade: Engines/TrendEA.mq5.

This is where most EAs quietly break in live trading — not because the strategy is wrong, but because execution is messy: mixed symbol context, missing indicator history, spread spikes, stop-level rejections, rapid re-entry, and risk controls that aren’t centralized.

Your engine is built the right way: it treats the strategy output as a proposal, then applies reality:

  • Resolve symbol once (avoid mixed context)
  • Create indicator handles with retry / preload
  • Run only on new closed bars
  • Apply safety gates (session/spread/cooldown/daily loss/loss streak/max trades)
  • Convert volatility (ATR points) into execution policy (SL/TP, sizing)
  • Manage positions separately (time-stop, trailing)

Let’s walk through TrendEA.mq5 block by block, and focus on why each piece matters in production.


2) TrendEA.mq5 (Engine) Walkthrough

Now the engine file. This is where production failure usually happens if you’re not careful. Your engine is doing the right things:

  • Resolve symbol once (avoid mixed context)
  • Create indicator handles with retry
  • Run only on new closed bars
  • Block trades via gates (session/spread/cooldown/loss)
  • Manage positions separately (time-stop, trailing)

2.1 Includes + global state

  • Lines 1–26: Header comment explains the contract and architecture.
  • Line 27: strict mode.
  • Line 29: MT5 standard trade executor (CTrade).
  • Line 32: your umbrella helper include. This is where things like Risk_*, SpreadOK, PositionExists, tracking helpers, etc. live.
  • Line 35: strategy include (Trend.mqh).
  • Line 38: input schema include (your Excel-aligned preset system).
  • Line 41: instantiate CTrade trade;.
  • Lines 46–70: global runtime state:
    • TrendIndicatorPack g_ind; stores indicator handles (created in OnInit).
    • g_symbol is the resolved traded symbol.
    • g_lastClosedBarTime enforces “once per bar” evaluation.
    • DailyRiskState g_risk, loss streak fields, deal IDs, and diagnostics counters.

2.2 PlaceTrade(): turning distances into a real order

This is the “execution policy” function. The strategy says “buy/sell + ATR points”. The engine decides how big, where SL/TP goes, and whether broker constraints allow it.

  • Line 88: function signature: symbol, direction, SL/TP distances in points.
  • Lines 90–93: defensive guard. Never allow non-positive distances to reach execution.
  • Lines 94–113: position sizing:
    • Line 99: calls Risk_CalcTradeVolume() with SL distance and risk settings.
    • Lines 108–113: if volume is invalid, increment diagnostic counter and stop.
    This is where many EAs quietly fail in production: if sizing returns 0 (tiny balance, incorrect symbol settings, min lot constraints), they keep “signaling” but never trade. Your code makes this visible via g_diag.block_orderfail.
  • Lines 115–122: read current ask/bid and validate.
  • Line 124: compute entry price depending on buy/sell.
  • Lines 126–146: convert points to price:
    • Line 127: fetch symbol point size.
    • Lines 137–146: compute SL/TP around entry based on direction.
    This keeps everything consistent: your EA inputs and strategy outputs use points, and only execution turns them into price.
  • Lines 148–154: broker constraint enforcement:
    • EnsureStopsLevel() likely handles stops level and freeze level logic. If it cannot adjust or validate stops, the EA blocks trading and records block_stops.
  • Lines 156–166: configure CTrade and send order:
    • Magic number and deviation (slippage tolerance) are set.
    • Order comment includes EA name + version + direction (good for forensics).
  • Lines 168–179: bookkeeping:
    • If ok: update daily risk state and increment trades.
    • If fail: count block_orderfail.

2.3 OnInit(): freeze the context and create handles

  • Line 193: resolve symbol once using ResolveEngineSymbol(). This prevents accidental cross-symbol logic where the EA is attached to one chart but trades another without clarity.
  • Line 196: show chart label with identity (helps during multi-chart operation).
  • Lines 199–203: warnings if the user attached the EA to a different symbol or timeframe than the preset expects.
  • Lines 206–217: indicator handle creation:
    • Reset pack
    • Create with retry + preload (inside helper)
    • Fail init if handles aren’t ready
    This is a production move: you avoid running the EA “half initialized”.
  • Line 220: init risk state.
  • Lines 223–226: seed last closed bar time to avoid “startup burst” trades.
  • Lines 227–229: configure CTrade once.

2.4 OnDeinit(): release resources and print diagnostics

  • Line 244: print daily summary snapshot (even if partial day).
  • Line 247: release indicator handles via helper.

2.5 OnTradeTransaction(): keep streak tracking honest

In MT5, trade results and deal closure info are most reliable via transactions. You centralize streak updates here via Track_OnTradeTransaction(). That prevents subtle bugs where different engines track loss streak differently.

  • Lines 263–281: pass tracking enable flag, EA identity, magic, deal IDs, streak counters, timeframe, and symbol.

2.6 OnTick(): the production engine loop

This is the real orchestration. The structure is clean: manage open position first, then gates, then once-per-bar signal, then execute.

  1. Lines 296–304: daily roll + resets
    • DailyRollIfNeeded() prints prior day summary once per day.
    • DailyResetIfNewDay() resets risk state and optionally loss streak.
  2. Lines 309–339: if position exists, manage only
    • Time stop via Position_CheckMaxHoldExit()
    • Trailing via Position_ManageAtrTrailing() using ATR handle
    • Then return. You explicitly avoid “close then open same tick”.
  3. Lines 346–386: safety gates when flat
    • Session gate by offset hours
    • Spread gate
    • Cooldown gate
    • Daily loss limit gate
    • Loss streak gate
    • Max trades/day gate
    This is how you keep an EA alive during “wrong regime” periods. Most EAs fail because they keep trading.
  4. Lines 388–397: once per new closed bar
    • IsNewClosedBar() ensures stable, predictable evaluation.
    • Tracking hook and diagnostics counters updated.
  5. Lines 402–414: build TrendInputs from EA inputs
    • This is important: your strategy module stays decoupled from the EA “Inp” namespace.
  6. Lines 417–436: evaluate strategy
    • Use traded symbol’s point size, not chart’s _Point.
    • Call Trend_EvaluateHandles(..., shift=1, pt, inps, sig).
  7. Lines 438–446: interpret strategy return code
    • Increment the right diagnostics counter.
    • Exit early.
    This is how you get “why nothing traded” visibility without reading logs for hours.
  8. Lines 448–453: ambiguity guard
    • If both buy and sell are same boolean (both true or both false), block it.
    • This prevents “double fire” bugs from creeping into execution.
  9. Lines 461–468: engine-level SL/TP policy
    • Compute SL distance = ATR points × InpSlAtrMult
    • TP distance = SL × InpTpRMultiple
    • Guard against invalid distances.
    Strategy provides ATR in points; engine decides how to convert it into risk geometry. That separation is maintainability.
  10. Lines 473–474: execute
    • Call PlaceTrade() with direction.

TrendEA.mq5 full listing (numbered)

001: //+------------------------------------------------------------------+
002: //| File: Engines/TrendEA.mq5                                        |
003: //| Type: Engine (MT5 events + execution)                            |
004: //| Ver : 0.5.1                                                      |
005: //|                                                                  |
006: //| Trend EA                                                         |
007: //|                                                                  |
008: //| Contract                                                         |
009: //| - This .mq5 contains ONLY MT5 event handlers + PlaceTrade()      |
010: //| - All utilities come from KurosawaHelpers.mqh                    |
011: //| - Strategy logic lives in Strategies/Trend.mqh                   |
012: //| - All distances are POINTS (not pips)                            |
013: //|                                                                  |
014: //| Why this file exists                                             |
015: //| - GitHub users: this is the "orchestrator" only.                 |
016: //|   It wires together:                                             |
017: //|     (1) Session/risk/spread/cooldown gates                       |
018: //|     (2) Strategy signal evaluation (closed-bar, shift=1)         |
019: //|     (3) Execution (position sizing + SL/TP + order send)         |
020: //|     (4) Position management (time-stop / trailing)               |
021: //|                                                                  |
022: //| Design rules                                                     |
023: //| - Closed-bar signals: decide on bar close to reduce noise        |
024: //| - Strategy is pure: no order sending, only returns signals       |
025: //| - Engine owns safety: risk limits and execution protections      |
026: //+------------------------------------------------------------------+
027: #property strict
028:
029: #include <Trade/Trade.mqh>
030:
031: // Umbrella include (Risk/Time/Trade/Signal/Tracking helpers)
032: #include "../Helpers/KurosawaHelpers.mqh"
033:
034: // Strategy module (signal generation only)
035: #include "../Strategies/Trend.mqh"
036:
037: // Inputs (Excel-aligned schema for presets)
038: #include "../Inputs/Trend_Inputs.mqh"
039:
040: // Trade executor (MT5 standard)
041: CTrade trade;
042:
043: // ------------------------------------------------------------------
044: // Indicator handle pack for this engine (created in OnInit, released in OnDeinit)
045: // ------------------------------------------------------------------
046: TrendIndicatorPack g_ind;
047:
048: // ------------------------------------------------------------------
049: // Runtime state
050: // ------------------------------------------------------------------
051:
052: // Symbol this EA will trade (resolved once in OnInit and reused everywhere)
053: string g_symbol = "";
054:
055: // Tracks the last processed CLOSED bar time (so we evaluate once per bar)
056: datetime g_lastClosedBarTime = 0;
057:
058: // Daily risk state (trades today, daily P/L snapshot, cooldown tracking, etc.)
059: DailyRiskState g_risk;
060:
061: // Loss streak tracking comes from trade transaction handler
062: int      g_consecLosses  = 0;
063: datetime g_lastCloseTime = 0;
064:
065: // Tracking: last deal IDs to avoid duplicate processing
066: ulong g_lastOpenDealId  = 0;
067: ulong g_lastCloseDealId = 0;
068:
069: // Diagnostics counters (why trades were blocked, how many bars/signals/trades)
070: DailyDiag g_diag;
071:
072: //+------------------------------------------------------------------+
073: //| PlaceTrade                                                       |
074: //|                                                                  |
075: //| What it does                                                     |
076: //| - Converts a signal (BUY/SELL + SL/TP in POINTS) into an order.  |
077: //| - Calculates trade volume (risk-based sizing or fixed-lot).      |
078: //| - Builds price-based SL/TP from point distances.                 |
079: //| - Validates broker stop constraints (stops level / freeze level).|
080: //| - Sends a market order with a readable comment for logs.         |
081: //|                                                                  |
082: //| Inputs                                                           |
083: //| - sym   : traded symbol                                          |
084: //| - isBuy : true=BUY, false=SELL                                   |
085: //| - slPts : stop distance in POINTS                                |
086: //| - tpPts : take profit distance in POINTS                         |
087: //+------------------------------------------------------------------+
088: bool PlaceTrade(const string sym, const bool isBuy, const double slPts, const double tpPts)
089: {
090:    // Defensive: strategy or math must never send non-positive distances
091:    if(slPts <= 0.0 || tpPts <= 0.0)
092:       return false;
093:
094:    // 1) Position sizing
095:    // Risk_CalcTradeVolume handles:
096:    // - Risk % sizing when enabled
097:    // - Fixed lot fallback
098:    // - Max lot cap enforcement
099:    const double vol = Risk_CalcTradeVolume(
100:       sym,
101:       slPts,
102:       InpUseRiskSizing,
103:       InpRiskPercent,
104:       InpFixedLot,
105:       InpMaxLotCap
106:    );
107:
108:    if(vol <= 0.0)
109:    {
110:       // Treat as order fail because it blocks execution (e.g., tiny balance, invalid settings)
111:       g_diag.block_orderfail++;
112:       return false;
113:    }
114:
115:    // 2) Get current prices for entry calculation
116:    const double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
117:    const double bid = SymbolInfoDouble(sym, SYMBOL_BID);
118:    if(ask <= 0.0 || bid <= 0.0)
119:    {
120:       g_diag.block_indfail++;
121:       return false;
122:    }
123:
124:    const double entry = isBuy ? ask : bid;
125:
126:    // 3) Convert points -> price
127:    const double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
128:    if(pt <= 0.0)
129:    {
130:       g_diag.block_indfail++;
131:       return false;
132:    }
133:
134:    double sl = 0.0;
135:    double tp = 0.0;
136:
137:    if(isBuy)
138:    {
139:       sl = entry - slPts * pt;
140:       tp = entry + tpPts * pt;
141:    }
142:    else
143:    {
144:       sl = entry + slPts * pt;
145:       tp = entry - tpPts * pt;
146:    }
147:
148:    // 4) Validate SL/TP vs broker constraints (stops level/freeze level)
149:    // EnsureStopsLevel may adjust SL/TP slightly if needed; returns false if impossible.
150:    if(!EnsureStopsLevel(sym, entry, sl, tp, isBuy, true))
151:    {
152:       g_diag.block_stops++;
153:       return false;
154:    }
155:
156:    // 5) Configure executor (magic + slippage/deviation)
157:    trade.SetExpertMagicNumber((int)InpMagic);
158:    trade.SetDeviationInPoints((int)InpDeviationPoints);
159:
160:    // 6) Send order with a readable comment to help users debug
161:    const string base = InpEaName + "|" + InpEaVersion;
162:    bool ok = false;
163:
164:    if(isBuy) ok = trade.Buy(vol, sym, 0.0, sl, tp, base + "|BUY");
165:    else      ok = trade.Sell(vol, sym, 0.0, sl, tp, base + "|SELL");
166:
167:    // 7) Post-send bookkeeping
168:    if(ok)
169:    {
170:       // Update daily risk state (cooldown + trades_today)
171:       Risk_OnTradePlaced(g_risk, TimeCurrent());
172:       g_diag.trades++;
173:    }
174:    else
175:    {
176:       g_diag.block_orderfail++;
177:    }
178:
179:    return ok;
180: }
181:
182: //+------------------------------------------------------------------+
183: //| OnInit                                                           |
184: //|                                                                  |
185: //| What happens here                                                |
186: //| - Resolve the traded symbol once (InpTargetPair or chart symbol) |
187: //| - Create indicator handles for that symbol/timeframe             |
188: //| - Initialize risk state and bar-tracking state                   |
189: //+------------------------------------------------------------------+
190: int OnInit()
191: {
192:    // Resolve once and cache. This is the EA's immutable execution context.
193:    g_symbol = ResolveEngineSymbol(InpTargetPair, _Symbol);
194:
195:    // Visual label on chart (helps users identify EA + magic + version)
196:    ShowEaLabel(InpEaName, InpEaId, (int)InpMagic, _Symbol, (ENUM_TIMEFRAMES)_Period);
197:
198:    // Friendly warnings (preset expects a target, but user attached elsewhere)
199:    if(StringLen(InpTargetPair) > 0 && _Symbol != InpTargetPair)
200:       Print("Warning: Intended symbol=", InpTargetPair, ", attached chart symbol=", _Symbol);
201:
202:    if(_Period != InpTargetTf)
203:       Print("Warning: Intended TF=", EnumToString(InpTargetTf), ", current chart TF=", EnumToString(_Period));
204:
205:    // Create and validate indicator handles (retry + history preload inside helper)
206:    TrendIndicators_Reset(g_ind);
207:
208:    if(!TrendIndicators_CreateWithRetry(
209:          g_symbol, InpTargetTf,
210:          InpEmaFast, InpEmaSlow,
211:          InpRsiPeriod, InpAtrPeriod,
212:          InpUseAdxFilter, InpAdxPeriod,
213:          g_ind))
214:    {
215:       Print("INIT_FAILED: indicator handles not ready. sym=", g_symbol, " tf=", EnumToString(InpTargetTf));
216:       return INIT_FAILED;
217:    }
218:
219:    // Initialize daily risk state (resets internal counters / daily snapshot)
220:    Risk_Init(g_risk, TimeCurrent());
221:
222:    // Set last closed bar time so we do not "burst" trade on start
223:    g_lastClosedBarTime = (datetime)iTime(g_symbol, InpTargetTf, 1);
224:    if(g_lastClosedBarTime <= 0)
225:       g_lastClosedBarTime = TimeCurrent();
226:
227:    // Configure CTrade once
228:    trade.SetExpertMagicNumber((int)InpMagic);
229:    trade.SetDeviationInPoints((int)InpDeviationPoints);
230:
231:    return INIT_SUCCEEDED;
232: }
233:
234: //+------------------------------------------------------------------+
235: //| OnDeinit                                                         |
236: //|                                                                  |
237: //| What happens here                                                |
238: //| - Print daily diagnostics snapshot (even if day is incomplete)   |
239: //| - Release indicator handles                                      |
240: //+------------------------------------------------------------------+
241: void OnDeinit(const int reason)
242: {
243:    // Print the current (partial) day snapshot before exit
244:    PrintDailySummary(InpEaName, g_symbol, InpTargetTf, g_diag);
245:
246:    // Always release indicator handles
247:    TrendIndicators_Release(g_ind);
248: }
249:
250: //+------------------------------------------------------------------+
251: //| OnTradeTransaction                                               |
252: //|                                                                  |
253: //| Why this matters                                                 |
254: //| - In MT5, the most reliable way to update streak/PnL-related     |
255: //|   state is from trade transactions (deals closing, etc.).        |
256: //| - We centralize streak updates here so the logic is consistent   |
257: //|   across all engines.                                            |
258: //+------------------------------------------------------------------+
259: void OnTradeTransaction(const MqlTradeTransaction &trans,
260:                         const MqlTradeRequest &request,
261:                         const MqlTradeResult &result)
262: {
263:    Track_OnTradeTransaction(
264:       InpTrackEnable,
265:       trans,
266:
267:       InpEaId,
268:       InpEaName,
269:       InpEaVersion,
270:
271:       (int)InpMagic,
272:       InpTrackSendOpen,
273:
274:       g_lastOpenDealId,
275:       g_lastCloseDealId,
276:       g_consecLosses,
277:       g_lastCloseTime,
278:
279:       InpTargetTf,
280:       g_symbol
281:    );
282: }
283:
284: //+------------------------------------------------------------------+
285: //| OnTick                                                           |
286: //|                                                                  |
287: //| Engine flow (high level)                                         |
288: //| 1) Daily roll/reset bookkeeping                                  |
289: //| 2) If position exists: manage it (time-stop / trailing)          |
290: //| 3) If flat: apply safety gates (session/spread/cooldown/loss)    |
291: //| 4) Once per closed bar: evaluate strategy                        |
292: //| 5) If signal: compute SL/TP distances and place trade            |
293: //+------------------------------------------------------------------+
294: void OnTick()
295: {
296:    const string   sym = g_symbol;
297:    const datetime now = TimeCurrent();
298:
299:    // A) Daily reporting roll (prints prior day summary once per day)
300:    DailyRollIfNeeded(InpEaName, sym, InpTargetTf, g_diag, NowYmdJst());
301:
302:    // B) Risk reset (optionally resets streak on a new day)
303:    DailyResetIfNewDay(g_risk, now, g_consecLosses, InpResetConsecLossDaily);
304:
305:    // ----------------------------------------------------------------
306:    // 1) If we already have a position, do position management only.
307:    //    This EA is designed for "one position per magic per symbol".
308:    // ----------------------------------------------------------------
309:    if(PositionExists(sym, (int)InpMagic))
310:    {
311:       // Time-stop: close if held too long
312:       if(InpMaxHoldMinutes > 0)
313:       {
314:          const bool closed = Position_CheckMaxHoldExit(
315:             trade, sym, (long)InpMagic, InpMaxHoldMinutes, now
316:          );
317:
318:          if(closed)
319:             return; // position is gone, do not open a new trade on same tick
320:       }
321:
322:       // Trailing: ATR-step trailing after profit reaches TrailStartR
323:       if(InpUseTrailing)
324:       {
325:          Position_ManageAtrTrailing(
326:             trade,
327:             sym,
328:             (long)InpMagic,
329:             g_ind.hATR,
330:             InpTrailStartR,
331:             InpTrailStepAtrMult,
332:             g_diag.block_indfail,
333:             g_diag.block_stops,
334:             g_diag.block_orderfail
335:          );
336:       }
337:
338:       return;
339:    }
340:
341:    // ----------------------------------------------------------------
342:    // 2) Safety gates (only when flat)
343:    //    These prevent trading during bad conditions.
344:    // ----------------------------------------------------------------
345:
346:    // Session window gate
347:    if(!IsTimeWindowByOffsetHours(InpStartHour, InpEndHour, InpUtcOffset))
348:    {
349:       g_diag.block_session++;
350:       return;
351:    }
352:
353:    // Spread gate (avoid wide spread conditions)
354:    if(!SpreadOK(sym, InpMaxSpreadPoints))
355:    {
356:       g_diag.block_spread++;
357:       return;
358:    }
359:
360:    // Cooldown gate (avoid rapid-fire re-entry)
361:    if(!CooldownOK(g_risk, InpCooldownMinutes, now))
362:    {
363:       g_diag.block_cooldown++;
364:       return;
365:    }
366:
367:    // Daily loss limit gate (capital protection)
368:    if(!DailyLossLimitOK(g_risk, InpDailyLossLimitPercent))
369:    {
370:       g_diag.block_maxday++;
371:       return;
372:    }
373:
374:    // Loss streak gate (avoid continuing in a bad regime)
375:    if(!LossStreakOK(g_consecLosses, InpMaxConsecLosses))
376:    {
377:       g_diag.block_loss++;
378:       return;
379:    }
380:
381:    // Max trades per day gate
382:    if(InpMaxTradesPerDay > 0 && g_risk.trades_today >= InpMaxTradesPerDay)
383:    {
384:       g_diag.block_maxtrades++;
385:       return;
386:    }
387:
388:    // ----------------------------------------------------------------
389:    // 3) Only run strategy once per NEW CLOSED bar.
390:    //    This avoids noisy intrabar signals and makes backtests stable.
391:    // ----------------------------------------------------------------
392:    if(!IsNewClosedBar(sym, InpTargetTf, g_lastClosedBarTime))
393:       return;
394:
395:    Track_OnNewBar(InpTrackEnable, (int)InpMagic, sym, InpTargetTf);
396:    g_diag.bars++;
397:
398:    // ----------------------------------------------------------------
399:    // 4) Build strategy inputs from EA inputs.
400:    //    Strategy stays pure: it only consumes handles + inputs.
401:    // ----------------------------------------------------------------
402:    TrendInputs inps;
403:    inps.rsi_buy_below     = InpRsiBuyBelow;
404:    inps.rsi_sell_above    = InpRsiSellAbove;
405:
406:    inps.atr_min_points    = InpAtrMinPoints;
407:    inps.atr_max_points    = InpAtrMaxPoints;
408:
409:    inps.use_adx_filter    = InpUseAdxFilter;
410:    inps.adx_min_to_trade  = InpMinAdxToTrade;
411:    inps.adx_max_to_trade  = InpMaxAdxToTrade;
412:
413:    inps.require_ema_slope = InpRequireEmaSlope;
414:
415:    TrendSignal sig;
416:
417:    // Important: use the traded symbol point size, not _Point (chart symbol may differ)
418:    const double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
419:    if(pt <= 0.0)
420:    {
421:       g_diag.block_indfail++;
422:       return;
423:    }
424:
425:    // Evaluate signal using indicator handles on CLOSED bar (shift=1)
426:    const TrendResult sres = Trend_EvaluateHandles(
427:       g_ind.hEmaFast,
428:       g_ind.hEmaSlow,
429:       g_ind.hRSI,
430:       g_ind.hATR,
431:       g_ind.hADX,
432:       1,   // shift=1 -> last closed bar
433:       pt,  // point size for the traded symbol
434:       inps,
435:       sig
436:    );
437:
438:    // Strategy can return “blocked by filters” vs “no signal” vs “indicator error”
439:    if(sres != TREND_OK)
440:    {
441:       if(sres == TREND_BLOCK_ATR)             g_diag.block_atr++;
442:       else if(sres == TREND_BLOCK_ADX)        g_diag.block_adx++;
443:       else if(sres == TREND_BLOCK_NO_SIGNAL)  g_diag.block_nosignal++;
444:       else                                    g_diag.block_indfail++;
445:       return;
446:    }
447:
448:    // Defensive: should never be both true or both false at TREND_OK
449:    if(sig.buy == sig.sell)
450:    {
451:       g_diag.block_ambig++;
452:       return;
453:    }
454:
455:    g_diag.signals++;
456:
457:    // ----------------------------------------------------------------
458:    // 5) Convert ATR signal into SL/TP distances in POINTS.
459:    //    We keep SL/TP distance logic in engine because it is execution policy.
460:    // ----------------------------------------------------------------
461:    const double slPts = sig.atr_points * InpSlAtrMult;
462:    const double tpPts = slPts * InpTpRMultiple;
463:
464:    if(slPts <= 0.0 || tpPts <= 0.0)
465:    {
466:       g_diag.block_stops++;
467:       return;
468:    }
469:
470:    // ----------------------------------------------------------------
471:    // 6) Execute
472:    // ----------------------------------------------------------------
473:    if(sig.buy)  PlaceTrade(sym, true,  slPts, tpPts);
474:    else         PlaceTrade(sym, false, slPts, tpPts);
475: }

3) Architecture Notes: Why This Pattern Survives Longer

  • Closed-bar evaluation reduces “signal wobble”. Most production EAs die from subtle differences between backtest/live. Shift=1 helps keep them aligned.
  • Filters are explicit and counted. Your TrendResult return codes and DailyDiag counters are what turns “why no trades” into an answer.
  • Execution is policy, not strategy. ATR becomes SL in the engine, not in the strategy. That keeps your strategy reusable across different risk styles.
  • Production gates are centralized. Session/spread/cooldown/daily loss/streak limits are in one place, not scattered across signal conditions.
If this helped your EA work, share it.
X Facebook LinkedIn

Keisuke Kurosawa
Hello
Share
https://1kpips.com/en/blog/trend-ea-line-by-line-walkthrough-2
Categories
EA Development & Programming
Tags
MQL5 Code Walkthrough,Trend EA, MT5 EA Architecture,EA Execution Engine,Maintainable EA

Related Articles

Next step
Save this idea into your EA: add a session filter, then backtest with and without it to see the difference.