Engineering Trading Discipline into Code (Part 5): Account-Level Risk Enforcement in MQL5
Contents
- Introduction
- Concept and System Design
- Core Components of the Discipline System
- MQL5 Implementation
- Testing
- Conclusion
Introduction
In trading, risk rules are rarely the problem. The parameters are usually clear: a fixed percentage of equity per trade, a defined Stop Loss, a Take Profit derived from the same model, and a consistent risk-to-reward ratio. The real issue emerges after execution. Once positions are live, they can be modified, partially closed, or opened from multiple sources—including manual entries and different Expert Advisors operating across symbols. Over time, this leads to a disconnect between intended risk and actual exposure.
This breakdown follows recognizable patterns: positions without Stop Loss or Take Profit, lot sizes that exceed the defined risk threshold, or Take Profit levels that no longer reflect the intended risk-to-reward structure. In MetaTrader 5, there is no built-in mechanism that continuously validates these conditions at the account level once a trade has been placed.
To address this, risk must be treated as a set of enforceable conditions rather than static rules defined at entry. In this system, a position is only considered valid when all of the following are satisfied simultaneously: a Stop Loss is present, a Take Profit is defined, exposure is aligned with a fixed percentage of equity, and the resulting structure respects the configured risk-to-reward ratio. Any deviation from these conditions is treated as a violation.
This article introduces an account-level enforcement engine that continuously monitors all open positions, evaluates them against these conditions, and applies corrective actions when required. The focus is not on entry logic or signal generation, but on maintaining structural consistency of risk throughout the entire trade lifecycle.
Concept and System Design

From Trading Rules to Measurable Conditions
The limitation identified in the previous section is the absence of a mechanism that guarantees consistent execution. For discipline to be reliable, it must be measurable, evaluable, and enforceable programmatically.
The first step is to move from general principles to concrete conditions. In this system, a position is not judged subjectively but evaluated against a fixed set of rules.

A trade is considered compliant only if the following requirements are satisfied:
- A valid Stop Loss is present,
- a Take Profit is defined,
- risk is calculated as a predefined percentage of account equity,
- and the Take Profit maintains a consistent risk-to-reward ratio.
These conditions are not only checked but recalculated dynamically based on account equity and symbol properties, ensuring that compliance is enforced programmatically rather than assumed at execution. They form the foundation of the system and guide all subsequent decisions.
Continuous Evaluation in a Dynamic Environment
Once these criteria are established, the problem shifts to continuous evaluation. In MetaTrader 5, positions can be opened, modified, or partially closed, and may originate from different sources. This means compliance cannot be checked once; it must be evaluated repeatedly. The system is therefore designed to operate in a continuous cycle, where every active position is periodically inspected and validated against the defined rules.
Operational Cycle of the System
This leads to an operational model:
The system starts by detecting all active positions at the account level. It does not distinguish between manual and automated trades, ensuring that every position is subject to the same conditions. Each detected position is evaluated against compliance criteria. This includes verifying the presence of Stop Loss and Take Profit levels, as well as confirming that the risk exposure aligns with the configured percentage of account equity. If a violation is identified, enforcement actions are applied. Depending on the configuration, this may involve applying missing Stop Loss and Take Profit levels, correcting inconsistent parameters, or adjusting position size to restore acceptable risk exposure. After enforcement, the system updates both internal state and visual output. This includes updating on-chart information, maintaining a record of evaluated positions, and tracking how often corrections are required.
System Behavior and Control Modes
A key design aspect is operational flexibility. Not all trading scenarios require immediate intervention. For this reason, the system supports multiple modes of operation. In passive mode, positions are evaluated and violations are reported without modification. In assisted mode, the system highlights issues and prepares corrective actions with notifications. In strict mode, enforcement is automatic and all violations are corrected immediately. This layered approach allows the same system to function both as a learning tool and as a strict execution controller.
From Behavior to Structure
The design shifts responsibility from momentary decisions to a structured process. Instead of relying on manual execution under pressure, the system continuously aligns positions with predefined risk parameters. In doing so, it transforms discipline from a variable behavior into a consistent property of the trading environment.
Core Components of the Discipline System
A disciplined trading environment requires more than predefined rules. This section describes the system’s core components and their roles in maintaining consistency and control. These components define how discipline is maintained automatically across multiple charts, symbols, and trade types.

1. Trade Monitor
The Trade Monitor tracks all positions and pending orders in real time, regardless of execution source. It tracks all positions in real time, verifies compliance with risk rules, and flags violations. Without continuous monitoring, trade limits may not be enforced consistently.
2. Risk Evaluator
After data collection, the Risk Evaluator processes the information. This component evaluates whether a trade fits within equity-based risk settings, verifies correct Stop Loss (SL) and Take Profit (TP) levels, and ensures the risk-to-reward ratio matches the strategy. It determines whether the trade complies with defined rules. If not, the trade is blocked or adjusted.
3. Enforcement Module
The Enforcement Module applies rule-based actions. It enforces limits by deleting excess pending orders and closing or adjusting positions that violate defined risk constraints. Crucially, it does this without affecting trades already running within the allowed limit. This ensures consistent enforcement without affecting compliant positions.
4. Dashboard & Alerts
System visibility is required for effective monitoring. The Dashboard provides a real-time overview of system status—how many trades have been used, which limits are approaching, and which rules are active. Alerts, both on-screen and push notifications, communicate critical events immediately. This feedback provides continuous visibility without requiring constant monitoring.
5. Configuration & Session Control
The system includes configurable parameters for the trading day, session start time, daily entry limits, risk percentages, and allowed symbols. This allows adaptation to different trading strategies and workflows, rather than forcing you into rigid parameters. It also defines exactly what counts as a “trade” and when a day begins. This removes ambiguity in rule enforcement.
MQL5 Implementation
With the system structure clearly defined, the next step is to translate these concepts into a working MQL5 program. The implementation follows the same logic introduced earlier: detect trades, evaluate compliance, enforce rules, and communicate the result. Each part of this process is mapped to specific MQL5 functions and event handlers, ensuring that the system operates continuously and reliably at the account level.
Initialization (OnInit()): System Setup
The system initializes in OnInit(), where all core parameters are set. This includes the default operation mode, risk percentage, and risk-to-reward ratio. These values define the baseline for trade evaluation.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Validate timer if(InpTimerSeconds < 1) { Print("ERROR: InpTimerSeconds must be >= 1. Using 1."); EventSetTimer(1); } else EventSetTimer(InpTimerSeconds); //--- Copy inputs to globals g_Mode = InpDefaultMode; g_RiskPercent = InpRiskPercent; g_RRatio = InpRRatio; //--- Create panel DeletePanel(); CreatePanel(); UpdatePanel(); Print("Discipline Engine started. Mode=",EnumToString(g_Mode), ", Risk=",DoubleToString(g_RiskPercent,2),"%, R:R=",DoubleToString(g_RRatio,2)); return INIT_SUCCEEDED; }
The system activates a timer using EventSetTimer(). This ensures that monitoring occurs at fixed intervals, independent of market ticks. This avoids reliance on OnTick(), which depends on price movement. In low-activity markets, trades may remain unevaluated. Using a timer ensures consistent execution regardless of market conditions.
In addition, the initialization phase creates the on-chart interface through the CreatePanel() function. The panel acts as a real-time control layer for adjusting system parameters.
Continuous Monitoring (OnTimer()): Execution Loop
The core execution loop runs in OnTimer(). This function executes at fixed intervals. Each cycle scans all active positions using PositionsTotal() and retrieves tickets via PositionGetTicket(). After PositionSelectByTicket(), the system extracts key parameters: symbol, lot size, entry price, SL, TP, and position type. It uses PositionGetDouble() and PositionGetInteger() for retrieval. At this stage, the system performs structural validation. Risk and ratio validation are handled during enforcement calculations.
//+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { ulong activeTickets[]; ArrayResize(activeTickets, 0); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; string symbol = PositionGetString(POSITION_SYMBOL); double lot = PositionGetDouble(POSITION_VOLUME); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- store active ticket int idx = ArraySize(activeTickets); ArrayResize(activeTickets, idx + 1); activeTickets[idx] = ticket; double requiredSLDist = CalculateRequiredSLDistance(symbol, lot, g_RiskPercent); if(requiredSLDist <= 0.0) { Print("Warning: cannot calculate risk for ", symbol, " ticket ", ticket); continue; } if(g_Mode != MODE_PASSIVE) AdjustLotSize(ticket, symbol, type, lot, openPrice, requiredSLDist); //--- refresh position data if(PositionSelectByTicket(ticket)) { lot = PositionGetDouble(POSITION_VOLUME); sl = PositionGetDouble(POSITION_SL); tp = PositionGetDouble(POSITION_TP); } //--- compliance check bool isCompliant = true; string violationMsg = ""; if(sl == 0.0) { isCompliant = false; violationMsg = "Missing SL"; } else if(tp == 0.0) { isCompliant = false; violationMsg = "Missing TP"; } else { double currentSLDist = MathAbs(openPrice - sl); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(MathAbs(currentSLDist - requiredSLDist) > point * 0.5) { isCompliant = false; violationMsg = StringFormat("Risk mismatch: current=%.5f required=%.5f", currentSLDist, requiredSLDist); } } g_TotalTradesChecked++; //--- violation handling if(!isCompliant) { Print("Violation on ticket ", ticket, ": ", violationMsg); if(g_Mode != MODE_PASSIVE) { bool corrected = EnforceSLTP(ticket, symbol, type, lot, openPrice, requiredSLDist, g_RRatio); if(corrected) { g_ViolationsCorrected++; if(g_Mode == MODE_ASSISTED) { Alert(StringFormat("Trade %I64u: %s. Corrected.", ticket, violationMsg)); LogAction(ticket, violationMsg + " corrected (assisted)"); } else if(g_Mode == MODE_STRICT) { LogAction(ticket, violationMsg + " corrected (strict)"); } } else { LogAction(ticket, violationMsg + " correction failed"); } } else { LogAction(ticket, violationMsg + " (passive, no action)"); } } //--- draw SL/TP lines if(symbol == _Symbol && PositionSelectByTicket(ticket)) { sl = PositionGetDouble(POSITION_SL); tp = PositionGetDouble(POSITION_TP); DrawSLTPLines(ticket, sl, tp); } } CleanupLines(activeTickets); UpdatePanel(); }
This approach evaluates all positions regardless of origin. Whether the trade originates from manual execution or another Expert Advisor, it is treated uniformly. This eliminates inconsistencies that arise when different strategies operate independently on the same account. Monitoring is continuous rather than a one-time action, with positions re-evaluated at every timer cycle. This allows the system to respond to changes such as manual modifications, partial closes, or broker-side adjustments, ensuring that compliance is maintained throughout the trade’s lifecycle.
Evaluating Trade Compliance
Once position data is collected, the system evaluates each trade against the predefined rules. Evaluation begins with structural checks: verifying whether Stop Loss and Take Profit values are present. If either value is missing, the position is immediately flagged as non-compliant. If both are present, the system proceeds to calculate the required Stop Loss distance using the CalculateRequiredSLDistance() function.
//+------------------------------------------------------------------+ //| Calculate required SL distance in price units | //+------------------------------------------------------------------+ double CalculateRequiredSLDistance(string symbol,double lot,double riskPercent) { double equity = AccountInfoDouble(ACCOUNT_EQUITY); if(equity<=0.0 || riskPercent<=0.0 || lot<=0.0) return 0.0; double riskAmount = equity*riskPercent/100.0; double tickValue = SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_SIZE); if(tickValue<=0.0 || tickSize<=0.0) return 0.0; double riskTicks = riskAmount/(tickValue*lot); if(riskTicks<=0.0) return 0.0; double slDistance = riskTicks*tickSize; long stopsLevel = SymbolInfoInteger(symbol,SYMBOL_TRADE_STOPS_LEVEL); if(stopsLevel>0) { double minStop = stopsLevel*tickSize; if(slDistance < minStop) slDistance = minStop; } return slDistance; }
This function translates risk from a percentage of account equity into a price distance. It uses inputs such as account equity (AccountInfoDouble(ACCOUNT_EQUITY)), symbol tick value (SYMBOL_TRADE_TICK_VALUE), and tick size (SYMBOL_TRADE_TICK_SIZE). This ensures risk is measured consistently across different symbols, regardless of their pricing structure.
The calculated Stop Loss distance is then compared to the actual distance between the entry price and the existing Stop Loss. Because of floating-point precision and broker pricing constraints, the system does not require exact equality. Instead, it applies a tolerance threshold based on the symbol’s point value. This prevents unnecessary modifications caused by minor rounding differences and avoids excessive trade requests.
Handling Broker Constraints and Precision
The implementation must respect broker limitations. Every symbol has a minimum stop distance defined by SYMBOL_TRADE_STOPS_LEVEL. If a calculated Stop Loss or Take Profit is too close to the current market price, the broker will reject the modification. To handle this, the system validates all calculated levels before sending a request. If the required Stop Loss distance is smaller than the minimum allowed, it is adjusted accordingly. This ensures that all enforcement actions remain valid and executable.
Precision handling is required for accurate comparisons. Prices are normalized using the symbol’s point size, and comparisons are made within a defined tolerance range. This avoids repeated modifications caused by tiny discrepancies, which could otherwise lead to unnecessary trade traffic and potential execution delays.
Lot Size Adjustment Logic
Before enforcing Stop Loss and Take Profit corrections, the system performs a critical safety check using the AdjustLotSize() function. This step ensures that the position size itself does not violate risk constraints. The function calculates the maximum allowable lot size based on the configured risk percentage and minimum stop distance. If the current lot size exceeds this value, the system reduces exposure by partially closing the position using a reverse trade operation through OrderSend(). The result of the trade request should be validated to ensure successful execution and handle potential errors.
//+------------------------------------------------------------------+ //| Adjust oversized lot (partial close) | //+------------------------------------------------------------------+ void AdjustLotSize(ulong ticket,string symbol,ENUM_POSITION_TYPE type, double currentLot,double openPrice,double requiredSLDist) { long stopsLevel = SymbolInfoInteger(symbol,SYMBOL_TRADE_STOPS_LEVEL); if(stopsLevel <= 0) return; double tickSize = SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_SIZE); double minStopPrice = stopsLevel*tickSize; if(requiredSLDist >= minStopPrice) return; double equity = AccountInfoDouble(ACCOUNT_EQUITY); double riskAmount = equity*g_RiskPercent/100.0; double tickValue = SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_VALUE); if(tickValue <= 0.0) return; double maxSafeLot = riskAmount/(tickValue*(minStopPrice/tickSize)); maxSafeLot = NormalizeVolumeByStep(symbol,maxSafeLot); if(currentLot > maxSafeLot + SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP)*0.5) { double reduceAmount = currentLot - maxSafeLot; reduceAmount = NormalizeVolumeByStep(symbol,reduceAmount); if(reduceAmount <= 0.0) return; Print("Lot size too large (",DoubleToString(currentLot,2), "). Reducing by ",DoubleToString(reduceAmount,2), " to ",DoubleToString(maxSafeLot,2)," for ticket ",ticket); MqlTradeRequest req = {}; MqlTradeResult res = {}; req.action = TRADE_ACTION_DEAL; req.symbol = symbol; req.volume = reduceAmount; req.deviation = 10; req.position = ticket; req.magic = (ulong)PositionGetInteger(POSITION_MAGIC); req.type_filling = ORDER_FILLING_IOC; if(type == POSITION_TYPE_BUY) req.type = ORDER_TYPE_SELL; else req.type = ORDER_TYPE_BUY; req.price = SymbolInfoDouble(symbol,(type==POSITION_TYPE_BUY)?SYMBOL_BID:SYMBOL_ASK); if(OrderSend(req,res)) { if(res.retcode == TRADE_RETCODE_DONE) { LogAction(ticket,StringFormat("Reduced lot from %.2f to %.2f",currentLot,maxSafeLot)); ObjectSetString(0,g_LblLastAct,OBJPROP_TEXT, StringFormat("Last: Ticket %I64u lot reduced",ticket)); } else LogAction(ticket,StringFormat("Lot reduction failed, retcode %d",res.retcode)); } else LogAction(ticket,"Lot reduction OrderSend failed"); } }
This step is essential because risk is a combination of both Stop Loss distance and lot size. Even with a valid Stop Loss, an oversized position can still result in excessive exposure. Correcting lot size first keeps Stop Loss adjustments aligned with intended risk.
Enforcing Stop Loss and Take Profit
Once the position size is validated, the system proceeds to enforce proper Stop Loss and Take Profit levels using the EnforceSLTP() function.
//+------------------------------------------------------------------+ //| Enforce correct SL and TP for a position | //+------------------------------------------------------------------+ bool EnforceSLTP(ulong ticket,string symbol,ENUM_POSITION_TYPE type, double lot,double openPrice,double requiredSLDist,double rr) { if(requiredSLDist <= 0.0 || rr <= 0.0) return false; double newSL = 0.0; double newTP = 0.0; if(type == POSITION_TYPE_BUY) { newSL = openPrice - requiredSLDist; newTP = openPrice + requiredSLDist*rr; } else { newSL = openPrice + requiredSLDist; newTP = openPrice - requiredSLDist*rr; } newSL = NormalizePriceBySymbol(symbol,newSL); newTP = NormalizePriceBySymbol(symbol,newTP); long stopsLevel = SymbolInfoInteger(symbol,SYMBOL_TRADE_STOPS_LEVEL); double point = SymbolInfoDouble(symbol,SYMBOL_POINT); double minStopDist = stopsLevel*point; if(MathAbs(openPrice-newSL) < minStopDist || MathAbs(openPrice-newTP) < minStopDist) { Print("SL/TP too close to market (minStop=",DoubleToString(minStopDist,_Digits), "), correction skipped for ticket ",ticket); return false; } MqlTradeRequest req = {}; MqlTradeResult res = {}; req.action = TRADE_ACTION_SLTP; req.symbol = symbol; req.position = ticket; req.sl = newSL; req.tp = newTP; if(OrderSend(req,res) && res.retcode == TRADE_RETCODE_DONE) { LogAction(ticket,StringFormat("SL/TP set: SL=%.5f, TP=%.5f",newSL,newTP)); ObjectSetString(0,g_LblLastAct,OBJPROP_TEXT, StringFormat("Last: Ticket %I64u SL/TP corrected",ticket)); return true; } Print("Modify failed for ticket ",ticket,", retcode=",res.retcode); return false; }
This function calculates new Stop Loss and Take Profit prices based on the entry price, required Stop Loss distance, and configured risk-to-reward ratio. It then compares these values with the current levels to determine whether modification is necessary. If a correction is required, a trade request is prepared using TRADE_ACTION_SLTP and sent via OrderSend(). Before execution, the system ensures that all values meet broker requirements and do not violate minimum distance constraints. This process ensures that every position follows a consistent structure: controlled risk, defined exit points, and a predictable reward profile.
Mode-Based Behavior Control
The behavior of the system is controlled through user interaction handled in the OnChartEvent() function. This allows the trader to switch between modes using on-screen buttons without restarting the EA.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id != CHARTEVENT_OBJECT_CLICK) return; //--- MODE CONTROL if(sparam == g_BtnPassive) { g_Mode = MODE_PASSIVE; UpdatePanel(); Alert("Mode set to PASSIVE"); Print("Mode changed to PASSIVE"); } else if(sparam == g_BtnAssisted) { g_Mode = MODE_ASSISTED; UpdatePanel(); Alert("Mode set to ASSISTED"); Print("Mode changed to ASSISTED"); } else if(sparam == g_BtnStrict) { g_Mode = MODE_STRICT; UpdatePanel(); Alert("Mode set to STRICT"); Print("Mode changed to STRICT"); } //--- RISK CONTROL else if(sparam == g_BtnRiskUp) { g_RiskPercent = MathMin(10.0, g_RiskPercent + 0.1); UpdatePanel(); Alert(StringFormat("Risk percent set to %.2f%%", g_RiskPercent)); PrintFormat("Risk changed to %.2f%%", g_RiskPercent); } else if(sparam == g_BtnRiskDown) { g_RiskPercent = MathMax(0.1, g_RiskPercent - 0.1); UpdatePanel(); Alert(StringFormat("Risk percent set to %.2f%%", g_RiskPercent)); PrintFormat("Risk changed to %.2f%%", g_RiskPercent); } //--- R:R CONTROL else if(sparam == g_BtnRRUp) { g_RRatio = MathMin(10.0, g_RRatio + 0.5); UpdatePanel(); Alert(StringFormat("R:R ratio set to %.1f", g_RRatio)); PrintFormat("R:R changed to %.1f", g_RRatio); } else if(sparam == g_BtnRRDown) { g_RRatio = MathMax(0.5, g_RRatio - 0.5); UpdatePanel(); Alert(StringFormat("R:R ratio set to %.1f", g_RRatio)); PrintFormat("R:R changed to %.1f", g_RRatio); } }
In passive mode, the system evaluates trades and logs violations without making any changes. In assisted mode, it applies corrections while issuing alerts to inform the trader. In strict mode, all violations are corrected automatically, and the system operates as a fully autonomous enforcement mechanism. This flexibility allows the system to adapt to different stages of trading development, from observation and learning to strict rule enforcement.
Visual Feedback and State Tracking
The system provides visual feedback through the on-chart panel using the UpdatePanel() function. This panel displays key information such as current mode, risk percentage, risk-to-reward ratio, account equity, and a calculated discipline score.
//+------------------------------------------------------------------+ //| Update on-chart information panel | //+------------------------------------------------------------------+ void UpdatePanel() { string modeStr = EnumToString(g_Mode); ObjectSetString(0, g_LblMode, OBJPROP_TEXT, "Mode: " + modeStr); ObjectSetString(0, g_LblRisk, OBJPROP_TEXT, StringFormat("Risk: %.2f%%", g_RiskPercent)); ObjectSetString(0, g_LblRR, OBJPROP_TEXT, StringFormat("R:R: %.1f", g_RRatio)); double equity = AccountInfoDouble(ACCOUNT_EQUITY); ObjectSetString(0, g_LblEquity, OBJPROP_TEXT, StringFormat("Equity: %.2f", equity)); }
The discipline score is a measurable compliance indicator. It is calculated as the proportion of trades requiring correction out of the total trades evaluated. This transforms discipline into a quantifiable metric rather than a subjective assessment.
//+------------------------------------------------------------------+ //| Draw horizontal lines for SL and TP | //+------------------------------------------------------------------+ void DrawSLTPLines(ulong ticket,double sl,double tp) { string slName = g_Prefix + "SL_" + (string)ticket; string tpName = g_Prefix + "TP_" + (string)ticket; ObjectDelete(0,slName); ObjectDelete(0,tpName); if(sl != 0.0) { if(ObjectCreate(0,slName,OBJ_HLINE,0,0,sl)) { ObjectSetInteger(0,slName,OBJPROP_COLOR,clrRed); ObjectSetInteger(0,slName,OBJPROP_WIDTH,1); ObjectSetInteger(0,slName,OBJPROP_STYLE,STYLE_DASH); ObjectSetString(0,slName,OBJPROP_TOOLTIP,"SL Ticket " + (string)ticket); ObjectSetString(0,slName,OBJPROP_TEXT,"SL " + (string)ticket); } } if(tp != 0.0) { if(ObjectCreate(0,tpName,OBJ_HLINE,0,0,tp)) { ObjectSetInteger(0,tpName,OBJPROP_COLOR,clrLimeGreen); ObjectSetInteger(0,tpName,OBJPROP_WIDTH,1); ObjectSetInteger(0,tpName,OBJPROP_STYLE,STYLE_DASH); ObjectSetString(0,tpName,OBJPROP_TOOLTIP,"TP Ticket " + (string)ticket); ObjectSetString(0,tpName,OBJPROP_TEXT,"TP " + (string)ticket); } } }
In addition to the panel, the system visually represents Stop Loss and Take Profit levels using the DrawSLTPLines() function. These lines are dynamically updated and removed when positions are closed using the CleanupLines() function, ensuring that the chart always reflects the current state accurately.
//+------------------------------------------------------------------+ //| Remove lines for positions that are no longer active | //+------------------------------------------------------------------+ void CleanupLines(ulong &activeTickets[]) { int total = ObjectsTotal(0,-1,-1); for(int i=total-1; i>=0; i--) { string name = ObjectName(0,i); if(StringFind(name,g_Prefix+"SL_")==0 || StringFind(name,g_Prefix+"TP_")==0) { int firstUnderscore = StringFind(name,"_"); int secondUnderscore = StringFind(name,"_",firstUnderscore+1); if(secondUnderscore > 0) { ulong ticket = (ulong)StringToInteger(StringSubstr(name,secondUnderscore+1)); bool found = false; for(int j=0; j<ArraySize(activeTickets); j++) { if(activeTickets[j] == ticket) { found = true; break; } } if(!found) ObjectDelete(0,name); } } } }
Logging and Traceability
Every action performed by the system is recorded using the LogAction() function. This includes detected violations, applied corrections, and any failed operations.
//+------------------------------------------------------------------+ //| Log action to Experts tab | //+------------------------------------------------------------------+ void LogAction(ulong ticket,string msg) { Print(StringFormat("[%I64u] %s",ticket,msg)); }
This logging mechanism provides traceability. It allows the trader to review system behavior, verify that rules are being applied correctly, and identify any potential issues during testing or live operation.
Lifecycle Management and Stability
System cleanup is handled in OnDeinit(). This includes stopping the timer using EventKillTimer(), removing all panel objects, and clearing any graphical elements created during execution.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); DeletePanel(); DeleteAllLines(); }
This prevents leftover objects and ensures a clean environment, especially when attaching or removing the EA multiple times.
Testing
Testing demonstrated that the system behaves as designed under real market conditions. The testing included monitoring positions, identifying violations, and applying enforcement actions without interfering with normal trading activity. It was conducted on live charts across multiple symbols and timeframes. After attaching the Expert Advisor, behavior was observed from initialization through active trade management under different scenarios.

The following image shows the system immediately after initialization. At this point, the panel is active and displays the configured parameters, including mode, risk percentage, and risk-to-reward ratio. For this test, the system was set to STRICT mode, where all violations are corrected automatically. Account equity is displayed in real time, along with the discipline score, which reflects the current compliance state of the system. Trades were then introduced under controlled conditions to test system logic.
The first test focused on missing Stop Loss and Take Profit levels. Positions were opened manually without protective levels. During the next timer cycle, the system detected these positions and immediately applied both Stop Loss and Take Profit based on the configured risk parameters. The changes were reflected on the chart through the horizontal levels drawn at the enforced prices.
The second test targeted risk inconsistency. Positions were deliberately opened with lot sizes that exceeded the allowed exposure relative to account equity. In this case, the system first reduced the position size before adjusting the Stop Loss. Once the exposure was aligned with the defined risk percentage, it recalculated and enforced the correct Stop Loss and Take Profit levels. This confirmed that the system treats risk as a combination of position size and price distance, rather than handling them independently.
The third test validated continuous monitoring. After the system corrected positions, Stop Loss levels were manually removed and Take Profit values were altered. In each case, the system detected the changes during subsequent timer cycles and restored compliance automatically. This shows that enforcement is continuous throughout the trade lifecycle.

The second image shows the system under dynamic conditions. Market movement continues while positions evolve in real time. The system maintains a consistent structure across all trades. The panel updates continuously, and enforced Stop Loss and Take Profit levels remain visible. One important observation from the testing is that the system operates independently of trade origin. Whether trades were opened manually or generated by other logic, they were evaluated and corrected using the same rules. This demonstrates that discipline is enforced at the account level. All trade modifications respected broker constraints, including minimum stop distances and pricing precision. The timer-based execution proved reliable, ensuring that monitoring continued even during periods of low market activity.
Overall, testing shows that the system performs as intended. It consistently detects violations, applies corrections, and maintains compliance across all active positions without requiring manual intervention.
Conclusion
This article presented a system that transforms risk management from a one-time setup into a continuous enforcement process. Instead of relying on discipline at the moment of execution, the approach introduces a structured cycle—detecting positions, evaluating compliance, enforcing corrections, and updating system state—applied consistently across the entire account.
The MQL5 implementation follows this structure directly. A timer-driven loop ensures that all positions are reviewed at fixed intervals, independent of market activity. Risk is translated from a percentage of equity into precise price levels, allowing consistent validation across different symbols. When violations are identified, corrective actions are applied through two mechanisms: enforcing Stop Loss and Take Profit levels, and reducing position size when exposure exceeds acceptable limits. Supporting components such as logging, chart visualization, and a control panel provide transparency and interaction.
The result is a deployable account-level discipline engine that operates independently of how trades are opened. Once attached to a chart, it continuously evaluates every active position and ensures alignment with defined risk parameters. Its behavior is observable in real time: missing protection levels are applied, excessive exposure is reduced, actions are recorded in the log, and updated levels are reflected on the chart interface.
This approach removes reliance on execution-time discipline by introducing continuous validation at the account level. Instead of assuming correct risk at entry, the system repeatedly verifies and restores compliance throughout the trade lifecycle. As a result, every position remains aligned with defined risk parameters, ensuring consistent and enforceable structure across the entire account.
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.
From Novice to Expert: Creating an MTF CRT Overlay Indicator in MQL5
Building an Object-Oriented FVG Scanner in MQL5
Beyond the Clock (Part 1): Building Activity and Imbalance Bars in Python and MQL5
How to implement AutoARIMA forecasting in MQL5
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use