preview
Engineering a Self-Healing Expert Advisor in MQL5 (Part 3): Restart-Aware Breakeven and Trailing Systems

Engineering a Self-Healing Expert Advisor in MQL5 (Part 3): Restart-Aware Breakeven and Trailing Systems

MetaTrader 5Expert Advisors |
119 0
Chacha Ian Maroa
Chacha Ian Maroa

Introduction

Breakeven and trailing-stop systems are common techniques for reducing risk and protecting profits during the lifetime of a trade. Unlike fixed stop-loss and take-profit levels, these protection mechanisms evolve as market conditions change.

The challenge appears when a terminal restart interrupts trade management. Although the trade remains open, important runtime information may be lost. For example:

  • Breakeven activation state;
  • trailing-stop progression;
  • the last price used during trailing updates.

Without this information, the Expert Advisor may resume management using an outdated view of the trade. In this part of the series, we will extend the recovery architecture to persist and restore the dynamic protection state. The goal is to ensure that breakeven and trailing management can continue correctly after a terminal restart instead of starting over from the beginning.


Why Dynamic Protection Requires Additional State

Breakeven and trailing-stop systems behave differently from fixed protection levels. A virtual stop-loss or take-profit remains unchanged until it is triggered. Breakeven and trailing protection, on the other hand, continuously modify the active protection state while a trade is running.

Consider the following scenario:

  • Trade enters profit
  • Breakeven activates
  • Trailing advances several times
  • Terminal restarts
  • EA loses trailing progress
  • Virtual protection becomes outdated
Although the trade remains open, some of the information required to manage it correctly may no longer be available after a restart. For example, the Expert Advisor may no longer know:
  • whether breakeven was already activated;
  • whether trailing was already active;
  • the last price level used during a trailing update.
Without this information, the EA may resume management from an incorrect state. Trailing updates may be repeated, breakeven may activate again unnecessarily, or protection may be applied using outdated information.

The key difference is that dynamic protection systems are stateful. Their behaviour depends not only on the current market price, but also on decisions that were made earlier while the trade was being managed.

For this reason, restoring a trade after a restart requires more than recovering the current virtual stop-loss. The Expert Advisor must also preserve the state information used by the breakeven and trailing systems.


Preparing the Project

This article builds directly on the completed source code from Part 2. Download the MQL5 source file attached under the name "SelfHealingExpertPart2.mq5" and use it as the starting point for the implementations that follow.

The breakeven and trailing systems developed in this article are controlled using the input parameters that were introduced earlier in Part 1:

input bool   InpUseBreakeven           = true;
input double InpBreakevenTriggerPoints = 300;
input double InpBreakevenLockPoints    = 20;
input bool   InpUseTrailingStop        = true;
input double InpTrailStartPoints       = 500;
input double InpTrailStepPoints        = 100;
input double InpTrailDistancePoints    = 300;

These parameters define when breakeven and trailing protection activate and how they behave once active.

ParameterDescription
InpUseBreakevenEnables breakeven management.
InpBreakevenTriggerPoints
Profit required before the breakeven activates.
InpBreakevenLockPoints
Profit is locked when breakeven is activated.
InpUseTrailingStop
Enables trailing-stop management.
InpTrailStartPoints
Profit required before trailing begins.
InpTrailStepPoints
Minimum price movement required before another trailing update is allowed.
InpTrailDistancePoints  Distance maintained between the market price and the virtual stop-loss.

With the project prepared, we can now implement the breakeven management system.


Designing the Breakeven Workflow

Breakeven protection introduces a different type of trade-management behavior from fixed virtual stop-loss and take-profit levels. A virtual stop-loss normally remains unchanged until it is triggered. Breakeven protection permanently modifies the active protection level once a predefined profit threshold has been reached.

A typical breakeven workflow follows this sequence:

  1. Trade enters profit
  2. Profit reaches trigger level
  3. Virtual stop-loss moves to a protected level
  4. Breakeven becomes active

After this transition occurs, the trade is no longer operating under its original protection state. The virtual stop-loss has already been adjusted to reduce or eliminate risk.

An important characteristic of breakeven management is that activation should occur only once during the lifetime of a trade. Once protection has been moved to the breakeven level, there is no reason to repeat the same action during subsequent management cycles. For this reason, the Expert Advisor must be able to distinguish between trades that have not yet reached breakeven and trades where breakeven protection has already been activated.

In the next section, we will implement this behaviour by introducing a dedicated breakeven management function.


Adding Restart-Aware Breakeven Logic

We can now implement the breakeven management function. This function monitors the active trade and automatically upgrades its protection once the configured profit threshold has been reached. When breakeven activates, the virtual stop-loss is moved to a protected level, the trade state is updated to reflect the transition, and the new protection state is persisted so it can be restored correctly after a terminal restart.

Add the following function below CheckVirtualExits:

//+------------------------------------------------------------------+
//| Moves the virtual stop loss to breakeven after price advances.   |
//+------------------------------------------------------------------+
void ManageBreakeven()
  {
//--- check whether breakeven management is enabled
   if(!InpUseBreakeven)
      return;

//--- validate active runtime trade state
   if(!g_hasTradeState)
      return;

//--- prevent repeated breakeven activation
   if(g_tradeState.breakevenActivated)
      return;

//--- validate broker position existence
   if(!PositionSelectByTicket(g_tradeState.ticket))
      return;

//--- retrieve current market prices
   double bid         = SymbolInfoDouble(g_tradeState.symbol, SYMBOL_BID);
   double ask         = SymbolInfoDouble(g_tradeState.symbol, SYMBOL_ASK);
   double triggerSize = InpBreakevenTriggerPoints * _Point;
   double lockSize    = InpBreakevenLockPoints * _Point;

//--- process BUY breakeven logic
   if(g_tradeState.direction == POSITION_TYPE_BUY)
     {
      if(bid - g_tradeState.entryPrice >= triggerSize)
        {
         //--- move virtual stop loss above entry price
         g_tradeState.virtualSL          = g_tradeState.entryPrice + lockSize;
         g_tradeState.breakevenActivated = true;
         g_tradeState.lastUpdateTime     = TimeCurrent();

         SaveTradeState(g_tradeState);

         PrintFormat("Breakeven activated for BUY position. New virtual SL: %.5f",
                     g_tradeState.virtualSL);
        }
     }

//--- process SELL breakeven logic
   if(g_tradeState.direction == POSITION_TYPE_SELL)
     {
      if(g_tradeState.entryPrice - ask >= triggerSize)
        {
         //--- move virtual stop loss below entry price
         g_tradeState.virtualSL          = g_tradeState.entryPrice - lockSize;
         g_tradeState.breakevenActivated = true;
         g_tradeState.lastUpdateTime     = TimeCurrent();

         SaveTradeState(g_tradeState);

         PrintFormat("Breakeven activated for SELL position. New virtual SL: %.5f",
                     g_tradeState.virtualSL);
        }
     }
  }

The most important element of this implementation is the breakevenActivated member. Once breakeven protection has been applied, the trade permanently transitions into a new protection state. Subsequent management cycles should recognise that this transition has already occurred and avoid repeating it.

After activation, the updated trade state is persisted to SQLite using SaveTradeState. This ensures that both the adjusted virtual stop-loss and the breakeven activation state remain available after a terminal restart.

During recovery, the Expert Advisor can restore the saved trade state and continue managing the trade without repeating the breakeven transition. This allows the protection workflow to remain consistent before and after interruption.


Designing the Trailing Workflow

Trailing-stop management differs from breakeven protection in one important way. Breakeven activates once and then remains unchanged. Trailing protection continues to evolve while the trade remains active. A typical trailing workflow follows this sequence:

  1. Trade enters profit
  2. Trailing activates
  3. Virtual stop-loss advances
  4. Price continues moving
  5. Virtual stop-loss advances again

Each trailing update depends on information produced by previous updates. For this reason, trailing protection is stateful. Consider the following scenario:

  1. Trade enters profit
  2. Trailing activates
  3. Virtual stop-loss advances multiple times
  4. Terminal restarts
  5. EA loses the previous trailing progression

After recovery, the Expert Advisor must be able to determine where trailing previously stopped. Otherwise, it may repeat earlier trailing updates or resume management using outdated information. To solve this problem, the trailing system maintains additional runtime state beyond the virtual stop-loss itself.

One of the most important members is lastTrailPrice. This value stores the market price that triggered the most recent trailing update. It acts as a reference point for future trailing decisions and allows the Expert Advisor to resume trailing from the correct progression level after recovery.

In the next section, we will implement the trailing management system and use lastTrailPrice to control trailing activation and progression.


Building Restart-Aware Trailing Protection

Add the following function below ManageBreakeven:

//+------------------------------------------------------------------+
//| Trails the virtual stop loss after price advances sufficiently.  |
//+------------------------------------------------------------------+
void ManageTrailingStop()
  {
//--- check whether trailing management is enabled
   if(!InpUseTrailingStop)
      return;
//--- validate active runtime trade state
   if(!g_hasTradeState)
      return;
//--- validate broker position existence
   if(!PositionSelectByTicket(g_tradeState.ticket))
      return;
//--- retrieve current market prices and trailing settings
   double bid           = SymbolInfoDouble(g_tradeState.symbol, SYMBOL_BID);
   double ask           = SymbolInfoDouble(g_tradeState.symbol, SYMBOL_ASK);
   double trailStart    = InpTrailStartPoints * _Point;
   double trailStep     = InpTrailStepPoints * _Point;
   double trailDistance = InpTrailDistancePoints * _Point;
//--- process BUY trailing logic
   if(g_tradeState.direction == POSITION_TYPE_BUY)
     {
      if(bid - g_tradeState.entryPrice < trailStart)
         return;
      if(!g_tradeState.trailingActivated)
        {
         //--- activate trailing for the first time
         g_tradeState.virtualSL          = bid - trailDistance;
         g_tradeState.lastTrailPrice     = bid;
         g_tradeState.trailingActivated  = true;
         g_tradeState.lastUpdateTime     = TimeCurrent();
         SaveTradeState(g_tradeState);
         PrintFormat("Trailing activated for BUY position. New virtual SL: %.5f",
                     g_tradeState.virtualSL);
         return;
        }
      if(bid - g_tradeState.lastTrailPrice >= trailStep)
        {
         double newVirtualSL = bid - trailDistance;
         if(newVirtualSL > g_tradeState.virtualSL)
           {
            //--- advance BUY virtual stop loss
            g_tradeState.virtualSL       = newVirtualSL;
            g_tradeState.lastTrailPrice  = bid;
            g_tradeState.lastUpdateTime  = TimeCurrent();
            SaveTradeState(g_tradeState);
            PrintFormat("Trailing updated for BUY position. New virtual SL: %.5f",
                        g_tradeState.virtualSL);
           }
        }
      return;
     }
//--- process SELL trailing logic
   if(g_tradeState.direction == POSITION_TYPE_SELL)
     {
      if(g_tradeState.entryPrice - ask < trailStart)
         return;
      if(!g_tradeState.trailingActivated)
        {
         //--- activate trailing for the first time
         g_tradeState.virtualSL          = ask + trailDistance;
         g_tradeState.lastTrailPrice     = ask;
         g_tradeState.trailingActivated  = true;
         g_tradeState.lastUpdateTime     = TimeCurrent();
         SaveTradeState(g_tradeState);
         PrintFormat("Trailing activated for SELL position. New virtual SL: %.5f",
                     g_tradeState.virtualSL);
         return;
        }
      if(g_tradeState.lastTrailPrice - ask >= trailStep)
        {
         double newVirtualSL = ask + trailDistance;
         if(newVirtualSL < g_tradeState.virtualSL)
           {
            //--- advance SELL virtual stop loss
            g_tradeState.virtualSL       = newVirtualSL;
            g_tradeState.lastTrailPrice  = ask;
            g_tradeState.lastUpdateTime  = TimeCurrent();
            SaveTradeState(g_tradeState);
            PrintFormat("Trailing updated for SELL position. New virtual SL: %.5f",
                        g_tradeState.virtualSL);
           }
        }
     }
  }

The ManageTrailingStop function is responsible for activating and advancing the virtual trailing stop as a trade moves further into profit. Unlike breakeven protection, trailing protection is a continuous process that may update multiple times during the lifetime of a trade.

When the configured trailing conditions are satisfied, the function calculates a new virtual stop-loss level and advances protection in the direction of profit. Each trailing update records the latest progression state and persists it to SQLite.

The most important member involved in this process is g_tradeState.lastTrailPrice. This value acts as a progression marker for the trailing workflow. Rather than updating the virtual stop-loss on every market tick, the Expert Advisor uses lastTrailPrice together with the configured trailing step to determine when another trailing adjustment is justified.

Each time trailing activates or advances, the function updates the virtual stop-loss, records the latest trailing price, and saves the modified trade state:

SaveTradeState(g_tradeState);

Persisting this information is what makes the trailing system restart-aware. After a terminal restart, the Expert Advisor can restore the previous trailing progression from SQLite and continue managing the trade from the last recorded state instead of restarting the trailing workflow from the beginning.

With both breakeven and trailing protection implemented, the recovery architecture can now preserve and restore dynamic protection state throughout the lifetime of a managed trade.


Integrating Dynamic Protection into Runtime Management

With breakeven and trailing protection implemented, the next step is to integrate them into the Expert Advisor's runtime management workflow. Dynamic protection is only effective while it is actively monitored and persisted throughout the lifetime of the trade.

Before updating the management loop, add the following helper function below MarkTradeClosed():

//+------------------------------------------------------------------+
//| Updates the heartbeat timestamp of an active trade state.        |
//+------------------------------------------------------------------+
bool UpdateTradeHeartbeat(const ulong ticket)
  {
//--- validate database connection
   if(g_database == INVALID_HANDLE)
      return(false);
//--- prepare SQL query used to update heartbeat information
   string query = StringFormat(
                     "UPDATE trade_states SET last_heartbeat=%I64d,last_update_time=%I64d "
                     "WHERE ticket=%I64u AND state='ACTIVE';",
                     (long)TimeCurrent(),
                     (long)TimeCurrent(),
                     ticket
                  );
//--- execute heartbeat update query
   if(!DatabaseExecute(g_database, query))
     {
      //--- print SQL execution error
      PrintFormat("Failed to update trade heartbeat. Error: %d", GetLastError());
      return(false);
     }
//--- heartbeat updated successfully
   return(true);
  }

The function updates last_heartbeat and last_update_time for the active trade record. It does not change virtual stop-loss, take-profit, breakeven, or trailing values. Its purpose is to confirm that the saved trade state is still being actively maintained.

Next, update ManageActiveTrade so it calls the dynamic protection functions during runtime.

//+-------------------------------------------------------------------+
//| Manages the active trade using the saved virtual protection state.|
//+-------------------------------------------------------------------+
void ManageActiveTrade()
  {
//--- allow management only during normal runtime
   if(g_eaState != EA_STATE_RUNNING)
      return;
//--- validate active runtime trade state
   if(!g_hasTradeState)
      return;
//--- validate broker position existence
   if(!PositionSelectByTicket(g_tradeState.ticket))
     {
      //--- broker position no longer exists
      PrintFormat("Managed position no longer exists. Ticket: %I64u", g_tradeState.ticket);
      MarkTradeClosed(g_tradeState.ticket);
      g_hasTradeState = false;
      return;
     }
//--- manage dynamic protection systems
   ManageBreakeven();
   ManageTrailingStop();
//--- check virtual stop-loss and take-profit levels
   CheckVirtualExits();
//--- stop if virtual exit closed the position
   if(!g_hasTradeState)
      return;
//--- update runtime timestamps
   g_tradeState.lastHeartbeat  = TimeCurrent();
   g_tradeState.lastUpdateTime = TimeCurrent();
//--- update persistent heartbeat and save latest state
   UpdateTradeHeartbeat(g_tradeState.ticket);
   SaveTradeState(g_tradeState);
  }

At this stage, ManageActiveTrade becomes the central runtime management function of the Expert Advisor. Each execution cycle now coordinates breakeven protection, trailing-stop management, virtual exit monitoring, heartbeat updates, and persistence.

This integration is what allows the recovery architecture to preserve the latest protection state continuously while the trade remains active. Any changes made by the breakeven or trailing systems are immediately persisted, ensuring that the Expert Advisor can restore and continue the same protection workflow after a terminal restart.

With this update, the recovery system is no longer limited to restoring static protection levels. It can now preserve and resume evolving trade-management decisions throughout the entire lifetime of a managed position.


Testing Breakeven and Trailing Recovery

At this stage, the recovery architecture is now capable of:
  • Activating breakeven protection dynamically.
  • Trailing the virtual stop-loss continuously.
  • Persisting runtime trailing progression into SQLite.
  • Restoring the same dynamic protection state safely after a terminal restart.
The next step is to verify that the trailing system continues correctly after recovery instead of restarting from the beginning. For easier observation during testing, temporarily configure the following input parameters on EURUSD:
input int    InpTimerSeconds            = 2 ;
input double InpVirtualSLPoints         = 150;
input double InpVirtualTPPoints         = 300;
input double InpBreakevenTriggerPoints  = 40;
input double InpBreakevenLockPoints     = 10;
input double InpTrailStartPoints        = 60;
input double InpTrailStepPoints         = 20;
input double InpTrailDistancePoints     = 30;
These values allow breakeven and trailing protection to activate within a relatively short period of market movement, making the recovery workflow easier to study during testing. Begin by compiling the Expert Advisor and attaching it to a EURUSD chart. If test-trade opening is enabled, the EA should automatically open a managed recovery trade. As price moves into profit:
  • Breakeven protection should activate.
  • Trailing protection should become active. 
  • The virtual stop-loss should begin advancing progressively.
At this point, inspect the SQLite database using a SQLite browser application and observe the saved trade-state record. The following fields should now contain updated runtime recovery information:
  1. breakeven_activated
  2. trailing_activated
  3. virtual_sl
  4. last_trail_price

Next, allow the trailing system to advance multiple times while the trade remains active. As the market continues moving favorably, the Expert Advisor should:

  • Update the virtual stop-loss repeatedly.
  • Update last_trail_price.
  • Persist the latest trailing progression into SQLite.
Now close the MetaTrader terminal completely while the trade remains active. After restarting the terminal, attach the Expert Advisor to the same chart again. During startup recovery, the EA should:
  • Detect the managed broker-side position.
  • Restore the saved trade-state information.
  • Restore the latest virtual stop-loss.
  • Restore the trailing activation state.
  • Reload the previous lastTrailPrice value from SQLite.

Successful recovery

The most important validation occurs after recovery. Trailing protection should continue from the previously saved progression point instead of restarting from the original trade entry price or initial trailing activation level.

As market price advances further:

  • The next trailing update should occur only after the price exceeds the saved lastTrailPrice by the configured trailing step distance.
  • The virtual stop-loss should continue advancing from the restored protection state.

This confirms that the trailing system preserved operational continuity successfully across terminal restart. Finally, inspect the SQLite database again and confirm that virtual_sl, last_trail_price, breakeven_activated, and trailing_activated continue updating correctly after recovery.

At this stage, the recovery architecture can now preserve and restore evolving runtime protection state safely, allowing breakeven and trailing management to continue consistently even after terminal interruption or restart.


Conclusion

In this part of the series, we extended the recovery architecture developed in Part 2 by introducing restart-aware breakeven and trailing-stop systems. The Expert Advisor can now:

  • Activate breakeven protection dynamically.
  • Persist breakeven activation state inside SQLite.
  • Trail the virtual stop-loss continuously during runtime.
  • Preserve trailing progression information after restart.
  • Continue dynamic protection safely from the exact operational state reached before interruption.

The recovery system no longer restores only static virtual stop-loss and take-profit levels. The Expert Advisor also preserves the evolving runtime protection behavior. This allows the recovery workflow to maintain operational continuity even when: trailing protection was already active; multiple trailing updates had already occurred; or breakeven protection had already been activated before restart.

Readers are encouraged to compare their implementation with the attached source file:

SelfHealingExpertPart3.mq5

before proceeding further. In Part 4 of the series, we will continue extending the recovery architecture by introducing: heartbeat monitoring, trade-state reconciliation, and Safe Mode recovery systems. The Expert Advisor will become capable of detecting recovery inconsistencies, identifying broken runtime synchronization, and protecting itself from unsafe recovery conditions during live operation.

Attachments

FilenameDescription
SelfHealingExpertPart2.mq5
Completed source code from Part 2. Use this file as the starting point for all implementations developed in Part 3.
SelfHealingExpertPart3.mq5
Completed source code for Part 3, including restart-aware breakeven management, persistent trailing-stop progression, and dynamic protection recovery after terminal restart.
MetaTrader 5 Machine Learning Blueprint (Part 18): Sequential Bootstrap, Corrected — Clone, Class Erasure, and the Comparison Toolkit MetaTrader 5 Machine Learning Blueprint (Part 18): Sequential Bootstrap, Corrected — Clone, Class Erasure, and the Comparison Toolkit
The article diagnoses two defects that neutralize sequential bootstrap during cross‑validation: type erasure of SequentiallyBootstrappedBaggingClassifier and a fold‑level shape mismatch from cloning full samples info sets. It retains the classifier's identity, adds find seq bagging to re‑inject fold‑sliced t1 in CalibratorCV.fit, and resets state per split. A new bootstrap_comparison module reports OOF and OOB metrics and memory, letting you verify that sequential sampling is applied correctly and quantify its impact.
Building an Object-Oriented Session VWAP Engine in MQL5 Building an Object-Oriented Session VWAP Engine in MQL5
This article shows how to implement a session vwap in MQL5 as a reusable include class with a strict daily reset at broker midnight. The engine computes VWAP and volume‑weighted deviation bands only on closed bars and anchors accumulation with MqlDateTime to avoid distortions from missing candles. A companion indicator plots the baseline and bands, while an Expert Advisor reads signals once per bar for consistent, CPU‑efficient execution and reliable testing.
Lazy-Loading Indicator Handles in MQL5: A Resource Manager Pattern for Multi-Timeframe EAs Lazy-Loading Indicator Handles in MQL5: A Resource Manager Pattern for Multi-Timeframe EAs
Multi‑timeframe EAs that initialize every indicator handle in OnInit() pay a fixed startup cost even when most handles are never used. CIndicatorCache applies lazy loading with composite‑key lookup, reference‑counted Acquire/Release, and a deterministic FlushAll() for cleanup. Handles are created on first request and reused across ticks, reducing startup latency, avoiding repeated heap allocation, and preventing terminal resource leaks through centralized ownership.
Feature Engineering for ML (Part 7): Entropy Features in Python Feature Engineering for ML (Part 7): Entropy Features in Python
The article provides production-ready entropy estimators (Shannon, plug-in, Lempel–Ziv, Kontoyiannis) operating on tick-rule–encoded sequences. It resolves three correctness and performance issues in the original code, verifies outputs against chapter references, and extends encoding with quantile and sigma options. Users gain reproducible results and markedly improved computation speed for large bar sets.