Engineering a Self-Healing Expert Advisor in MQL5 (Part 3): Restart-Aware Breakeven and Trailing Systems
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
- whether breakeven was already activated;
- whether trailing was already active;
- the last price level used during a trailing update.
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.
| Parameter | Description |
|---|---|
| InpUseBreakeven | Enables 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:
- Trade enters profit
- Profit reaches trigger level
- Virtual stop-loss moves to a protected level
- 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:
- Trade enters profit
- Trailing activates
- Virtual stop-loss advances
- Price continues moving
- 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:
- Trade enters profit
- Trailing activates
- Virtual stop-loss advances multiple times
- Terminal restarts
- 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.
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.
- breakeven_activated
- trailing_activated
- virtual_sl
- 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.
- 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.

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.
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
| Filename | Description |
|---|---|
| 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. |
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
MetaTrader 5 Machine Learning Blueprint (Part 18): Sequential Bootstrap, Corrected — Clone, Class Erasure, and the Comparison Toolkit
Building an Object-Oriented Session VWAP Engine in MQL5
Lazy-Loading Indicator Handles in MQL5: A Resource Manager Pattern for Multi-Timeframe EAs
Feature Engineering for ML (Part 7): Entropy Features in Python
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use