preview
Building a Prop Firm Compliance Monitor in MQL5 (Part 3): Daily Drawdown, Overall Drawdown, and Profit Targets

Building a Prop Firm Compliance Monitor in MQL5 (Part 3): Daily Drawdown, Overall Drawdown, and Profit Targets

MetaTrader 5 — Trading |
139 0
Chacha Ian Maroa
Chacha Ian Maroa

Introduction

Monitoring a prop firm account requires more than displaying balance and equity. The EA must determine how much of the permitted daily and overall loss has been used, how close the account is to a breach, how much progress has been made toward the profit target, and whether each of those calculations is actually valid when reported.

Part 2 established the persistent account settings and daily-session reference needed for this work. Part 3 now turns those stored references into runtime financial states. Daily drawdown uses the equity captured when the trader confirms the session baseline, overall drawdown uses the initial challenge balance under a static floor model, and profit target progress uses the current realized balance.

These references must remain separate because they answer different compliance questions. The daily rule belongs to the current trading day, while the overall rule remains anchored to the challenge configuration. The profit target is also evaluated independently because it measures realized growth rather than drawdown risk.

The models used here are deliberate choices for Prop Firm Guard, not universal prop firm formulas. Other firms may use different daily references, trailing or high-watermark drawdown, or equity-based target conditions.

Part 3 also introduces readiness-aware calculations. If a required reference is missing or invalid, the EA must report that the calculation is unavailable instead of treating it as safe.

By the end of this part, Prop Firm Guard will calculate and classify daily drawdown, static overall drawdown, and realized-balance profit target progress, then combine them into one readiness-aware account-level guard status.


Defining Readiness-Aware Financial States

Part 3 needs separate runtime structures for daily drawdown, overall drawdown, and profit target progress because each rule uses a different reference and produces a different set of calculated values. Before adding those structures, we first extend the account-level guard states so the EA can represent an incomplete financial picture explicitly.

Add the following enumeration immediately after ENUM_TARGET_STATUS:

//+------------------------------------------------------------------+
//| Overall guard status                                             |
//+------------------------------------------------------------------+
//| Represents the combined state of the Part 3 financial rules.     |
//+------------------------------------------------------------------+
enum ENUM_GUARD_STATUS
  {
   GUARD_NOT_READY = 0,          // At least one required financial rule is unavailable
   GUARD_SAFE,                   // All evaluated financial rules are within safe limits
   GUARD_TARGET_REACHED,         // Profit target is reached with no higher-priority risk
   GUARD_WARNING,                // At least one drawdown rule is in warning state
   GUARD_CRITICAL,               // At least one drawdown rule is in critical state
   GUARD_BREACHED                // At least one drawdown rule has been breached
  };

GUARD_NOT_READY is the important addition here. It gives the combined guard a way to distinguish an unavailable calculation from a valid safe result. Next, place the three financial-state structures immediately after SDailySession.

//+------------------------------------------------------------------+
//| Daily drawdown state                                             |
//+------------------------------------------------------------------+
//| Holds the current runtime result of the selected daily rule.     |
//+------------------------------------------------------------------+
struct SDailyDrawdownState
  {
   bool                    ready;                // Whether the daily rule can be evaluated
   double                  referenceEquity;      // Confirmed equity reference for the session
   double                  currentEquity;        // Current live account equity
   double                  maximumDailyLoss;     // Maximum permitted daily loss
   double                  dailyEquityFloor;     // Minimum permitted equity for the daily rule
   double                  currentDailyLoss;     // Current loss from the daily equity reference
   double                  drawdownPercent;      // Loss as a percentage of reference equity
   double                  limitUsagePercent;    // Percentage of daily allowance already used
   double                  remainingAllowance;   // Amount remaining before daily breach
   ENUM_COMPLIANCE_STATUS  status;               // Valid only when ready is true
  };

The daily structure introduces three fields that are especially important for the revised model. referenceEquity stores the confirmed equity baseline used by the daily calculation, dailyEquityFloor stores the minimum equity permitted by that selected rule, and ready indicates whether the calculation is currently valid. A COMPLIANCE_SAFE value is meaningful only when ready is true. If the session has not been confirmed or the reference is invalid, the calculation is unavailable rather than safe.

Add the overall state next:

//+------------------------------------------------------------------+
//| Overall drawdown state                                           |
//+------------------------------------------------------------------+
//| Holds the current runtime result of the static overall rule.     |
//+------------------------------------------------------------------+
struct SOverallDrawdownState
  {
   bool                    ready;                // Whether the overall rule can be evaluated
   double                  initialBalance;       // Fixed initial prop-firm account balance
   double                  currentEquity;        // Current live account equity
   double                  maximumOverallLoss;   // Maximum permitted overall loss
   double                  overallFloor;         // Static minimum permitted account equity
   double                  currentOverallLoss;   // Current loss from the initial balance
   double                  limitUsagePercent;    // Percentage of overall allowance already used
   double                  remainingAllowance;   // Amount remaining before overall breach
   ENUM_COMPLIANCE_STATUS  status;               // Valid only when ready is true
  };

This structure remains anchored to the initial challenge balance. Its overallFloor is static, so later account growth does not move that reference. The ready field again separates a valid calculated state from an unavailable one. Then add the profit target state:

//+------------------------------------------------------------------+
//| Profit target state                                              |
//+------------------------------------------------------------------+
//| Holds the current runtime result of the realized-balance target. |
//+------------------------------------------------------------------+
struct SProfitTargetState
  {
   bool                ready;              // Whether the target rule can be evaluated
   double              initialBalance;     // Fixed initial prop-firm account balance
   double              currentBalance;     // Current realized account balance
   double              targetAmount;       // Profit required in account currency
   double              targetBalance;      // Balance required to reach the target
   double              currentProfit;      // Realized profit above the initial balance
   double              progressPercent;    // Percentage of the target already achieved
   double              remainingProfit;    // Profit still required to reach the target
   ENUM_TARGET_STATUS  status;             // Valid only when ready is true
  };

The same readiness rule applies here. TARGET_IN_PROGRESS, TARGET_NEARING, and TARGET_REACHED are valid only after the target calculation has succeeded. If the initial balance or target amount is invalid, ready remains false and the status must not be interpreted.

Finally, replace the current global variables block with:

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
SAccountState         g_accountState;       // Latest monitored account state
SAccountSettings      g_accountSettings;    // Persisted prop-firm settings
SDailySession         g_dailySession;       // Current configured daily session
SDailyDrawdownState   g_dailyDrawdown;      // Current daily drawdown runtime state
SOverallDrawdownState g_overallDrawdown;    // Current overall drawdown runtime state
SProfitTargetState    g_profitTarget;       // Current profit-target runtime state

string g_startBalanceEdit = "PFG_StartBalanceEdit"; // Starting balance input field
string g_confirmButton    = "PFG_ConfirmBalance";   // Daily balance confirmation button
string g_databaseName     = "prop_firm_compliance.sqlite"; // SQLite database file
string g_instanceLockName = "";      // Account-specific terminal instance lock

int  g_database             = INVALID_HANDLE; // Active SQLite database handle
bool g_hasDailySession      = false;          // Whether a daily session is currently loaded
bool g_instanceLockAcquired = false;          // Whether this EA owns the instance lock

The distinction between these globals is important. g_accountSettings and g_dailySession contain state that originates from the persistence layer and can be restored after restart. By contrast, g_dailyDrawdown, g_overallDrawdown, and g_profitTarget are runtime calculation results. They are rebuilt from the stored references and the latest account values rather than stored as persistent records. This separation gives Part 3 a clear rule: persistence supplies the references, while the financial structures describe the latest calculated state.


Validating Financial Inputs and Classifying Loss Usage

Part 2 already validates the configured percentages and threshold ordering when the EA initializes, but the Part 3 calculation functions still need their own defensive checks. Runtime financial values can be unavailable, invalid, or unsuitable as denominators even when the original Inputs were valid. A monitoring function should therefore refuse to publish a financial status unless every value required by the calculation has passed validation.

The first helper provides a common test for financial references. Place IsValidFinancialReference() immediately after UpdateAccountState():

//+------------------------------------------------------------------+
//| Validate financial reference                                     |
//+------------------------------------------------------------------+
bool IsValidFinancialReference(const double value)
  {
//--- Financial references must be finite and strictly positive
   if(!MathIsValidNumber(value) ||
      value <= 0.0)
      return(false);

   return(true);
  }

This helper is intended for values that will become financial references or divisors, such as the confirmed daily equity, the initial challenge balance, or the maximum permitted loss. A zero or negative value is not interpreted as zero risk. It makes the corresponding calculation unavailable.

The next helper converts valid drawdown usage into a compliance state. Add ClassifyLossUsage() immediately after IsValidFinancialReference():

//+------------------------------------------------------------------+
//| Classify consumed loss allowance                                 |
//+------------------------------------------------------------------+
bool ClassifyLossUsage(const double             usagePercent,
                       ENUM_COMPLIANCE_STATUS  &status)
  {
//--- Reject invalid usage before assigning a compliance state
   if(!MathIsValidNumber(usagePercent) ||
      usagePercent < 0.0)
     {
      Print("Prop Firm Guard: Cannot classify an invalid loss usage value.");

      return(false);
     }

//--- Defend the classifier against invalid threshold configuration
   if(!MathIsValidNumber(InpWarningLevelPercent) ||
      !MathIsValidNumber(InpCriticalLevelPercent) ||
      InpWarningLevelPercent <= 0.0 ||
      InpWarningLevelPercent >= InpCriticalLevelPercent ||
      InpCriticalLevelPercent >= 100.0)
     {
      Print("Prop Firm Guard: Cannot classify loss usage with invalid warning thresholds.");

      return(false);
     }

//--- Evaluate the most severe condition first
   if(usagePercent >= 100.0)
     {
      status = COMPLIANCE_BREACHED;

      return(true);
     }

   if(usagePercent >= InpCriticalLevelPercent)
     {
      status = COMPLIANCE_CRITICAL;

      return(true);
     }

   if(usagePercent >= InpWarningLevelPercent)
     {
      status = COMPLIANCE_WARNING;

      return(true);
     }

   status = COMPLIANCE_SAFE;

   return(true);
  }

The order is important because the ranges overlap. A usage value of 100% is also above both the warning and critical thresholds, so the most severe condition must be checked first. The effective priority is therefore:

usage >= 100
    -> BREACHED

usage >= critical
    -> CRITICAL

usage >= warning
    -> WARNING

otherwise
    -> SAFE

The function returns false when the usage value or threshold configuration is invalid. This prevents an unusable denominator from being converted into 0% usage and then misclassified as SAFE. The three runtime states also need deterministic reset helpers. Add them immediately after ClassifyLossUsage():

//+------------------------------------------------------------------+
//| Reset daily drawdown state                                       |
//+------------------------------------------------------------------+
void ResetDailyDrawdownState()
  {
//--- Mark the complete daily calculation as unavailable
   g_dailyDrawdown.ready              = false;
   g_dailyDrawdown.referenceEquity    = 0.0;
   g_dailyDrawdown.currentEquity      = 0.0;
   g_dailyDrawdown.maximumDailyLoss   = 0.0;
   g_dailyDrawdown.dailyEquityFloor   = 0.0;
   g_dailyDrawdown.currentDailyLoss   = 0.0;
   g_dailyDrawdown.drawdownPercent    = 0.0;
   g_dailyDrawdown.limitUsagePercent  = 0.0;
   g_dailyDrawdown.remainingAllowance = 0.0;
  }

//+------------------------------------------------------------------+
//| Reset overall drawdown state                                     |
//+------------------------------------------------------------------+
void ResetOverallDrawdownState()
  {
//--- Mark the complete overall calculation as unavailable
   g_overallDrawdown.ready              = false;
   g_overallDrawdown.initialBalance     = 0.0;
   g_overallDrawdown.currentEquity      = 0.0;
   g_overallDrawdown.maximumOverallLoss = 0.0;
   g_overallDrawdown.overallFloor       = 0.0;
   g_overallDrawdown.currentOverallLoss = 0.0;
   g_overallDrawdown.limitUsagePercent  = 0.0;
   g_overallDrawdown.remainingAllowance = 0.0;
  }

//+------------------------------------------------------------------+
//| Reset profit target state                                        |
//+------------------------------------------------------------------+
void ResetProfitTargetState()
  {
//--- Mark the complete target calculation as unavailable
   g_profitTarget.ready            = false;
   g_profitTarget.initialBalance   = 0.0;
   g_profitTarget.currentBalance   = 0.0;
   g_profitTarget.targetAmount     = 0.0;
   g_profitTarget.targetBalance    = 0.0;
   g_profitTarget.currentProfit    = 0.0;
   g_profitTarget.progressPercent  = 0.0;
   g_profitTarget.remainingProfit  = 0.0;
  }

Each reset helper clears the calculated values and, most importantly, sets ready to false. The status field is not used to represent failure or missing data. A compliance or target status becomes meaningful only after the corresponding calculation completes successfully and sets ready to true.

This gives the Part 3 financial functions a consistent defensive pattern: reset first, validate every prerequisite, calculate only with valid denominators, classify only valid usage, and publish a ready result only at the end.


Calculating Daily Drawdown From Confirmed Equity

The daily loss rule needs a reference that remains stable for the current trading day and is measured on the same basis as the live value being monitored. For Prop Firm Guard, Part 3 uses a daily equity-reference model: the reference is the account equity captured when the trader confirms the daily baseline, and current risk is measured against live equity.

The model is:

Daily reference equity
= confirmed session starting equity

Maximum daily loss
= reference equity × daily drawdown % / 100

Daily equity floor
= reference equity − maximum daily loss

Current daily loss
= max(0, reference equity − current equity)

Drawdown %
= current daily loss / reference equity × 100

Limit usage
= current daily loss / maximum daily loss × 100

Remaining allowance
= max(0, maximum daily loss − current daily loss)

This avoids the earlier balance-versus-equity mismatch. A confirmed balance and current equity are not necessarily equivalent, especially when open positions already exist. Using startingEquity keeps the daily reference and the live measurement on the same basis. This is the model selected for Prop Firm Guard; other firms may define the daily rule from balance, reset equity, or another reference.

Place CalculateDailyDrawdown() immediately after ResetProfitTargetState():

//+------------------------------------------------------------------+
//| Calculate daily drawdown                                         |
//+------------------------------------------------------------------+
void CalculateDailyDrawdown()
  {
//--- Start from an explicitly unavailable calculation state
   ResetDailyDrawdownState();

//--- Require the current configured daily session
   if(!g_hasDailySession)
      return;

//--- The daily rule is valid only for an active confirmed session
   if(g_dailySession.status != SESSION_ACTIVE ||
      !g_dailySession.balanceConfirmed)
      return;

//--- Validate the equity reference captured at confirmation
   double referenceEquity = g_dailySession.startingEquity;

   if(!IsValidFinancialReference(referenceEquity))
     {
      Print("Prop Firm Guard: Daily drawdown cannot use an invalid reference equity.");

      return;
     }

//--- Defend against invalid daily rule configuration
   double dailyDrawdownPercent =
      g_accountSettings.dailyDrawdownPercent;

   if(!MathIsValidNumber(dailyDrawdownPercent) ||
      dailyDrawdownPercent <= 0.0 ||
      dailyDrawdownPercent > 100.0)
     {
      Print("Prop Firm Guard: Daily drawdown percentage is invalid at runtime.");

      return;
     }

//--- Current equity may be negative after a severe account loss
   double currentEquity = g_accountState.equity;

   if(!MathIsValidNumber(currentEquity))
     {
      Print("Prop Firm Guard: Current equity is invalid for daily drawdown calculation.");

      return;
     }

//--- Calculate the permitted daily loss from the equity reference
   double maximumDailyLoss =
      referenceEquity *
      dailyDrawdownPercent / 100.0;

   if(!IsValidFinancialReference(maximumDailyLoss))
     {
      Print("Prop Firm Guard: Calculated maximum daily loss is invalid.");

      return;
     }

//--- Derive the selected model's fixed daily equity floor
   double dailyEquityFloor =
      referenceEquity -
      maximumDailyLoss;

   if(!MathIsValidNumber(dailyEquityFloor))
     {
      Print("Prop Firm Guard: Calculated daily equity floor is invalid.");

      return;
     }

//--- Measure current equity loss from the confirmed equity reference
   double currentDailyLoss =
      referenceEquity -
      currentEquity;

   if(currentDailyLoss < 0.0)
      currentDailyLoss = 0.0;

   if(!MathIsValidNumber(currentDailyLoss))
     {
      Print("Prop Firm Guard: Calculated daily loss is invalid.");

      return;
     }

//--- Express the loss relative to the session equity reference
   double drawdownPercent =
      currentDailyLoss /
      referenceEquity * 100.0;

//--- Express the loss as consumption of the permitted daily allowance
   double limitUsagePercent =
      currentDailyLoss /
      maximumDailyLoss * 100.0;

   if(!MathIsValidNumber(drawdownPercent) ||
      !MathIsValidNumber(limitUsagePercent))
     {
      Print("Prop Firm Guard: Calculated daily drawdown percentages are invalid.");

      return;
     }

//--- Preserve zero usage when equity remains above the reference
   if(drawdownPercent < 0.0)
      drawdownPercent = 0.0;

   if(limitUsagePercent < 0.0)
      limitUsagePercent = 0.0;

//--- Calculate money still available before the daily floor is reached
   double remainingAllowance =
      maximumDailyLoss -
      currentDailyLoss;

   if(remainingAllowance < 0.0)
      remainingAllowance = 0.0;

   if(!MathIsValidNumber(remainingAllowance))
     {
      Print("Prop Firm Guard: Calculated daily remaining allowance is invalid.");

      return;
     }

//--- Classify only after all required financial values are valid
   ENUM_COMPLIANCE_STATUS status;

   if(!ClassifyLossUsage(limitUsagePercent,
                         status))
      return;

//--- Publish the complete calculated state only after validation succeeds
   g_dailyDrawdown.referenceEquity    = referenceEquity;
   g_dailyDrawdown.currentEquity      = currentEquity;
   g_dailyDrawdown.maximumDailyLoss   = maximumDailyLoss;
   g_dailyDrawdown.dailyEquityFloor   = dailyEquityFloor;
   g_dailyDrawdown.currentDailyLoss   = currentDailyLoss;
   g_dailyDrawdown.drawdownPercent    = drawdownPercent;
   g_dailyDrawdown.limitUsagePercent  = limitUsagePercent;
   g_dailyDrawdown.remainingAllowance = remainingAllowance;
   g_dailyDrawdown.status             = status;
   g_dailyDrawdown.ready              = true;
  }

The function begins by resetting the runtime state, so any early return leaves the daily calculation unavailable. It then requires a loaded session, an active lifecycle state, confirmed baseline data, and a valid positive startingEquity. The configured daily percentage, current equity, and resulting maximum permitted loss are also validated before any classification takes place.

The derived values are calculated in local variables first. This prevents partially updated global state if one of the later checks fails. Only after the usage percentage has been classified successfully are the calculated values copied into g_dailyDrawdown, with ready set to true last.

For example, with a confirmed reference equity of 20,000 and a daily limit of 5%, the maximum daily loss is 1,000 and the daily equity floor is 19,000. If current equity falls to 19,300, the current daily loss is 700, limit usage is 70%, and the result enters the warning range when the default warning threshold is 70%.


Calculating Static Overall Drawdown

Overall drawdown uses a different reference from the daily rule. Instead of resetting with the current trading day, it remains anchored to the initial challenge balance stored in g_accountSettings.initialBalance. The selected model is:

Initial reference
= initial challenge balance

Maximum overall loss
= initial balance × overall drawdown % / 100

Static overall equity floor
= initial balance − maximum overall loss

Current overall loss
= max(0, initial balance − current equity)

Limit usage
= current overall loss / maximum overall loss × 100

Remaining allowance
= max(0, maximum overall loss − current overall loss)

For example, with an initial challenge balance of 20,000 and an overall drawdown limit of 10%, the maximum permitted overall loss is 2,000. The resulting static equity floor is therefore 18,000. Add CalculateOverallDrawdown() immediately after CalculateDailyDrawdown():

//+------------------------------------------------------------------+
//| Calculate overall drawdown                                       |
//+------------------------------------------------------------------+
void CalculateOverallDrawdown()
  {
//--- Start from an explicitly unavailable calculation state
   ResetOverallDrawdownState();

//--- Validate the fixed initial account reference
   double initialBalance = g_accountSettings.initialBalance;

   if(!IsValidFinancialReference(initialBalance))
     {
      Print("Prop Firm Guard: Overall drawdown cannot use an invalid initial balance.");

      return;
     }

//--- Defend against invalid overall rule configuration
   double overallDrawdownPercent =
      g_accountSettings.overallDrawdownPercent;

   if(!MathIsValidNumber(overallDrawdownPercent) ||
      overallDrawdownPercent <= 0.0 ||
      overallDrawdownPercent > 100.0)
     {
      Print("Prop Firm Guard: Overall drawdown percentage is invalid at runtime.");

      return;
     }

//--- Current equity may become negative after a severe account loss
   double currentEquity = g_accountState.equity;

   if(!MathIsValidNumber(currentEquity))
     {
      Print("Prop Firm Guard: Current equity is invalid for overall drawdown calculation.");

      return;
     }

//--- Calculate the maximum permitted static overall loss
   double maximumOverallLoss =
      initialBalance *
      overallDrawdownPercent / 100.0;

   if(!IsValidFinancialReference(maximumOverallLoss))
     {
      Print("Prop Firm Guard: Calculated maximum overall loss is invalid.");

      return;
     }

//--- Derive the static minimum equity permitted by this rule model
   double overallFloor =
      initialBalance -
      maximumOverallLoss;

   if(!MathIsValidNumber(overallFloor))
     {
      Print("Prop Firm Guard: Calculated overall equity floor is invalid.");

      return;
     }

//--- Measure current equity loss from the fixed initial balance
   double currentOverallLoss =
      initialBalance -
      currentEquity;

   if(currentOverallLoss < 0.0)
      currentOverallLoss = 0.0;

   if(!MathIsValidNumber(currentOverallLoss))
     {
      Print("Prop Firm Guard: Calculated overall loss is invalid.");

      return;
     }

//--- Express current loss as consumption of the overall allowance
   double limitUsagePercent =
      currentOverallLoss /
      maximumOverallLoss * 100.0;

   if(!MathIsValidNumber(limitUsagePercent))
     {
      Print("Prop Firm Guard: Calculated overall limit usage is invalid.");

      return;
     }

   if(limitUsagePercent < 0.0)
      limitUsagePercent = 0.0;

//--- Calculate money still available before the static floor is reached
   double remainingAllowance =
      maximumOverallLoss -
      currentOverallLoss;

   if(remainingAllowance < 0.0)
      remainingAllowance = 0.0;

   if(!MathIsValidNumber(remainingAllowance))
     {
      Print("Prop Firm Guard: Calculated overall remaining allowance is invalid.");

      return;
     }

//--- Classify only after all required financial values are valid
   ENUM_COMPLIANCE_STATUS status;

   if(!ClassifyLossUsage(limitUsagePercent,
                         status))
      return;

//--- Publish the complete calculated state only after validation succeeds
   g_overallDrawdown.initialBalance     = initialBalance;
   g_overallDrawdown.currentEquity      = currentEquity;
   g_overallDrawdown.maximumOverallLoss = maximumOverallLoss;
   g_overallDrawdown.overallFloor       = overallFloor;
   g_overallDrawdown.currentOverallLoss = currentOverallLoss;
   g_overallDrawdown.limitUsagePercent  = limitUsagePercent;
   g_overallDrawdown.remainingAllowance = remainingAllowance;
   g_overallDrawdown.status             = status;
   g_overallDrawdown.ready              = true;
  }

The function follows the same defensive pattern used by the daily calculation. It resets the runtime state first, validates the fixed reference and percentage, calculates the permitted loss and equity floor, then classifies usage only after every required value is valid. ready is set to true only after the complete state has been published.

The important difference is the reference itself. Daily drawdown can change when a new trading day begins and a new session equity is confirmed. Overall drawdown does not. Under this model, the initial challenge balance remains fixed, so the equity floor also remains fixed even if the account later grows.

This article implements a static overall equity floor. It does not implement trailing drawdown, end-of-day trailing drawdown, balance high-watermark, or equity high-watermark rules. Those models require different reference-update logic and should not be treated as equivalent to the implementation used here.


Tracking the Realized-Balance Profit Target

The profit target answers a different question from the two drawdown rules. Instead of measuring how much loss allowance has been consumed, it measures how much realized account growth has been achieved relative to the initial challenge balance. For Prop Firm Guard, Part 3 uses a realized-balance target model. The relationships are:

Target amount
= initial balance × profit target % / 100

Target balance
= initial balance + target amount

Current realized profit
= max(0, current balance − initial balance)

Progress
= current realized profit / target amount × 100

Remaining profit
= max(0, target amount − current realized profit)

For example, an initial challenge balance of 20,000 with an 8% profit target requires 1,600 in realized profit. The target balance is therefore 21,600. Add CalculateProfitTarget() immediately after CalculateOverallDrawdown():

//+------------------------------------------------------------------+
//| Calculate profit target                                          |
//+------------------------------------------------------------------+
void CalculateProfitTarget()
  {
//--- Start from an explicitly unavailable calculation state
   ResetProfitTargetState();

//--- Validate the fixed initial account reference
   double initialBalance = g_accountSettings.initialBalance;

   if(!IsValidFinancialReference(initialBalance))
     {
      Print("Prop Firm Guard: Profit target cannot use an invalid initial balance.");

      return;
     }

//--- Defend against invalid profit target configuration
   double profitTargetPercent =
      g_accountSettings.profitTargetPercent;

   if(!MathIsValidNumber(profitTargetPercent) ||
      profitTargetPercent <= 0.0 ||
      profitTargetPercent > 100.0)
     {
      Print("Prop Firm Guard: Profit target percentage is invalid at runtime.");

      return;
     }

//--- Read the current realized account balance
   double currentBalance = g_accountState.balance;

   if(!MathIsValidNumber(currentBalance))
     {
      Print("Prop Firm Guard: Current balance is invalid for profit target calculation.");

      return;
     }

//--- Calculate the required realized profit
   double targetAmount =
      initialBalance *
      profitTargetPercent / 100.0;

   if(!IsValidFinancialReference(targetAmount))
     {
      Print("Prop Firm Guard: Calculated profit target amount is invalid.");

      return;
     }

//--- Derive the realized balance required to reach the target
   double targetBalance =
      initialBalance +
      targetAmount;

   if(!MathIsValidNumber(targetBalance))
     {
      Print("Prop Firm Guard: Calculated target balance is invalid.");

      return;
     }

//--- Measure realized profit above the initial challenge balance
   double currentProfit =
      currentBalance -
      initialBalance;

   if(currentProfit < 0.0)
      currentProfit = 0.0;

   if(!MathIsValidNumber(currentProfit))
     {
      Print("Prop Firm Guard: Calculated realized profit is invalid.");

      return;
     }

//--- Express realized profit as completion of the required target
   double progressPercent =
      currentProfit /
      targetAmount * 100.0;

   if(!MathIsValidNumber(progressPercent))
     {
      Print("Prop Firm Guard: Calculated profit target progress is invalid.");

      return;
     }

   if(progressPercent < 0.0)
      progressPercent = 0.0;

   if(progressPercent > 100.0)
      progressPercent = 100.0;

//--- Calculate realized profit still required to reach the target
   double remainingProfit =
      targetAmount -
      currentProfit;

   if(remainingProfit < 0.0)
      remainingProfit = 0.0;

   if(!MathIsValidNumber(remainingProfit))
     {
      Print("Prop Firm Guard: Calculated remaining profit is invalid.");

      return;
     }

//--- Classify the valid realized-balance target state
   ENUM_TARGET_STATUS status;

   if(currentBalance >= targetBalance)
      status = TARGET_REACHED;
   else if(progressPercent >= 90.0)
      status = TARGET_NEARING;
   else
      status = TARGET_IN_PROGRESS;

//--- Publish the complete calculated state only after validation succeeds
   g_profitTarget.initialBalance   = initialBalance;
   g_profitTarget.currentBalance   = currentBalance;
   g_profitTarget.targetAmount     = targetAmount;
   g_profitTarget.targetBalance    = targetBalance;
   g_profitTarget.currentProfit    = currentProfit;
   g_profitTarget.progressPercent  = progressPercent;
   g_profitTarget.remainingProfit  = remainingProfit;
   g_profitTarget.status           = status;
   g_profitTarget.ready            = true;
  }

As with the drawdown functions, the target state is reset before any calculation begins. The initial balance, configured target percentage, current balance, and resulting target amount must all be valid before the function can publish a result.

The validation of targetAmount is especially important. A zero or invalid target amount does not mean that the target has been achieved. It makes the calculation unavailable, so ready remains false. This prevents a contradictory state in which progress is reported as 0% while the account is also classified as TARGET_REACHED.

Once the calculation is ready, the target status follows three levels:

Progress below 90%
    -> TARGET_IN_PROGRESS

Progress at or above 90%, but balance below target
    -> TARGET_NEARING

Current balance at or above target balance
    -> TARGET_REACHED

Progress is capped at 100%, and remainingProfit cannot become negative after the target has been exceeded. Under the model selected here, floating equity does not determine target completion because the calculation uses the current realized balance. This should not be interpreted as a universal prop firm rule. Some firms may use equity-based targets or require open positions to be closed before completion is recognized.


Building the Readiness-Aware Overall Guard

The three financial engines developed so far answer different questions. CalculateDailyDrawdown() evaluates the current trading-day loss, CalculateOverallDrawdown() measures the account against the static challenge floor, and CalculateProfitTarget() measures realized progress toward the objective.

Those results are useful individually, but a monitoring system also needs a concise account-level condition. The difficulty is that the three states cannot simply be treated as equals. A reached profit target should not hide a warning or breach, and an unavailable calculation should not be mistaken for a safe one.

Before building that account-level guard, we first add small conversion helpers so the enum values can later be presented consistently in logs, dashboards, and alerts. Place ComplianceStatusToString() immediately after CalculateProfitTarget():

//+------------------------------------------------------------------+
//| Convert compliance status to text                                |
//+------------------------------------------------------------------+
string ComplianceStatusToString(const ENUM_COMPLIANCE_STATUS status)
  {
//--- Convert only valid calculated compliance states
   switch(status)
     {
      case COMPLIANCE_WARNING:
         return("WARNING");

      case COMPLIANCE_CRITICAL:
         return("CRITICAL");

      case COMPLIANCE_BREACHED:
         return("BREACHED");

      case COMPLIANCE_SAFE:
         return("SAFE");
     }

   return("UNKNOWN");
  }

ComplianceStatusToString() does not perform any financial calculation. Its purpose is to keep the internal enum representation separate from the text shown to the user. This becomes useful later because the same status may need to appear in the Experts tab, a dashboard, or a notification without repeating the same switch logic in several places. The fallback is deliberately UNKNOWN. Returning SAFE for an unexpected enum value would incorrectly convert an invalid state into a reassuring result.

Add TargetStatusToString() immediately afterward:

//+------------------------------------------------------------------+
//| Convert target status to text                                    |
//+------------------------------------------------------------------+
string TargetStatusToString(const ENUM_TARGET_STATUS status)
  {
//--- Convert only valid calculated profit-target states
   switch(status)
     {
      case TARGET_NEARING:
         return("NEARING TARGET");

      case TARGET_REACHED:
         return("TARGET REACHED");

      case TARGET_IN_PROGRESS:
         return("IN PROGRESS");
     }

   return("UNKNOWN");
  }

This helper performs the same presentation role for the profit target. The target engine has its own enum because its states describe progress rather than risk. Keeping the two enum families separate prevents concepts such as TARGET_REACHED from being mixed into the drawdown classifier. The account-level decision can now be introduced. Place GetOverallGuardStatus() immediately after TargetStatusToString():

//+------------------------------------------------------------------+
//| Get overall guard status                                         |
//+------------------------------------------------------------------+
ENUM_GUARD_STATUS GetOverallGuardStatus()
  {
//--- Do not report a complete guard state while any rule is unavailable
   if(!g_dailyDrawdown.ready ||
      !g_overallDrawdown.ready ||
      !g_profitTarget.ready)
      return(GUARD_NOT_READY);

//--- Give highest financial priority to any drawdown breach
   if(g_dailyDrawdown.status == COMPLIANCE_BREACHED ||
      g_overallDrawdown.status == COMPLIANCE_BREACHED)
      return(GUARD_BREACHED);

//--- Check for a critical drawdown condition
   if(g_dailyDrawdown.status == COMPLIANCE_CRITICAL ||
      g_overallDrawdown.status == COMPLIANCE_CRITICAL)
      return(GUARD_CRITICAL);

//--- Check for a warning drawdown condition
   if(g_dailyDrawdown.status == COMPLIANCE_WARNING ||
      g_overallDrawdown.status == COMPLIANCE_WARNING)
      return(GUARD_WARNING);

//--- Report target completion only after all risk rules are available
   if(g_profitTarget.status == TARGET_REACHED)
      return(GUARD_TARGET_REACHED);

//--- All required calculations are ready and no higher state applies
   return(GUARD_SAFE);
  }

GetOverallGuardStatus() is the point where the three independent financial engines become one account-level view. It does not recalculate the underlying values. Instead, it reads the latest runtime states and resolves them according to a fixed priority.

The first decision is readiness. This must be checked before any individual status is consumed because a status field is meaningful only when its corresponding calculation has completed successfully. If even one required engine is unavailable, the combined result becomes GUARD_NOT_READY.

For example:

Daily     = NOT READY
Overall   = SAFE
Target    = REACHED

Guard     = NOT READY

Without the readiness check, the account could incorrectly appear to have completed the target even though the daily loss rule has not been evaluated.

Once all three engines are ready, risk takes priority over achievement. A breach is more important than a critical state, a critical state is more important than a warning, and every active drawdown condition takes priority over TARGET_REACHED.

The complete order is therefore:

GUARD_NOT_READY
GUARD_BREACHED
GUARD_CRITICAL
GUARD_WARNING
GUARD_TARGET_REACHED
GUARD_SAFE

Consider another case:

Daily     = CRITICAL
Overall   = SAFE
Target    = REACHED

The result is GUARD_CRITICAL. The reached target remains true inside the profit-target state, but it does not become the account-level result while a more important risk condition exists. Finally, add GuardStatusToString() immediately after GetOverallGuardStatus():

//+------------------------------------------------------------------+
//| Convert guard status to text                                     |
//+------------------------------------------------------------------+
string GuardStatusToString(const ENUM_GUARD_STATUS status)
  {
//--- Convert the complete account-level guard state
   switch(status)
     {
      case GUARD_NOT_READY:
         return("NOT READY");

      case GUARD_TARGET_REACHED:
         return("TARGET REACHED");

      case GUARD_WARNING:
         return("WARNING");

      case GUARD_CRITICAL:
         return("CRITICAL");

      case GUARD_BREACHED:
         return("BREACHED");

      case GUARD_SAFE:
         return("SAFE");
     }

   return("UNKNOWN");
  }

This final helper provides the readable form of the combined guard result. Like the other conversion functions, it returns UNKNOWN for an unexpected enum value instead of converting an invalid value into SAFE.

At this point, the financial architecture has three distinct layers. The calculation functions produce individual runtime states, the ready flags determine whether those states may be trusted, and GetOverallGuardStatus() combines the valid results into one account-level condition.


Integrating Financial Calculations Into the Runtime Lifecycle

The three financial engines are now complete, but they still need to be placed correctly inside the EA lifecycle. Their calculations depend on live account values, persisted settings, and the current daily session, so execution order matters just as much as the formulas themselves.

Part 3 integrates the financial layer at three points: immediately after a daily baseline is confirmed, during EA initialization, and during each timer-driven refresh. Together, these points ensure that the runtime states are reconstructed when the EA starts, refreshed immediately after confirmation, and then updated periodically as account values change.

Refreshing Financial State After Daily Confirmation

The first integration point is ConfirmDailyStartingBalance(). Part 2 already validates the entered balance, refreshes the account state, captures the confirmation values, and persists the daily session. Part 3 extends that workflow so the financial calculations are refreshed immediately after the confirmed session has been stored.

Replace the existing ConfirmDailyStartingBalance() function with the following complete version:

//+------------------------------------------------------------------+
//| Confirm daily starting balance                                   |
//+------------------------------------------------------------------+
bool ConfirmDailyStartingBalance()
  {
//--- Require an initialized daily session
   if(!g_hasDailySession)
     {
      Print("Prop Firm Guard: Cannot confirm a starting balance without a daily session.");

      return(false);
     }

//--- Accept confirmation only while the session is awaiting it
   if(g_dailySession.status != SESSION_AWAITING_CONFIRMATION)
     {
      Print("Prop Firm Guard: Current daily session is not awaiting balance confirmation.");

      return(false);
     }

//--- Protect an already confirmed baseline from accidental overwrite
   if(g_dailySession.balanceConfirmed)
     {
      Print("Prop Firm Guard: Daily starting balance is already confirmed.");

      return(false);
     }

//--- Require the starting balance field to exist
   if(ObjectFind(0,
                 g_startBalanceEdit) < 0)
     {
      Print("Prop Firm Guard: Starting balance field is not available.");

      return(false);
     }

//--- Read the complete text entered by the trader
   string balanceText;

   ResetLastError();

   if(!ObjectGetString(0,
                       g_startBalanceEdit,
                       OBJPROP_TEXT,
                       0,
                       balanceText))
     {
      Print("Prop Firm Guard: Could not read starting balance input. Error: ",
            GetLastError());

      return(false);
     }

//--- Validate the complete input before changing session state
   double startingBalance = 0.0;

   if(!ParseStartingBalance(balanceText,
                            startingBalance))
     {
      Print("Prop Firm Guard: Starting balance must be a valid positive monetary value.");

      return(false);
     }

//--- Preserve the previous session state for persistence rollback
   SDailySession previousSession = g_dailySession;

//--- Refresh live account values immediately before capturing equity
   UpdateAccountState();

//--- Convert the waiting session into a confirmed active session
   g_dailySession.startingBalance  = startingBalance;
   g_dailySession.startingEquity   = g_accountState.equity;
   g_dailySession.balanceConfirmed = true;
   g_dailySession.status           = SESSION_ACTIVE;
   g_dailySession.updatedAt        = TimeCurrent();

//--- Persist the confirmed state before exposing dependent calculations
   if(!SaveDailySession())
     {
      //--- Restore the previous in-memory state after persistence failure
      g_dailySession = previousSession;

      Print("Prop Firm Guard: Starting balance confirmation was not saved. "
            "The previous session state has been restored.");

      return(false);
     }

//--- Refresh all financial states from the persisted session baseline
   CalculateDailyDrawdown();
   CalculateOverallDrawdown();
   CalculateProfitTarget();

//--- Report an unavailable financial state without reversing persistence
   if(!g_dailyDrawdown.ready ||
      !g_overallDrawdown.ready ||
      !g_profitTarget.ready)
     {
      Print("Prop Firm Guard: Starting balance was confirmed and saved, "
            "but one or more financial calculations are not ready.");
     }

//--- Format the confirmed monetary values for reporting
   string formattedBalance;
   string formattedEquity;

   if(!FormatAccountMoney(g_dailySession.startingBalance,
                          formattedBalance))
      formattedBalance = DoubleToString(g_dailySession.startingBalance);

   if(!FormatAccountMoney(g_dailySession.startingEquity,
                          formattedEquity))
      formattedEquity = DoubleToString(g_dailySession.startingEquity);

//--- Synchronize presentation only after persistence succeeds
   if(!SynchronizeDailySessionControls())
     {
      Print("Prop Firm Guard: Starting balance was confirmed and saved, "
            "but the confirmation controls could not be synchronized.");

      return(true);
     }

   Print("Prop Firm Guard: Daily starting balance confirmed at ",
         formattedBalance,
         ". Equity captured at confirmation: ",
         formattedEquity,
         ".");

   return(true);
  }

The important change appears after SaveDailySession() succeeds. At that point, the confirmed startingBalance, captured startingEquity, and active session status have already become persistent state. Only then do the three financial engines rebuild their runtime results.

The resulting sequence is:

refresh account
capture starting balance and starting equity
activate daily session
persist confirmed session
recalculate financial states
synchronize controls

This order prevents the calculations from using a daily reference that has not yet been successfully stored. It also removes the previous delay in which confirmation could succeed while the daily drawdown state remained unavailable until the next timer event.

The Part 2 persistence rule still applies. Once SaveDailySession() succeeds, the persisted record is authoritative. If control synchronization later fails, the function reports the UI problem but still returns true; the stored confirmation is not rolled back merely because chart objects could not be updated.

Reconstructing the Financial State in OnInit()

The second integration point is initialization. The Part 3 runtime structures are not stored in SQLite, so they must be reconstructed whenever the EA is attached or the terminal restarts.

The calculations cannot run immediately at the start of OnInit(). They first need a current account snapshot, the stored account settings, and the daily session for the current trading day. Replace the complete existing OnInit() function with:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Validate the complete input configuration
   if(!ValidateComplianceInputs())
      return(INIT_PARAMETERS_INCORRECT);

//--- Refresh the account identity and current financial state
   UpdateAccountState();

//--- Enforce one Prop Firm Guard instance for the trading account
   if(!AcquireInstanceLock())
      return(INIT_FAILED);

//--- Open the SQLite persistence layer
   if(!OpenDatabase())
     {
      Print("Prop Firm Guard: Database initialization failed.");

      if(!ReleaseInstanceLock())
         Print("Prop Firm Guard: Instance lock cleanup was incomplete.");

      return(INIT_FAILED);
     }

//--- Ensure all persistence tables required through Part 2 exist
   if(!EnsureDatabaseTables())
     {
      Print("Prop Firm Guard: Could not initialize database tables.");

      CloseDatabase();

      if(!ReleaseInstanceLock())
         Print("Prop Firm Guard: Instance lock cleanup was incomplete.");

      return(INIT_FAILED);
     }

//--- Restore and synchronize the account settings
   if(!InitializeAccountSettings())
     {
      Print("Prop Firm Guard: Could not initialize account settings.");

      CloseDatabase();

      if(!ReleaseInstanceLock())
         Print("Prop Firm Guard: Instance lock cleanup was incomplete.");

      return(INIT_FAILED);
     }

//--- Restore or create the current configured daily session
   if(!InitializeDailySession())
     {
      Print("Prop Firm Guard: Could not initialize daily session.");

      CloseDatabase();

      if(!ReleaseInstanceLock())
         Print("Prop Firm Guard: Instance lock cleanup was incomplete.");

      return(INIT_FAILED);
     }

//--- Build the initial Part 3 financial runtime state
   CalculateDailyDrawdown();
   CalculateOverallDrawdown();
   CalculateProfitTarget();

//--- Match the confirmation controls to the current session state
   if(!SynchronizeDailySessionControls())
     {
      Print("Prop Firm Guard: Could not synchronize daily session controls.");

      if(!DeleteDailySessionControls())
         Print("Prop Firm Guard: Daily session control cleanup was incomplete.");

      CloseDatabase();

      if(!ReleaseInstanceLock())
         Print("Prop Firm Guard: Instance lock cleanup was incomplete.");

      return(INIT_FAILED);
     }

//--- Start periodic session maintenance and financial monitoring
   ResetLastError();

   if(!EventSetTimer(InpTimerSeconds))
     {
      Print("Prop Firm Guard: Could not start the session timer. Error: ",
            GetLastError());

      if(!DeleteDailySessionControls())
         Print("Prop Firm Guard: Daily session control cleanup was incomplete.");

      CloseDatabase();

      if(!ReleaseInstanceLock())
         Print("Prop Firm Guard: Instance lock cleanup was incomplete.");

      return(INIT_FAILED);
     }

   Print("Prop Firm Guard initialized successfully.");

   return(INIT_SUCCEEDED);
  }

The dependency order is deliberate. The EA first obtains the live account state, then restores the persistent settings and daily session, and only after those prerequisites exist does it calculate the Part 3 financial states. The controls are synchronized afterward, and the timer starts only after initialization has completed successfully.

An unconfirmed daily session is not an initialization error. In that case, CalculateDailyDrawdown() resets its state and leaves ready == false, while overall drawdown and profit target calculations can still become ready because their references come from g_accountSettings.

If the current daily session was already confirmed before the EA restarted, its persisted startingEquity is restored before the financial functions run. The daily calculation can therefore be reconstructed immediately from the stored reference and the latest account equity without persisting the calculated state itself.

Adding Permanent Periodic Recalculation

The third integration point is OnTimer(). This becomes the permanent Part 3 update path for live financial values. Replace the complete current OnTimer() function with:

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- Refresh the account snapshot used by the financial engines
   UpdateAccountState();

//--- Maintain the configured daily session boundary before calculation
   if(!CheckForNewTradingDay())
     {
      Print("Prop Firm Guard: Daily session maintenance could not be completed.");

      return;
     }

//--- Keep chart controls aligned with the authoritative session state
   if(!SynchronizeDailySessionControls())
     {
      Print("Prop Firm Guard: Daily session controls could not be synchronized.");
     }

//--- Recalculate the current Part 3 financial state
   CalculateDailyDrawdown();
   CalculateOverallDrawdown();
   CalculateProfitTarget();
  }

The order here is especially important for the daily rule. UpdateAccountState() first refreshes the balance and equity used by the calculations. CheckForNewTradingDay() then determines whether the current session reference still belongs to the active trading day.

If rollover creates or restores a different session, that transition must happen before CalculateDailyDrawdown() runs. Otherwise, the EA could briefly calculate the new day's risk against the previous day's confirmed equity.

After rollover succeeds, SynchronizeDailySessionControls() updates the confirmation interface. A newly created unconfirmed session will then cause CalculateDailyDrawdown() to remain not ready, while the overall and profit target engines continue to refresh normally.

Control synchronization is deliberately not allowed to stop the financial calculations. A chart-object failure affects presentation, not the authoritative account or session data. By contrast, a failed rollover causes the timer cycle to return because the EA cannot safely assume that the active daily reference represents the current trading day.

From this point forward, the three financial calculations remain permanently integrated into OnTimer(). Any temporary diagnostic code added during later testing should be appended to this runtime flow rather than replacing it with an older timer implementation.

The correct description of this behavior is periodic recalculation. The financial state is refreshed whenever the timer event runs, so it may briefly differ from the live account between timer invocations. It should therefore not be described as continuous monitoring.


Scope and Limitations

Part 3 deliberately implements a specific set of financial rule models rather than attempting to represent every prop firm configuration. The daily rule uses an equity reference captured when the trader confirms the session baseline, the overall rule uses a static floor derived from the initial challenge balance, and the profit target uses realized balance.

These choices make the calculation logic explicit and testable, but they also define the limits of the implementation. The daily model does not cover firms that reset from another balance or equity reference. The overall model does not implement trailing drawdown, end-of-day trailing drawdown, balance high-watermark, or equity high-watermark rules. The profit target does not cover firms that evaluate completion from equity or require all positions to be closed before a target is accepted.

The current calculations also use account balance and equity directly. They do not attempt to normalize deposits, withdrawals, bonuses, credits, balance adjustments, commissions, swaps, or other non-trading transactions. If such operations are possible on the monitored account, they can change the raw account values and therefore affect the calculated drawdown or target progress. A production implementation intended for those environments would need additional transaction classification and adjustment logic before the same formulas could be relied on without qualification.

The financial state itself is also runtime state. The database preserves the settings and daily-session references needed to reconstruct it, but the calculated drawdown and target structures are rebuilt in memory from those references and the latest account values. The timer periodically refreshes that state, so the values may differ from the live account between timer invocations.

These limitations are intentional. Part 3 establishes a clear and internally consistent financial monitoring layer for one defined rule set, while leaving alternative prop firm models and account-normalization logic for later extension.


Conclusion

Part 3 turns the persistence and daily-session foundation from Part 2 into a working financial compliance engine. Prop Firm Guard can now determine whether each required financial calculation is available, measure daily and overall loss usage, track the remaining allowance before breach, calculate realized profit target progress, and combine the valid results into one readiness-aware guard status.

The daily calculation now uses confirmed session equity as its reference, which keeps the daily comparison on an equity-to-equity basis. The overall calculation remains anchored to the initial challenge balance under the selected static floor model, while the profit target follows realized balance. Each engine validates its own prerequisites before publishing a result, so an unavailable or invalid calculation is no longer allowed to appear as SAFE.

The account-level guard adds another layer of protection by checking readiness before financial severity. When all three engines are available, breach, critical, and warning conditions take priority over profit target completion. When any required calculation is unavailable, the combined result becomes GUARD_NOT_READY instead of presenting an incomplete account state as valid.

The runtime integration is also complete. A confirmed daily baseline triggers an immediate recalculation after persistence succeeds, initialization reconstructs the financial states from stored references, and the timer periodically rebuilds the current financial picture from the latest account snapshot after session rollover has been processed.

With the financial rule layer established, the next part can extend Prop Firm Guard beyond account-level limits. Part 4 will introduce economic-calendar awareness and begin adding news-related trading restrictions to the monitoring workflow.


Attachments

The attachment for this article is PropFirmGuard.mq5, containing the revised Part 3 source code. If your implementation does not compile correctly or the daily drawdown, static overall drawdown, profit target, readiness handling, or overall guard behavior differs from what was demonstrated, you can download the file and compare it with your current version. The revised Part 3 source has also been published as an Algo Forge release:

https://forge.mql5.io/CHACHAIAN/PropFirmGuard/releases/tag/v1.1-part-3

Attached files |
PropFirmGuard.mq5 (90.07 KB)
Bimodal Market Profile with Delta and Memory in MQL5 Bimodal Market Profile with Delta and Memory in MQL5
The classic Market Profile from forty years ago is still replicated in dozens of indicators that differ only in the color of their bars. In this article, I examine three conceptual blind spots in the original theory — the monolithic Value Area in bimodal distributions, TPO’s blindness to the aggressor, and the lack of memory between sessions — and build an indicator that addresses each of them: bimodality detection with a dead zone, order flow via CopyTicksRange with absorption detection, and composite market memory using Naked POC and HVN/LVN. The complete source code is included.
Offline Reinforcement Learning in MQL5: Conservative Q-Learning from Historical Trade Logs Without Live Exploration Offline Reinforcement Learning in MQL5: Conservative Q-Learning from Historical Trade Logs Without Live Exploration
Build and deploy a conservative, offline‑trained policy entirely in MQL5. Logged trades become a fixed dataset for a two‑layer Q‑network trained with Conservative Q‑Learning, which pushes down Q‑values for poorly supported actions to limit extrapolation error. The EA loads the frozen weights and applies an out‑of‑distribution guard using saved feature statistics. This yields reproducible training and execution without live exploration.
Building A Candlestick Trend Constraint Model (Part 11): Revealing Lower-Timeframe Triggers Within Higher-Timeframe Candle Relationships Building A Candlestick Trend Constraint Model (Part 11): Revealing Lower-Timeframe Triggers Within Higher-Timeframe Candle Relationships
This article develops an A-B-C-D candlestick constraint model for MetaTrader 5. Candles A and B establish a completed D1 relationship, Candle C supplies a historical M1 setup, and closed M1 candles inside the developing Candle D either confirm or invalidate the sequence. We implement the design as a visual Expert Advisor with optional trading disabled by default, sequence-specific chart objects, restart-safe history reconstruction, spread and position gates, normalized trade parameters, and a reproducible testing plan.
Automating Classic Market Methods in MQL5 (Part 9): Linda Bradford Raschke's 3/10 Oscillator System Automating Classic Market Methods in MQL5 (Part 9): Linda Bradford Raschke's 3/10 Oscillator System
The article implements two swing setups in MQL5: Anti, which enters on the first 3/10 oscillator pullback after a signal line zero confirmation with price confirmation, and Holy Grail, which enters on the first pullback to the 20 EMA in a strong rising ADX trend. A single EA runs two independent state machines with precise entry and exit rules, ATR-based stops, and state recovery. You get runnable code and traceable logging.