PropFirmGuard/PropFirmGuard.mq5

4393 lines
149 KiB
MQL5
Raw Permalink Normal View History

2026-08-13 09:00:54 -07:00
//+------------------------------------------------------------------+
//| PropFirmGuard.mq5 |
//| Copyright 2026, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
//+------------------------------------------------------------------+
//| Standard Library |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| Enumerations |
//+------------------------------------------------------------------+
//| Represents the general compliance condition of the account. |
//| Additional states will be used as more rules are implemented. |
//+------------------------------------------------------------------+
enum ENUM_COMPLIANCE_STATUS
{
COMPLIANCE_SAFE = 0, // Account is operating within configured limits
COMPLIANCE_WARNING, // Account is approaching a compliance limit
COMPLIANCE_CRITICAL, // Account is very close to a compliance limit
COMPLIANCE_BREACHED // A configured compliance limit has been breached
};
//+------------------------------------------------------------------+
//| Profit target status |
//+------------------------------------------------------------------+
//| Represents progress toward the configured account profit target. |
//+------------------------------------------------------------------+
enum ENUM_TARGET_STATUS
{
TARGET_IN_PROGRESS = 0, // Target is still being pursued
TARGET_NEARING, // At least 90% of the target has been reached
TARGET_REACHED // Required target balance has been achieved
};
//+------------------------------------------------------------------+
//| Overall guard status |
//+------------------------------------------------------------------+
//| Combines the monitored rules into one account-level condition. |
//+------------------------------------------------------------------+
enum ENUM_GUARD_STATUS
{
GUARD_SAFE = 0, // All monitored rules are within safe limits
GUARD_TARGET_REACHED, // Profit target has been achieved
GUARD_WARNING, // At least one rule is in warning state
GUARD_CRITICAL, // At least one rule is in critical state
GUARD_BREACHED // At least one drawdown rule has been breached
};
//+------------------------------------------------------------------+
//| News monitoring status |
//+------------------------------------------------------------------+
//| Represents the current condition of the economic news filter. |
//+------------------------------------------------------------------+
enum ENUM_NEWS_STATUS
{
NEWS_DISABLED = 0, // Economic news monitoring is disabled
NEWS_NO_EXPOSURE, // No supported open positions require news monitoring
NEWS_NO_EVENT, // No relevant event was found
NEWS_SAFE, // Relevant event exists but restriction is inactive
NEWS_RESTRICTED, // Current time is inside the restricted news window
NEWS_UNAVAILABLE // Calendar data could not be retrieved
};
//+------------------------------------------------------------------+
//| News position match status |
//+------------------------------------------------------------------+
//| Describes whether an open position is affected by the selected |
//| Economic Calendar event. |
//+------------------------------------------------------------------+
enum ENUM_NEWS_POSITION_STATUS
{
NEWS_POSITION_NOT_AFFECTED = 0, // Position does not use the event currency
NEWS_POSITION_AFFECTED, // Position is exposed to the event currency
NEWS_POSITION_UNSUPPORTED // Position is outside current Forex/XAUUSD scope
};
//+------------------------------------------------------------------+
//| News holding status |
//+------------------------------------------------------------------+
//| Describes the account's current holding-rule condition around |
//| the selected Economic Calendar event. |
//+------------------------------------------------------------------+
enum ENUM_NEWS_HOLDING_STATUS
{
NEWS_HOLDING_CLEAR = 0, // No active holding restriction
NEWS_HOLDING_ALLOWED, // Restricted window active but holding is permitted
NEWS_HOLDING_WARNING, // Event is approaching and affected positions exist
NEWS_HOLDING_PROTECTION // Restricted window active and protection is required
};
//+------------------------------------------------------------------+
//| Protection action |
//+------------------------------------------------------------------+
//| Defines what Prop Firm Guard should do when a monitored rule |
//| requires protective action. |
//+------------------------------------------------------------------+
enum ENUM_PROTECTION_ACTION
{
PROTECTION_WARN_ONLY = 0, // Report the condition without closing positions
PROTECTION_CLOSE_AFFECTED, // Close positions affected by the triggering rule
PROTECTION_CLOSE_ALL // Close every open account position
};
//+------------------------------------------------------------------+
//| Inputs |
//+------------------------------------------------------------------+
input group "General"
2026-08-28 18:38:10 +03:00
input int InpTimerSeconds = 1; // Dashboard update interval in seconds
input int InpDashboardX = 10; // Dashboard horizontal position
input int InpDashboardY = 105; // Dashboard vertical position
input bool InpEnableAlerts = true; // Enable terminal alerts
input bool InpEnablePushNotifications = false; // Enable mobile push notifications
input group "Prop Firm Rules"
input double InpInitialAccountBalance = 0.0; // Initial prop-firm account balance
input double InpDailyDrawdownPercent = 5.0; // Maximum permitted daily drawdown
input double InpOverallDrawdownPercent = 10.0; // Maximum permitted overall drawdown
input double InpProfitTargetPercent = 8.0; // Required profit target
input group "Warning Levels"
input double InpWarningLevelPercent = 70.0; // Loss limit usage that triggers warning
input double InpCriticalLevelPercent = 90.0; // Loss limit usage that triggers critical state
input group "News Filter"
input bool InpEnableNewsFilter = true; // Enable economic news monitoring
input ENUM_CALENDAR_EVENT_IMPORTANCE InpNewsImportance = CALENDAR_IMPORTANCE_HIGH; // Minimum monitored importance
input int InpMinutesBeforeNews = 10; // Restricted minutes before news
input int InpMinutesAfterNews = 10; // Restricted minutes after news
input bool InpAllowNewsHolding = true; // Allow positions to remain open during news
input group "Protection Actions"
input ENUM_PROTECTION_ACTION InpDailyDrawdownAction = PROTECTION_WARN_ONLY; // Daily breach action
input ENUM_PROTECTION_ACTION InpOverallDrawdownAction = PROTECTION_WARN_ONLY; // Overall breach action
input ENUM_PROTECTION_ACTION InpNewsRestrictionAction = PROTECTION_WARN_ONLY; // News restriction action
//+------------------------------------------------------------------+
//| Structures |
//+------------------------------------------------------------------+
//| Holds the live account values required by the monitoring logic. |
//| Keeping them together will make later calculations easier. |
//+------------------------------------------------------------------+
struct SAccountState
{
long login; // Trading account login number
double balance; // Current account balance
double equity; // Current account equity
double floatingPL; // Current floating profit or loss
ENUM_COMPLIANCE_STATUS status; // Current overall compliance status
};
//+------------------------------------------------------------------+
//| Account settings structure |
//+------------------------------------------------------------------+
//| Stores the main prop-firm rules associated with this account. |
//+------------------------------------------------------------------+
struct SAccountSettings
{
long accountLogin; // Trading account login
string accountServer; // Broker trading server
double initialBalance; // Initial prop-firm account balance
double dailyDrawdownPercent; // Maximum daily drawdown
double overallDrawdownPercent; // Maximum overall drawdown
double profitTargetPercent; // Required profit target
datetime updatedAt; // Last settings update time
};
//+------------------------------------------------------------------+
//| Daily session structure |
//+------------------------------------------------------------------+
//| Represents one monitored trading day. Starting values remain |
//| unconfirmed until the user explicitly confirms them in Phase 3. |
//+------------------------------------------------------------------+
2026-08-28 18:38:10 +03:00
//+------------------------------------------------------------------+
//| Daily session structure |
//+------------------------------------------------------------------+
struct SDailySession
{
2026-08-28 18:38:10 +03:00
long accountLogin;
string accountServer;
string tradingDate;
double startingBalance;
double startingEquity;
bool balanceConfirmed;
double latestBalance;
double latestEquity;
double dailyLoss;
double maximumDailyLossReached;
double targetProgress;
string sessionStatus;
datetime createdAt;
datetime updatedAt;
};
//+------------------------------------------------------------------+
//| Daily drawdown structure |
//+------------------------------------------------------------------+
//| Holds the calculated daily risk values for the active session. |
//+------------------------------------------------------------------+
struct SDailyDrawdownState
{
double startingBalance; // Confirmed daily starting balance
double currentEquity; // Current live account equity
double maximumDailyLoss; // Maximum money that may be lost today
double currentDailyLoss; // Current loss measured from starting balance
double drawdownPercent; // Current loss as percentage of starting balance
double limitUsagePercent; // Percentage of daily loss allowance already used
double remainingAllowance; // Money remaining before daily breach
ENUM_COMPLIANCE_STATUS status; // Current daily drawdown status
};
//+------------------------------------------------------------------+
//| Overall drawdown structure |
//+------------------------------------------------------------------+
//| Holds the live calculations for the static overall loss rule. |
//+------------------------------------------------------------------+
struct SOverallDrawdownState
{
double initialBalance; // Initial prop-firm account balance
double currentEquity; // Current live account equity
double maximumOverallLoss; // Maximum permitted overall loss
double overallFloor; // Minimum permitted account equity
double currentOverallLoss; // Current loss from initial balance
double limitUsagePercent; // Percentage of overall allowance used
double remainingAllowance; // Money remaining before breach
ENUM_COMPLIANCE_STATUS status; // Current overall drawdown status
};
//+------------------------------------------------------------------+
//| Profit target structure |
//+------------------------------------------------------------------+
//| Holds the live calculations used to track challenge progress. |
//+------------------------------------------------------------------+
struct SProfitTargetState
{
double initialBalance; // Initial prop-firm account balance
double currentBalance; // Current realized account balance
double targetAmount; // Profit required in account currency
double targetBalance; // Balance required to complete the target
double currentProfit; // Realized profit above initial balance
double progressPercent; // Percentage of target already completed
double remainingProfit; // Profit still required to reach target
ENUM_TARGET_STATUS status; // Current target progress state
};
//+------------------------------------------------------------------+
//| News monitoring structure |
//+------------------------------------------------------------------+
//| Holds account exposure, the next relevant economic event, and |
//| the current news-holding condition. |
//+------------------------------------------------------------------+
struct SNewsState
{
bool hasExposure; // Whether supported open positions exist
int monitoredCurrencies; // Number of unique currencies being monitored
string currencyList; // Comma-separated monitored currencies
bool eventFound; // Whether a relevant event was located
string eventCurrency; // Currency affected by selected event
ulong eventId; // Economic Calendar event identifier
string eventName; // Economic event name
ENUM_CALENDAR_EVENT_IMPORTANCE importance; // Event importance
datetime eventTime; // Event time in trade-server time
long secondsToEvent; // Seconds until event, negative after release
datetime restrictionStart; // Beginning of restricted news window
datetime restrictionEnd; // End of restricted news window
bool restrictedWindow; // Whether restriction is currently active
int affectedPositions; // Positions affected by selected event
int protectionPositions; // Positions exposed to Phase 12 protection
ENUM_NEWS_STATUS status; // Current news subsystem status
ENUM_NEWS_HOLDING_STATUS holdingStatus; // Current news-holding rule state
};
//+------------------------------------------------------------------+
//| News position match structure |
//+------------------------------------------------------------------+
//| Stores the classification and holding-rule state of one open |
//| position for the currently selected Economic Calendar event. |
//+------------------------------------------------------------------+
struct SNewsPositionMatch
{
ulong ticket; // Position ticket
string symbol; // Position symbol
string currency1; // First relevant currency
string currency2; // Second relevant currency
ENUM_NEWS_POSITION_STATUS status; // Event-currency classification
bool protectionRequired; // Whether Phase 12 may act on this position
};
2026-08-28 18:38:10 +03:00
//+------------------------------------------------------------------+
//| Alert state |
//+------------------------------------------------------------------+
//| Remembers which conditions have already generated notifications. |
//+------------------------------------------------------------------+
struct SAlertState
{
ENUM_COMPLIANCE_STATUS dailyDrawdownStatus;
ENUM_COMPLIANCE_STATUS overallDrawdownStatus;
ENUM_TARGET_STATUS profitTargetStatus;
ulong newsEventId;
bool newsApproachAlerted;
bool newsRestrictedAlerted;
string dashboardMessage;
};
//+------------------------------------------------------------------+
//| Global variables |
//+------------------------------------------------------------------+
SAccountState g_accountState; // Latest monitored account state
SAccountSettings g_accountSettings; // Persisted prop-firm settings
SDailySession g_dailySession; // Current trading-day record
SDailyDrawdownState g_dailyDrawdown; // Current daily drawdown calculations
SOverallDrawdownState g_overallDrawdown; // Current overall drawdown calculations
SProfitTargetState g_profitTarget; // Current profit-target calculations
SNewsState g_newsState; // Current economic news state
SNewsPositionMatch g_newsPositionMatches[]; // Classification of open positions
2026-08-28 18:38:10 +03:00
SAlertState g_alertState;
CTrade g_trade; // Synchronous trade execution helper
string g_objectPrefix = "PFG_"; // Prefix used by dashboard objects
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
int g_database = INVALID_HANDLE; // Active SQLite database handle
bool g_hasDailySession = false; // Current-day session availability
ulong g_lastMatchedEventId = 0; // Last event printed to the Journal
ENUM_NEWS_HOLDING_STATUS g_lastNewsHoldingStatus = NEWS_HOLDING_CLEAR; // Last notified holding condition
bool g_dailyBreachWarned = false; // Prevent repeated daily breach warnings
bool g_overallBreachWarned = false; // Prevent repeated overall breach warnings
ulong g_lastNewsWarnedEventId = 0; // Prevent repeated warning for same news event
2026-08-28 18:38:10 +03:00
datetime g_lastRuntimeDatabaseUpdate = 0;
//+------------------------------------------------------------------+
//| Validate compliance inputs |
//+------------------------------------------------------------------+
bool ValidateComplianceInputs()
{
// Financial calculations require a valid initial challenge balance.
if(InpInitialAccountBalance <= 0.0)
{
Print("Prop Firm Guard: Initial account balance must be greater than zero.");
return(false);
}
if(InpDailyDrawdownPercent <= 0.0 ||
InpDailyDrawdownPercent > 100.0)
{
Print("Prop Firm Guard: Daily drawdown percentage must be between 0 and 100.");
return(false);
}
if(InpOverallDrawdownPercent <= 0.0 ||
InpOverallDrawdownPercent > 100.0)
{
Print("Prop Firm Guard: Overall drawdown percentage must be between 0 and 100.");
return(false);
}
if(InpProfitTargetPercent <= 0.0 ||
InpProfitTargetPercent > 100.0)
{
Print("Prop Firm Guard: Profit target percentage must be between 0 and 100.");
return(false);
}
if(InpWarningLevelPercent <= 0.0 ||
InpWarningLevelPercent >= InpCriticalLevelPercent)
{
Print("Prop Firm Guard: Warning level must be greater than 0 and below the critical level.");
return(false);
}
if(InpCriticalLevelPercent <= InpWarningLevelPercent ||
InpCriticalLevelPercent >= 100.0)
{
Print("Prop Firm Guard: Critical level must be above the warning level and below 100.");
return(false);
}
// News restriction windows cannot use negative durations.
if(InpMinutesBeforeNews < 0 ||
InpMinutesAfterNews < 0)
{
Print("Prop Firm Guard: News restriction minutes cannot be negative.");
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Set chart integer property |
//+------------------------------------------------------------------+
bool SetChartIntegerProperty(const ENUM_CHART_PROPERTY_INTEGER property,
const long value)
{
ResetLastError();
if(!ChartSetInteger(0,
property,
value))
{
Print("Prop Firm Guard: Could not set chart property ",
EnumToString(property),
". Error: ",
GetLastError());
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Configure Prop Firm Guard chart appearance |
//+------------------------------------------------------------------+
bool ConfigureChartAppearance()
{
// Use a light neutral background that keeps the dashboard readable.
if(!SetChartIntegerProperty(CHART_COLOR_BACKGROUND,
clrWhiteSmoke))
return(false);
// Keep axes, scales and chart text clearly visible.
if(!SetChartIntegerProperty(CHART_COLOR_FOREGROUND,
clrBlack))
return(false);
// The dashboard is cleaner without the default chart grid.
if(!SetChartIntegerProperty(CHART_SHOW_GRID,
false))
return(false);
// Display the market as Japanese candlesticks.
if(!SetChartIntegerProperty(CHART_MODE,
CHART_CANDLES))
return(false);
// Bullish candles and bars use green consistently.
if(!SetChartIntegerProperty(CHART_COLOR_CANDLE_BULL,
clrGreen))
return(false);
if(!SetChartIntegerProperty(CHART_COLOR_CHART_UP,
clrGreen))
return(false);
// Bearish candles and bars use red consistently.
if(!SetChartIntegerProperty(CHART_COLOR_CANDLE_BEAR,
clrRed))
return(false);
if(!SetChartIntegerProperty(CHART_COLOR_CHART_DOWN,
clrRed))
return(false);
// Keep the line-chart representation neutral and readable.
if(!SetChartIntegerProperty(CHART_COLOR_CHART_LINE,
clrBlack))
return(false);
// Use the same directional convention for live price lines.
if(!SetChartIntegerProperty(CHART_COLOR_BID,
clrGreen))
return(false);
if(!SetChartIntegerProperty(CHART_COLOR_ASK,
clrRed))
return(false);
// Keep the last traded price visually neutral.
if(!SetChartIntegerProperty(CHART_COLOR_LAST,
clrBlack))
return(false);
// Stop Loss, Take Profit and related stop levels use red because
// they represent important protection boundaries.
if(!SetChartIntegerProperty(CHART_COLOR_STOP_LEVEL,
clrRed))
return(false);
// Show open-position, pending-order, SL and TP levels.
if(!SetChartIntegerProperty(CHART_SHOW_TRADE_LEVELS,
true))
return(false);
// Allow trade levels to remain interactively draggable.
if(!SetChartIntegerProperty(CHART_DRAG_TRADE_LEVELS,
true))
return(false);
// Keep MetaTrader's One Click Trading panel visible.
if(!SetChartIntegerProperty(CHART_SHOW_ONE_CLICK,
true))
return(false);
ChartRedraw(0);
return(true);
}
2026-08-13 09:00:54 -07:00
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
if(InpTimerSeconds < 1)
{
Print("Prop Firm Guard: Timer interval must be at least 1 second.");
return(INIT_PARAMETERS_INCORRECT);
}
if(!ValidateComplianceInputs())
return(INIT_PARAMETERS_INCORRECT);
// Apply the visual chart theme before creating dashboard objects.
if(!ConfigureChartAppearance())
{
Print("Prop Firm Guard: Could not configure chart appearance.");
return(INIT_FAILED);
}
g_trade.SetAsyncMode(false);
UpdateAccountState();
if(!OpenDatabase())
{
Print("Prop Firm Guard: Database initialization failed.");
return(INIT_FAILED);
}
if(!EnsureDatabaseTables())
{
Print("Prop Firm Guard: Could not initialize database tables.");
CloseDatabase();
return(INIT_FAILED);
}
if(!InitializeAccountSettings())
{
Print("Prop Firm Guard: Could not initialize account settings.");
CloseDatabase();
return(INIT_FAILED);
}
if(!InitializeDailySession())
{
Print("Prop Firm Guard: Could not initialize daily session.");
CloseDatabase();
return(INIT_FAILED);
}
CalculateDailyDrawdown();
CalculateOverallDrawdown();
CalculateProfitTarget();
UpdateNewsState();
UpdateNewsPositionMatches();
2026-08-28 18:38:10 +03:00
// Store the current monitoring state so alerts begin from subsequent
// state transitions rather than from historical conditions.
InitializeAlertState();
if(!CreateDashboard())
{
Print("Prop Firm Guard: Failed to create the dashboard.");
CloseDatabase();
return(INIT_FAILED);
}
if(!EventSetTimer(InpTimerSeconds))
{
Print("Prop Firm Guard: Failed to start the timer. Error: ",
GetLastError());
DeleteDashboard();
CloseDatabase();
return(INIT_FAILED);
}
Print("Prop Firm Guard initialized successfully.");
2026-08-13 09:00:54 -07:00
return(INIT_SUCCEEDED);
}
2026-08-13 09:00:54 -07:00
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Stop timer events before shutting down other EA resources.
EventKillTimer();
// Remove only chart objects created by Prop Firm Guard.
DeleteDashboard();
// Close the SQLite connection after all database work is complete.
CloseDatabase();
Print("Prop Firm Guard removed. Deinitialization reason: ", reason);
2026-08-13 09:00:54 -07:00
}
2026-08-13 09:00:54 -07:00
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Phase 1 does not require tick-by-tick processing.
// Account monitoring currently runs once per second through OnTimer().
}
//+------------------------------------------------------------------+
//| Timer function |
//+------------------------------------------------------------------+
void OnTimer()
{
// Refresh live account information.
UpdateAccountState();
// Detect a new daily monitoring session when necessary.
CheckForNewTradingDay();
2026-08-28 18:38:10 +03:00
// Recalculate all financial compliance conditions.
CalculateDailyDrawdown();
CalculateOverallDrawdown();
CalculateProfitTarget();
2026-08-28 18:38:10 +03:00
// Update economic news exposure and affected positions.
UpdateNewsState();
UpdateNewsPositionMatches();
2026-08-28 18:38:10 +03:00
// Notify only when monitored conditions change.
ManageAlerts();
// Execute configured protection behavior.
ManageProtectionActions();
2026-08-28 18:38:10 +03:00
// Protection actions may change account values or positions.
UpdateAccountState();
CalculateDailyDrawdown();
CalculateOverallDrawdown();
CalculateProfitTarget();
2026-08-28 18:38:10 +03:00
// Persist the latest structured daily-session snapshot.
UpdateRuntimeDatabase();
// Refresh the dashboard with the resulting state.
UpdateDashboard();
2026-08-28 18:38:10 +03:00
}
//+------------------------------------------------------------------+
//| Chart event function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
// We are interested only in clicks on chart objects.
if(id != CHARTEVENT_OBJECT_CLICK)
return;
// Confirm the entered starting balance when the user presses
// the confirmation button.
if(sparam == g_confirmButton)
{
if(!ConfirmDailyStartingBalance())
Print("Prop Firm Guard: Starting balance confirmation failed.");
}
}
//+------------------------------------------------------------------+
//| Update account state |
//+------------------------------------------------------------------+
void UpdateAccountState()
{
// Read the account identifier so future persisted data can be tied
// to the correct trading account.
g_accountState.login = AccountInfoInteger(ACCOUNT_LOGIN);
// Balance represents realized funds currently available on the account.
g_accountState.balance = AccountInfoDouble(ACCOUNT_BALANCE);
// Equity includes the effect of all currently open positions.
g_accountState.equity = AccountInfoDouble(ACCOUNT_EQUITY);
// The difference between equity and balance represents the current
// floating profit or loss of open positions.
g_accountState.floatingPL = g_accountState.equity - g_accountState.balance;
// No compliance rules exist yet, so the initial state remains safe.
g_accountState.status = COMPLIANCE_SAFE;
}
//+------------------------------------------------------------------+
//| Calculate daily drawdown |
//+------------------------------------------------------------------+
void CalculateDailyDrawdown()
{
// Reset the live state when today's starting balance has not yet
// been confirmed by the trader.
if(!g_dailySession.balanceConfirmed ||
g_dailySession.startingBalance <= 0.0)
{
g_dailyDrawdown.startingBalance = 0.0;
g_dailyDrawdown.currentEquity = g_accountState.equity;
g_dailyDrawdown.maximumDailyLoss = 0.0;
g_dailyDrawdown.currentDailyLoss = 0.0;
g_dailyDrawdown.drawdownPercent = 0.0;
g_dailyDrawdown.limitUsagePercent = 0.0;
g_dailyDrawdown.remainingAllowance = 0.0;
g_dailyDrawdown.status = COMPLIANCE_SAFE;
return;
}
// Use the confirmed starting balance as the fixed daily reference.
g_dailyDrawdown.startingBalance = g_dailySession.startingBalance;
// Current equity reflects the effect of currently open positions.
g_dailyDrawdown.currentEquity = g_accountState.equity;
// Convert the configured daily percentage limit into account currency.
g_dailyDrawdown.maximumDailyLoss =
g_dailyDrawdown.startingBalance *
g_accountSettings.dailyDrawdownPercent / 100.0;
// A loss exists only when equity falls below the daily starting balance.
g_dailyDrawdown.currentDailyLoss =
g_dailyDrawdown.startingBalance -
g_dailyDrawdown.currentEquity;
if(g_dailyDrawdown.currentDailyLoss < 0.0)
g_dailyDrawdown.currentDailyLoss = 0.0;
// Express the current loss as a percentage of the starting balance.
g_dailyDrawdown.drawdownPercent =
g_dailyDrawdown.currentDailyLoss /
g_dailyDrawdown.startingBalance * 100.0;
// Measure how much of the permitted daily loss has already been used.
if(g_dailyDrawdown.maximumDailyLoss > 0.0)
{
g_dailyDrawdown.limitUsagePercent =
g_dailyDrawdown.currentDailyLoss /
g_dailyDrawdown.maximumDailyLoss * 100.0;
}
else
{
g_dailyDrawdown.limitUsagePercent = 0.0;
}
// Calculate the amount still available before the daily limit is breached.
g_dailyDrawdown.remainingAllowance =
g_dailyDrawdown.maximumDailyLoss -
g_dailyDrawdown.currentDailyLoss;
if(g_dailyDrawdown.remainingAllowance < 0.0)
g_dailyDrawdown.remainingAllowance = 0.0;
// Assign the most severe state first so a breach can never be
// incorrectly classified as merely critical or warning.
if(g_dailyDrawdown.limitUsagePercent >= 100.0)
g_dailyDrawdown.status = COMPLIANCE_BREACHED;
else if(g_dailyDrawdown.limitUsagePercent >= InpCriticalLevelPercent)
g_dailyDrawdown.status = COMPLIANCE_CRITICAL;
else if(g_dailyDrawdown.limitUsagePercent >= InpWarningLevelPercent)
g_dailyDrawdown.status = COMPLIANCE_WARNING;
else
g_dailyDrawdown.status = COMPLIANCE_SAFE;
2026-08-13 09:00:54 -07:00
}
//+------------------------------------------------------------------+
//| Calculate overall drawdown |
//+------------------------------------------------------------------+
void CalculateOverallDrawdown()
{
// Use the initial challenge balance as the permanent reference.
g_overallDrawdown.initialBalance = g_accountSettings.initialBalance;
// Equity is used because open positions can move the account toward
// a breach before their profit or loss becomes realized.
g_overallDrawdown.currentEquity = g_accountState.equity;
// Convert the configured percentage into the maximum monetary loss.
g_overallDrawdown.maximumOverallLoss =
g_overallDrawdown.initialBalance *
g_accountSettings.overallDrawdownPercent / 100.0;
// The static equity floor never moves as the account gains profit.
g_overallDrawdown.overallFloor =
g_overallDrawdown.initialBalance -
g_overallDrawdown.maximumOverallLoss;
// Measure the current loss relative to the initial challenge balance.
g_overallDrawdown.currentOverallLoss =
g_overallDrawdown.initialBalance -
g_overallDrawdown.currentEquity;
// Equity above the initial balance means no overall drawdown is in use.
if(g_overallDrawdown.currentOverallLoss < 0.0)
g_overallDrawdown.currentOverallLoss = 0.0;
// Calculate how much of the permitted overall loss has been consumed.
if(g_overallDrawdown.maximumOverallLoss > 0.0)
{
g_overallDrawdown.limitUsagePercent =
g_overallDrawdown.currentOverallLoss /
g_overallDrawdown.maximumOverallLoss * 100.0;
}
else
{
g_overallDrawdown.limitUsagePercent = 0.0;
}
// Remaining allowance is the distance between current equity and
// the static overall drawdown floor.
g_overallDrawdown.remainingAllowance =
g_overallDrawdown.maximumOverallLoss -
g_overallDrawdown.currentOverallLoss;
if(g_overallDrawdown.remainingAllowance < 0.0)
g_overallDrawdown.remainingAllowance = 0.0;
// Evaluate the current severity using the common warning thresholds.
if(g_overallDrawdown.limitUsagePercent >= 100.0)
g_overallDrawdown.status = COMPLIANCE_BREACHED;
else if(g_overallDrawdown.limitUsagePercent >= InpCriticalLevelPercent)
g_overallDrawdown.status = COMPLIANCE_CRITICAL;
else if(g_overallDrawdown.limitUsagePercent >= InpWarningLevelPercent)
g_overallDrawdown.status = COMPLIANCE_WARNING;
else
g_overallDrawdown.status = COMPLIANCE_SAFE;
}
//+------------------------------------------------------------------+
//| Calculate profit target |
//+------------------------------------------------------------------+
void CalculateProfitTarget()
{
// Use the original challenge balance as the fixed target reference.
g_profitTarget.initialBalance = g_accountSettings.initialBalance;
// Profit-target completion is based on realized account balance.
g_profitTarget.currentBalance = g_accountState.balance;
// Convert the configured target percentage into account currency.
g_profitTarget.targetAmount =
g_profitTarget.initialBalance *
g_accountSettings.profitTargetPercent / 100.0;
// Add the required profit to the initial balance.
g_profitTarget.targetBalance =
g_profitTarget.initialBalance +
g_profitTarget.targetAmount;
// Measure realized profit above the original account balance.
g_profitTarget.currentProfit =
g_profitTarget.currentBalance -
g_profitTarget.initialBalance;
// A balance below the initial value represents no target progress.
if(g_profitTarget.currentProfit < 0.0)
g_profitTarget.currentProfit = 0.0;
// Calculate the percentage of the target already completed.
if(g_profitTarget.targetAmount > 0.0)
{
g_profitTarget.progressPercent =
g_profitTarget.currentProfit /
g_profitTarget.targetAmount * 100.0;
}
else
{
g_profitTarget.progressPercent = 0.0;
}
// Target progress does not need to display more than 100%.
if(g_profitTarget.progressPercent > 100.0)
g_profitTarget.progressPercent = 100.0;
// Calculate how much additional realized profit is still required.
g_profitTarget.remainingProfit =
g_profitTarget.targetAmount -
g_profitTarget.currentProfit;
if(g_profitTarget.remainingProfit < 0.0)
g_profitTarget.remainingProfit = 0.0;
// Determine the current target state.
if(g_profitTarget.currentBalance >= g_profitTarget.targetBalance)
g_profitTarget.status = TARGET_REACHED;
else if(g_profitTarget.progressPercent >= 90.0)
g_profitTarget.status = TARGET_NEARING;
else
g_profitTarget.status = TARGET_IN_PROGRESS;
}
//+------------------------------------------------------------------+
//| Initialize news state |
//+------------------------------------------------------------------+
void InitializeNewsState()
{
g_newsState.hasExposure = false;
g_newsState.monitoredCurrencies = 0;
g_newsState.currencyList = "";
g_newsState.eventFound = false;
g_newsState.eventCurrency = "";
g_newsState.eventId = 0;
g_newsState.eventName = "";
g_newsState.importance = CALENDAR_IMPORTANCE_NONE;
g_newsState.eventTime = 0;
g_newsState.secondsToEvent = 0;
g_newsState.restrictionStart = 0;
g_newsState.restrictionEnd = 0;
g_newsState.restrictedWindow = false;
g_newsState.affectedPositions = 0;
g_newsState.protectionPositions = 0;
g_newsState.status = InpEnableNewsFilter ? NEWS_NO_EXPOSURE : NEWS_DISABLED;
g_newsState.holdingStatus = NEWS_HOLDING_CLEAR;
}
2026-08-28 18:38:10 +03:00
//+------------------------------------------------------------------+
//| Initialize alert state |
//+------------------------------------------------------------------+
void InitializeAlertState()
{
g_alertState.dailyDrawdownStatus = g_dailyDrawdown.status;
g_alertState.overallDrawdownStatus = g_overallDrawdown.status;
g_alertState.profitTargetStatus = g_profitTarget.status;
g_alertState.newsEventId = 0;
g_alertState.newsApproachAlerted = false;
g_alertState.newsRestrictedAlerted = false;
g_alertState.dashboardMessage = "";
}
//+------------------------------------------------------------------+
//| Check whether currency already exists in array |
//+------------------------------------------------------------------+
bool CurrencyExists(const string &currencies[],
const string currency)
{
int count = ArraySize(currencies);
for(int i = 0; i < count; i++)
{
if(currencies[i] == currency)
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
//| Add unique currency to array |
//+------------------------------------------------------------------+
bool AddUniqueCurrency(string &currencies[],
const string currency)
{
// Empty currencies do not represent useful Economic Calendar exposure.
if(currency == "")
return(true);
// Do not duplicate currencies already collected from another position.
if(CurrencyExists(currencies, currency))
return(true);
int currentSize = ArraySize(currencies);
// Extend the dynamic array by one element.
if(ArrayResize(currencies, currentSize + 1) != currentSize + 1)
{
Print("Prop Firm Guard: Could not expand monitored currency array.");
return(false);
}
currencies[currentSize] = currency;
return(true);
}
//+------------------------------------------------------------------+
//| Get symbol currencies relevant to news |
//+------------------------------------------------------------------+
bool GetSymbolNewsCurrencies(const string symbol,
string &currency1,
string &currency2)
{
string baseCurrency;
string profitCurrency;
long calculationMode = 0;
currency1 = "";
currency2 = "";
// Read the currencies defined by the broker for this symbol.
ResetLastError();
if(!SymbolInfoString(symbol,
SYMBOL_CURRENCY_BASE,
baseCurrency))
{
Print("Prop Firm Guard: Could not read base currency for ",
symbol,
". Error: ",
GetLastError());
return(false);
}
ResetLastError();
if(!SymbolInfoString(symbol,
SYMBOL_CURRENCY_PROFIT,
profitCurrency))
{
Print("Prop Firm Guard: Could not read profit currency for ",
symbol,
". Error: ",
GetLastError());
return(false);
}
// XAUUSD receives special treatment. Gold itself has no corresponding
// currency-calendar stream, so USD news represents its macro exposure.
if(baseCurrency == "XAU" &&
profitCurrency == "USD")
{
currency1 = "USD";
return(true);
}
// Confirm that the symbol uses one of MetaTrader's Forex calculation
// models before treating its currencies as Forex news exposure.
ResetLastError();
if(!SymbolInfoInteger(symbol,
SYMBOL_TRADE_CALC_MODE,
calculationMode))
{
Print("Prop Firm Guard: Could not read calculation mode for ",
symbol,
". Error: ",
GetLastError());
return(false);
}
if(calculationMode != SYMBOL_CALC_MODE_FOREX &&
calculationMode != SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE)
return(true);
currency1 = baseCurrency;
currency2 = profitCurrency;
return(true);
}
//+------------------------------------------------------------------+
//| Add currencies exposed by position symbol |
//+------------------------------------------------------------------+
bool AddPositionCurrencies(const string symbol,
string &currencies[])
{
string currency1;
string currency2;
if(!GetSymbolNewsCurrencies(symbol,
currency1,
currency2))
return(false);
// An empty result means this instrument is intentionally unsupported.
if(currency1 == "" &&
currency2 == "")
return(true);
if(!AddUniqueCurrency(currencies, currency1))
return(false);
if(!AddUniqueCurrency(currencies, currency2))
return(false);
return(true);
}
//+------------------------------------------------------------------+
//| Collect currencies from active positions |
//+------------------------------------------------------------------+
bool CollectExposureCurrencies(string &currencies[])
{
ArrayResize(currencies, 0);
int totalPositions = PositionsTotal();
// No open positions means there is currently no news exposure.
if(totalPositions == 0)
return(true);
for(int i = 0; i < totalPositions; i++)
{
ResetLastError();
// PositionGetSymbol() returns the symbol at the requested position
// index and selects that position for subsequent position access.
string symbol = PositionGetSymbol(i);
if(symbol == "")
{
Print("Prop Firm Guard: Could not retrieve position symbol at index ",
i,
". Error: ",
GetLastError());
return(false);
}
if(!AddPositionCurrencies(symbol, currencies))
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Build monitored currency list |
//+------------------------------------------------------------------+
string BuildCurrencyList(const string &currencies[])
{
string result = "";
int count = ArraySize(currencies);
for(int i = 0; i < count; i++)
{
if(i > 0)
result += ", ";
result += currencies[i];
}
return(result);
}
//+------------------------------------------------------------------+
//| Find next news event for currency |
//+------------------------------------------------------------------+
bool FindNextNewsForCurrency(const string currency,
const datetime timeFrom,
const datetime timeTo,
MqlCalendarValue &bestValue,
MqlCalendarEvent &bestEvent)
{
MqlCalendarValue values[];
ResetLastError();
// Request calendar values only for the specified currency and time range.
int valueCount = CalendarValueHistory(values,
timeFrom,
timeTo,
NULL,
currency);
if(valueCount < 0)
{
Print("Prop Firm Guard: Could not retrieve calendar values for ",
currency,
". Error: ",
GetLastError());
return(false);
}
bool eventFound = false;
datetime earliestTime = 0;
for(int i = 0; i < valueCount; i++)
{
MqlCalendarEvent event;
ResetLastError();
// Retrieve the descriptive information associated with this value.
if(!CalendarEventById(values[i].event_id,
event))
{
Print("Prop Firm Guard: Could not retrieve calendar event ",
values[i].event_id,
". Error: ",
GetLastError());
continue;
}
// Ignore events below the minimum importance selected by the user.
if(event.importance < InpNewsImportance)
continue;
// Ignore events that do not have a useful exact release time.
if(event.time_mode != CALENDAR_TIMEMODE_DATETIME)
continue;
// Keep the earliest matching event in the search interval.
if(!eventFound ||
values[i].time < earliestTime)
{
bestValue = values[i];
bestEvent = event;
earliestTime = values[i].time;
eventFound = true;
}
}
return(eventFound);
}
//+------------------------------------------------------------------+
//| Update news state |
//+------------------------------------------------------------------+
void UpdateNewsState()
{
// Clear previous event information before running a fresh search.
InitializeNewsState();
if(!InpEnableNewsFilter)
{
g_newsState.status = NEWS_DISABLED;
return;
}
string currencies[];
// Build one deduplicated currency list from every supported open
// position on the trading account.
if(!CollectExposureCurrencies(currencies))
{
g_newsState.status = NEWS_UNAVAILABLE;
return;
}
int currencyCount = ArraySize(currencies);
// No supported currency exposure means there is nothing for the
// Economic Calendar engine to monitor.
if(currencyCount == 0)
{
g_newsState.hasExposure = false;
g_newsState.monitoredCurrencies = 0;
g_newsState.currencyList = "";
g_newsState.status = NEWS_NO_EXPOSURE;
return;
}
g_newsState.hasExposure = true;
g_newsState.monitoredCurrencies = currencyCount;
g_newsState.currencyList = BuildCurrencyList(currencies);
// Economic Calendar functions operate using trade-server time.
datetime currentTime = TimeTradeServer();
// Include the recent past so an event remains selected throughout its
// configured post-news restriction window.
datetime searchFrom = currentTime - InpMinutesAfterNews * 60;
// Search up to one day ahead for the next relevant event.
datetime searchTo = currentTime + 24 * 60 * 60;
bool foundAny = false;
// Store the selected event in initialized scalar values. This avoids
// possible-uninitialized compiler warnings for MqlCalendarValue objects.
ulong selectedEventId = 0;
string selectedEventName = "";
string selectedCurrency = "";
ENUM_CALENDAR_EVENT_IMPORTANCE selectedImportance = CALENDAR_IMPORTANCE_NONE;
datetime selectedEventTime = 0;
// Search every unique currency currently represented by an open
// supported account position.
for(int i = 0; i < currencyCount; i++)
{
MqlCalendarValue candidateValue;
MqlCalendarEvent candidateEvent;
if(!FindNextNewsForCurrency(currencies[i],
searchFrom,
searchTo,
candidateValue,
candidateEvent))
continue;
// Keep the first qualifying event, then replace it only when another
// exposed currency has an earlier qualifying event.
if(!foundAny ||
candidateValue.time < selectedEventTime)
{
selectedEventId = candidateEvent.id;
selectedEventName = candidateEvent.name;
selectedCurrency = currencies[i];
selectedImportance = candidateEvent.importance;
selectedEventTime = candidateValue.time;
foundAny = true;
}
}
if(!foundAny)
{
g_newsState.status = NEWS_NO_EVENT;
return;
}
// Store the nearest qualifying event across the complete account exposure.
g_newsState.eventFound = true;
g_newsState.eventCurrency = selectedCurrency;
g_newsState.eventId = selectedEventId;
g_newsState.eventName = selectedEventName;
g_newsState.importance = selectedImportance;
g_newsState.eventTime = selectedEventTime;
// Positive values mean the event is still ahead. Negative values mean
// the event has already occurred.
g_newsState.secondsToEvent = (long)(g_newsState.eventTime - currentTime);
// Build the complete prohibited/restricted interval.
g_newsState.restrictionStart = g_newsState.eventTime - InpMinutesBeforeNews * 60;
g_newsState.restrictionEnd = g_newsState.eventTime + InpMinutesAfterNews * 60;
// Determine whether the current trade-server time is inside that window.
g_newsState.restrictedWindow =
(currentTime >= g_newsState.restrictionStart &&
currentTime <= g_newsState.restrictionEnd);
if(g_newsState.restrictedWindow)
g_newsState.status = NEWS_RESTRICTED;
else
g_newsState.status = NEWS_SAFE;
}
//+------------------------------------------------------------------+
//| Match open positions to selected news event |
//+------------------------------------------------------------------+
void MatchOpenPositionsToNews()
{
// Remove classifications from the previous monitoring cycle.
ArrayResize(g_newsPositionMatches, 0);
// Position matching requires an actual selected calendar event.
if(!g_newsState.eventFound ||
g_newsState.eventCurrency == "")
return;
int totalPositions = PositionsTotal();
for(int i = 0; i < totalPositions; i++)
{
ResetLastError();
// PositionGetTicket() selects the indexed position so its remaining
// properties can be read immediately afterward.
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
{
Print("Prop Firm Guard: Could not retrieve position ticket at index ",
i,
". Error: ",
GetLastError());
continue;
}
string symbol;
if(!PositionGetString(POSITION_SYMBOL,
symbol))
{
Print("Prop Firm Guard: Could not retrieve symbol for position ",
ticket,
". Error: ",
GetLastError());
continue;
}
string currency1;
string currency2;
if(!GetSymbolNewsCurrencies(symbol,
currency1,
currency2))
continue;
SNewsPositionMatch match;
match.ticket = ticket;
match.symbol = symbol;
match.currency1 = currency1;
match.currency2 = currency2;
match.status = NEWS_POSITION_NOT_AFFECTED;
match.protectionRequired = false;
// Empty currencies identify instruments outside the current
// Forex and XAUUSD news-monitoring scope.
if(currency1 == "" &&
currency2 == "")
{
match.status = NEWS_POSITION_UNSUPPORTED;
}
else if(currency1 == g_newsState.eventCurrency ||
currency2 == g_newsState.eventCurrency)
{
match.status = NEWS_POSITION_AFFECTED;
}
int currentSize = ArraySize(g_newsPositionMatches);
if(ArrayResize(g_newsPositionMatches,
currentSize + 1) != currentSize + 1)
{
Print("Prop Firm Guard: Could not expand news position match array.");
return;
}
g_newsPositionMatches[currentSize] = match;
}
}
//+------------------------------------------------------------------+
//| Evaluate news holding rules |
//+------------------------------------------------------------------+
void EvaluateNewsHoldingRules()
{
g_newsState.affectedPositions = 0;
g_newsState.protectionPositions = 0;
g_newsState.holdingStatus = NEWS_HOLDING_CLEAR;
int totalMatches = ArraySize(g_newsPositionMatches);
// Nothing needs holding-rule evaluation without a selected event.
if(!g_newsState.eventFound ||
totalMatches == 0)
return;
// First count every position affected by the selected event.
for(int i = 0; i < totalMatches; i++)
{
// Always reset protection state before evaluating the current window.
g_newsPositionMatches[i].protectionRequired = false;
if(g_newsPositionMatches[i].status == NEWS_POSITION_AFFECTED)
g_newsState.affectedPositions++;
}
// No active position is exposed to this particular event.
if(g_newsState.affectedPositions == 0)
return;
// When holding is allowed, affected positions remain untouched even
// while the event is inside its configured restricted window.
if(InpAllowNewsHolding)
{
if(g_newsState.restrictedWindow)
g_newsState.holdingStatus = NEWS_HOLDING_ALLOWED;
else
g_newsState.holdingStatus = NEWS_HOLDING_WARNING;
return;
}
// Holding is prohibited. Outside the restricted window we warn the
// trader that affected positions are exposed to an approaching event.
if(!g_newsState.restrictedWindow)
{
g_newsState.holdingStatus = NEWS_HOLDING_WARNING;
return;
}
// The restricted window is now active and holding is not permitted.
// Mark only affected positions for the protection subsystem.
for(int i = 0; i < totalMatches; i++)
{
if(g_newsPositionMatches[i].status != NEWS_POSITION_AFFECTED)
continue;
g_newsPositionMatches[i].protectionRequired = true;
g_newsState.protectionPositions++;
}
g_newsState.holdingStatus = NEWS_HOLDING_PROTECTION;
}
//+------------------------------------------------------------------+
//| Count news-affected positions |
//+------------------------------------------------------------------+
int CountAffectedNewsPositions()
{
int affectedCount = 0;
int totalMatches = ArraySize(g_newsPositionMatches);
for(int i = 0; i < totalMatches; i++)
{
if(g_newsPositionMatches[i].status == NEWS_POSITION_AFFECTED)
affectedCount++;
}
return(affectedCount);
}
//+------------------------------------------------------------------+
//| Build affected symbol list |
//+------------------------------------------------------------------+
string BuildAffectedSymbolList()
{
string result = "";
int totalMatches = ArraySize(g_newsPositionMatches);
for(int i = 0; i < totalMatches; i++)
{
if(g_newsPositionMatches[i].status != NEWS_POSITION_AFFECTED)
continue;
// Avoid repeating the same symbol when a hedging account contains
// several positions for that instrument.
string token = g_newsPositionMatches[i].symbol;
if(StringFind("," + result + ",",
"," + token + ",") >= 0)
continue;
if(result != "")
result += ",";
result += token;
}
if(result == "")
return("NONE");
return(result);
}
//+------------------------------------------------------------------+
//| Print news position matches |
//+------------------------------------------------------------------+
void PrintNewsPositionMatches()
{
if(!g_newsState.eventFound)
return;
Print("--------------------------------------------------");
Print("Prop Firm Guard: News position classification");
Print("Event: ",
g_newsState.eventCurrency,
" | ",
g_newsState.eventName);
int totalMatches = ArraySize(g_newsPositionMatches);
if(totalMatches == 0)
{
Print("No open positions available for classification.");
Print("--------------------------------------------------");
return;
}
for(int i = 0; i < totalMatches; i++)
{
Print("Ticket ",
g_newsPositionMatches[i].ticket,
" | ",
g_newsPositionMatches[i].symbol,
" | ",
NewsPositionStatusToString(g_newsPositionMatches[i].status));
}
Print("--------------------------------------------------");
}
//+------------------------------------------------------------------+
//| Update news position matches |
//+------------------------------------------------------------------+
void UpdateNewsPositionMatches()
{
// Classify all currently open positions against the selected event.
MatchOpenPositionsToNews();
// Apply the configured holding rule to those classifications.
EvaluateNewsHoldingRules();
// Reset event tracking when no calendar event is currently selected.
if(!g_newsState.eventFound)
{
g_lastMatchedEventId = 0;
return;
}
// Print the complete classification only when a different calendar
2026-08-28 18:38:10 +03:00
// event becomes active.
if(g_newsState.eventId != g_lastMatchedEventId)
{
PrintNewsPositionMatches();
g_lastMatchedEventId = g_newsState.eventId;
}
}
//+------------------------------------------------------------------+
//| Convert news status to text |
//+------------------------------------------------------------------+
string NewsStatusToString(const ENUM_NEWS_STATUS status)
{
switch(status)
{
case NEWS_DISABLED:
return("DISABLED");
case NEWS_NO_EXPOSURE:
return("NO EXPOSURE");
case NEWS_NO_EVENT:
return("NO EVENT");
case NEWS_SAFE:
return("SAFE");
case NEWS_RESTRICTED:
return("RESTRICTED");
case NEWS_UNAVAILABLE:
default:
return("UNAVAILABLE");
}
}
//+------------------------------------------------------------------+
//| Convert news position status to text |
//+------------------------------------------------------------------+
string NewsPositionStatusToString(const ENUM_NEWS_POSITION_STATUS status)
{
switch(status)
{
case NEWS_POSITION_AFFECTED:
return("AFFECTED");
case NEWS_POSITION_UNSUPPORTED:
return("UNSUPPORTED");
case NEWS_POSITION_NOT_AFFECTED:
default:
return("NOT AFFECTED");
}
}
//+------------------------------------------------------------------+
//| Convert calendar importance to text |
//+------------------------------------------------------------------+
string CalendarImportanceToString(const ENUM_CALENDAR_EVENT_IMPORTANCE importance)
{
switch(importance)
{
case CALENDAR_IMPORTANCE_HIGH:
return("HIGH");
case CALENDAR_IMPORTANCE_MODERATE:
return("MODERATE");
case CALENDAR_IMPORTANCE_LOW:
return("LOW");
case CALENDAR_IMPORTANCE_NONE:
default:
return("NONE");
}
}
//+------------------------------------------------------------------+
//| Format news countdown |
2026-08-13 09:00:54 -07:00
//+------------------------------------------------------------------+
string FormatNewsCountdown(const long secondsToEvent)
{
long absoluteSeconds = secondsToEvent;
if(absoluteSeconds < 0)
absoluteSeconds = -absoluteSeconds;
int hours = (int)(absoluteSeconds / 3600);
int minutes = (int)((absoluteSeconds % 3600) / 60);
int seconds = (int)(absoluteSeconds % 60);
string formatted =
StringFormat("%02d:%02d:%02d",
hours,
minutes,
seconds);
if(secondsToEvent >= 0)
return(formatted);
return("+" + formatted);
}
//+------------------------------------------------------------------+
//| Convert compliance status to text |
//+------------------------------------------------------------------+
string ComplianceStatusToString(const ENUM_COMPLIANCE_STATUS status)
{
switch(status)
{
case COMPLIANCE_WARNING:
return("WARNING");
case COMPLIANCE_CRITICAL:
return("CRITICAL");
case COMPLIANCE_BREACHED:
return("BREACHED");
case COMPLIANCE_SAFE:
default:
return("SAFE");
}
}
//+------------------------------------------------------------------+
//| Convert target status to text |
//+------------------------------------------------------------------+
string TargetStatusToString(const ENUM_TARGET_STATUS status)
{
switch(status)
{
case TARGET_NEARING:
return("NEARING TARGET");
case TARGET_REACHED:
return("TARGET REACHED");
case TARGET_IN_PROGRESS:
default:
return("IN PROGRESS");
}
}
//+------------------------------------------------------------------+
//| Convert news holding status to text |
//+------------------------------------------------------------------+
string NewsHoldingStatusToString(const ENUM_NEWS_HOLDING_STATUS status)
{
switch(status)
{
case NEWS_HOLDING_ALLOWED:
return("HOLDING ALLOWED");
case NEWS_HOLDING_WARNING:
return("EVENT APPROACHING");
case NEWS_HOLDING_PROTECTION:
return("PROTECTION REQUIRED");
case NEWS_HOLDING_CLEAR:
default:
return("CLEAR");
}
}
//+------------------------------------------------------------------+
//| Check successful position-close return code |
//+------------------------------------------------------------------+
bool IsSuccessfulCloseRetcode(const uint retcode)
{
if(retcode == TRADE_RETCODE_DONE ||
retcode == TRADE_RETCODE_POSITION_CLOSED)
return(true);
return(false);
}
//+------------------------------------------------------------------+
//| Close position by ticket |
//+------------------------------------------------------------------+
bool ClosePositionByTicket(const ulong ticket,
const string reason)
{
ResetLastError();
// Confirm that the position still exists before attempting to close it.
if(!PositionSelectByTicket(ticket))
{
int errorCode = GetLastError();
// A position may already have disappeared because another protection
// action or manual operation closed it.
Print("Prop Firm Guard: Position ",
ticket,
" is no longer available. Error: ",
errorCode);
return(true);
}
string symbol;
if(!PositionGetString(POSITION_SYMBOL,
symbol))
{
Print("Prop Firm Guard: Could not read symbol for position ",
ticket,
". Error: ",
GetLastError());
return(false);
}
// Configure CTrade using the filling policies allowed for this symbol.
if(!g_trade.SetTypeFillingBySymbol(symbol))
{
Print("Prop Firm Guard: Could not determine filling policy for ",
symbol,
".");
return(false);
}
ResetLastError();
// PositionClose() performs the basic request submission, but its Boolean
// result alone is not sufficient to confirm execution by the trade server.
bool requestAccepted = g_trade.PositionClose(ticket);
uint retcode = g_trade.ResultRetcode();
string retcodeDescription = g_trade.ResultRetcodeDescription();
if(!requestAccepted)
{
Print("Prop Firm Guard: Close request failed for ticket ",
ticket,
" [",
symbol,
"]. Reason: ",
reason,
". Retcode: ",
retcode,
" - ",
retcodeDescription,
". Runtime error: ",
GetLastError());
return(false);
}
// Always inspect the server return code after PositionClose().
if(!IsSuccessfulCloseRetcode(retcode))
{
Print("Prop Firm Guard: Trade server did not confirm complete closure of ticket ",
ticket,
" [",
symbol,
"]. Reason: ",
reason,
". Retcode: ",
retcode,
" - ",
retcodeDescription);
return(false);
}
// Verify that the position is no longer present after the synchronous
// close operation.
ResetLastError();
if(PositionSelectByTicket(ticket))
{
Print("Prop Firm Guard: Position ",
ticket,
" [",
symbol,
"] still exists after close request.");
return(false);
}
Print("Prop Firm Guard: Position ",
ticket,
" [",
symbol,
"] closed successfully. Reason: ",
reason,
".");
return(true);
}
//+------------------------------------------------------------------+
//| Close all open positions |
//+------------------------------------------------------------------+
bool CloseAllPositions(const string reason)
{
int totalPositions = PositionsTotal();
if(totalPositions == 0)
return(true);
ulong tickets[];
if(ArrayResize(tickets, totalPositions) != totalPositions)
{
Print("Prop Firm Guard: Could not allocate position ticket array.");
return(false);
}
int ticketCount = 0;
// Build a stable ticket list before modifying the open-position pool.
for(int i = 0; i < totalPositions; i++)
{
ResetLastError();
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
{
Print("Prop Firm Guard: Could not retrieve position ticket at index ",
i,
". Error: ",
GetLastError());
continue;
}
tickets[ticketCount] = ticket;
ticketCount++;
}
bool allClosed = true;
for(int i = 0; i < ticketCount; i++)
{
if(!ClosePositionByTicket(tickets[i],
reason))
allClosed = false;
}
return(allClosed);
}
//+------------------------------------------------------------------+
//| Close news-affected positions |
//+------------------------------------------------------------------+
bool CloseNewsAffectedPositions()
{
ulong tickets[];
int totalMatches = ArraySize(g_newsPositionMatches);
int ticketCount = 0;
// First collect all affected tickets so closing one position cannot
// invalidate our traversal of the match array.
for(int i = 0; i < totalMatches; i++)
{
if(!g_newsPositionMatches[i].protectionRequired)
continue;
int newSize = ticketCount + 1;
if(ArrayResize(tickets, newSize) != newSize)
{
Print("Prop Firm Guard: Could not expand affected-position ticket array.");
return(false);
}
tickets[ticketCount] = g_newsPositionMatches[i].ticket;
ticketCount++;
}
if(ticketCount == 0)
return(true);
bool allClosed = true;
string reason =
"News restriction: " +
g_newsState.eventCurrency +
" " +
g_newsState.eventName;
for(int i = 0; i < ticketCount; i++)
{
if(!ClosePositionByTicket(tickets[i],
reason))
allClosed = false;
}
return(allClosed);
}
//+------------------------------------------------------------------+
//| Handle daily drawdown protection |
//+------------------------------------------------------------------+
void HandleDailyDrawdownProtection()
{
if(!g_dailySession.balanceConfirmed)
return;
if(g_dailyDrawdown.status != COMPLIANCE_BREACHED)
{
g_dailyBreachWarned = false;
return;
}
switch(InpDailyDrawdownAction)
{
case PROTECTION_WARN_ONLY:
if(!g_dailyBreachWarned)
{
Alert("Prop Firm Guard: Daily drawdown limit breached.");
Print("Prop Firm Guard: Daily drawdown breached. Action: WARN ONLY.");
g_dailyBreachWarned = true;
}
break;
case PROTECTION_CLOSE_AFFECTED:
case PROTECTION_CLOSE_ALL:
// Daily drawdown is an account-wide rule, therefore every open
// position contributes to the breached account condition.
if(PositionsTotal() > 0)
{
if(!CloseAllPositions("Daily drawdown breach"))
Print("Prop Firm Guard: One or more positions could not be closed after daily drawdown breach.");
}
break;
}
}
//+------------------------------------------------------------------+
//| Handle overall drawdown protection |
//+------------------------------------------------------------------+
void HandleOverallDrawdownProtection()
{
if(g_overallDrawdown.status != COMPLIANCE_BREACHED)
{
g_overallBreachWarned = false;
return;
}
switch(InpOverallDrawdownAction)
{
case PROTECTION_WARN_ONLY:
if(!g_overallBreachWarned)
{
Alert("Prop Firm Guard: Overall drawdown limit breached.");
Print("Prop Firm Guard: Overall drawdown breached. Action: WARN ONLY.");
g_overallBreachWarned = true;
}
break;
case PROTECTION_CLOSE_AFFECTED:
case PROTECTION_CLOSE_ALL:
// Overall drawdown applies to the complete trading account.
if(PositionsTotal() > 0)
{
if(!CloseAllPositions("Overall drawdown breach"))
Print("Prop Firm Guard: One or more positions could not be closed after overall drawdown breach.");
}
break;
}
}
//+------------------------------------------------------------------+
//| Handle news protection |
//+------------------------------------------------------------------+
void HandleNewsProtection()
{
// Protection is needed only when Phase 11 determined that holding is
// prohibited and the restricted window is currently active.
if(g_newsState.holdingStatus != NEWS_HOLDING_PROTECTION)
return;
if(!g_newsState.eventFound)
return;
switch(InpNewsRestrictionAction)
{
case PROTECTION_WARN_ONLY:
// Warn only once for this particular Economic Calendar event.
if(g_lastNewsWarnedEventId != g_newsState.eventId)
{
Alert("Prop Firm Guard: News holding restriction active for ",
g_newsState.eventCurrency,
" ",
g_newsState.eventName,
".");
Print("Prop Firm Guard: News protection required. Action: WARN ONLY.");
g_lastNewsWarnedEventId = g_newsState.eventId;
}
break;
case PROTECTION_CLOSE_AFFECTED:
if(g_newsState.protectionPositions > 0)
{
if(!CloseNewsAffectedPositions())
Print("Prop Firm Guard: One or more news-affected positions could not be closed.");
}
break;
case PROTECTION_CLOSE_ALL:
if(PositionsTotal() > 0)
{
string reason =
"News holding restriction: " +
g_newsState.eventCurrency +
" " +
g_newsState.eventName;
if(!CloseAllPositions(reason))
Print("Prop Firm Guard: One or more positions could not be closed during news restriction.");
}
break;
}
}
//+------------------------------------------------------------------+
//| Manage protection actions |
//+------------------------------------------------------------------+
void ManageProtectionActions()
{
// Financial breaches receive priority because they represent direct
// account-rule violations.
HandleDailyDrawdownProtection();
HandleOverallDrawdownProtection();
// News protection then applies to any positions that remain open.
HandleNewsProtection();
}
//+------------------------------------------------------------------+
//| Convert protection action to text |
//+------------------------------------------------------------------+
string ProtectionActionToString(const ENUM_PROTECTION_ACTION action)
{
switch(action)
{
case PROTECTION_CLOSE_AFFECTED:
return("CLOSE AFFECTED");
case PROTECTION_CLOSE_ALL:
return("CLOSE ALL");
case PROTECTION_WARN_ONLY:
default:
return("WARN ONLY");
}
}
//+------------------------------------------------------------------+
//| Get news trading-window text |
//+------------------------------------------------------------------+
string GetNewsTradingWindowText()
{
if(!InpEnableNewsFilter)
return("DISABLED");
if(!g_newsState.eventFound)
return("SAFE");
if(g_newsState.restrictedWindow)
return("RESTRICTED");
return("SAFE");
}
//+------------------------------------------------------------------+
//| Get configured news holding rule |
//+------------------------------------------------------------------+
string GetNewsHoldingRuleText()
{
if(InpAllowNewsHolding)
return("ALLOWED");
return("NOT ALLOWED");
}
//+------------------------------------------------------------------+
//| Get overall guard status |
//+------------------------------------------------------------------+
ENUM_GUARD_STATUS GetOverallGuardStatus()
{
// A breach always has the highest priority.
if(g_dailyDrawdown.status == COMPLIANCE_BREACHED ||
g_overallDrawdown.status == COMPLIANCE_BREACHED)
return(GUARD_BREACHED);
// Critical conditions take priority over warnings and target success.
if(g_dailyDrawdown.status == COMPLIANCE_CRITICAL ||
g_overallDrawdown.status == COMPLIANCE_CRITICAL)
return(GUARD_CRITICAL);
// A warning still takes priority over a reached profit target because
// the account may be profitable while simultaneously approaching a loss limit.
if(g_dailyDrawdown.status == COMPLIANCE_WARNING ||
g_overallDrawdown.status == COMPLIANCE_WARNING)
return(GUARD_WARNING);
// Report target completion only when no higher-risk condition exists.
if(g_profitTarget.status == TARGET_REACHED)
return(GUARD_TARGET_REACHED);
return(GUARD_SAFE);
}
//+------------------------------------------------------------------+
//| Convert guard status to text |
//+------------------------------------------------------------------+
string GuardStatusToString(const ENUM_GUARD_STATUS status)
{
switch(status)
{
case GUARD_BREACHED:
return("BREACHED");
case GUARD_CRITICAL:
return("CRITICAL");
case GUARD_WARNING:
return("WARNING");
case GUARD_TARGET_REACHED:
return("TARGET REACHED");
case GUARD_SAFE:
default:
return("SAFE");
}
}
//+------------------------------------------------------------------+
//| Get guard status color |
//+------------------------------------------------------------------+
color GetGuardStatusColor(const ENUM_GUARD_STATUS status)
{
switch(status)
{
case GUARD_BREACHED:
case GUARD_CRITICAL:
return(clrRed);
case GUARD_WARNING:
return(clrDarkOrange);
case GUARD_TARGET_REACHED:
return(clrGreen);
case GUARD_SAFE:
default:
return(clrGreen);
}
}
//+------------------------------------------------------------------+
//| Open database |
//+------------------------------------------------------------------+
bool OpenDatabase()
{
// Open the database for reading and writing, creating the file
// automatically when Prop Firm Guard runs for the first time.
ResetLastError();
g_database = DatabaseOpen(g_databaseName,
DATABASE_OPEN_READWRITE |
DATABASE_OPEN_CREATE);
if(g_database == INVALID_HANDLE)
{
Print("Prop Firm Guard: Could not open database ",
g_databaseName,
". Error: ",
GetLastError());
return(false);
}
Print("Prop Firm Guard: Database opened successfully: ",
g_databaseName);
return(true);
}
2026-08-28 18:38:10 +03:00
/*
//+------------------------------------------------------------------+
//| Ensure database tables |
//+------------------------------------------------------------------+
bool EnsureDatabaseTables()
{
// Account settings persist the main prop-firm rules independently
// for each combination of account login and trading server.
string settingsSql =
"CREATE TABLE IF NOT EXISTS settings ("
"account_login INTEGER NOT NULL,"
"account_server TEXT NOT NULL,"
"initial_balance REAL NOT NULL,"
"daily_drawdown_percent REAL NOT NULL,"
"overall_drawdown_percent REAL NOT NULL,"
"profit_target_percent REAL NOT NULL,"
"updated_at INTEGER NOT NULL,"
"PRIMARY KEY(account_login, account_server)"
");";
// Daily sessions hold values that belong to one specific trading day.
// The daily starting values remain zero until Phase 3 confirms them.
string sessionsSql =
"CREATE TABLE IF NOT EXISTS daily_sessions ("
"account_login INTEGER NOT NULL,"
"account_server TEXT NOT NULL,"
"trading_date TEXT NOT NULL,"
"starting_balance REAL NOT NULL DEFAULT 0,"
"starting_equity REAL NOT NULL DEFAULT 0,"
"balance_confirmed INTEGER NOT NULL DEFAULT 0,"
"created_at INTEGER NOT NULL,"
"updated_at INTEGER NOT NULL,"
"PRIMARY KEY(account_login, account_server, trading_date)"
");";
ResetLastError();
if(!DatabaseExecute(g_database, settingsSql))
{
Print("Prop Firm Guard: Could not create settings table. Error: ",
GetLastError());
return(false);
}
ResetLastError();
if(!DatabaseExecute(g_database, sessionsSql))
{
Print("Prop Firm Guard: Could not create daily_sessions table. Error: ",
GetLastError());
return(false);
}
Print("Prop Firm Guard: Database tables are ready.");
return(true);
}
2026-08-28 18:38:10 +03:00
*/
//+------------------------------------------------------------------+
//| Close database |
//+------------------------------------------------------------------+
void CloseDatabase()
{
// Do nothing if no valid database connection currently exists.
if(g_database == INVALID_HANDLE)
return;
ResetLastError();
DatabaseClose(g_database);
int errorCode = GetLastError();
if(errorCode != 0)
{
Print("Prop Firm Guard: Database close reported error: ",
errorCode);
}
else
{
Print("Prop Firm Guard: Database closed successfully.");
}
// Never keep a handle after the database has been closed.
g_database = INVALID_HANDLE;
}
2026-08-28 18:38:10 +03:00
//+------------------------------------------------------------------+
//| Check whether database column exists |
//+------------------------------------------------------------------+
bool DatabaseColumnExists(const string tableName,
const string columnName)
{
string sql = "PRAGMA table_info(" + tableName + ");";
ResetLastError();
int request = DatabasePrepare(g_database,
sql);
if(request == INVALID_HANDLE)
{
Print("Prop Firm Guard: Could not inspect table ",
tableName,
". Error: ",
GetLastError());
return(false);
}
bool columnFound = false;
while(true)
{
ResetLastError();
if(!DatabaseRead(request))
{
int errorCode = GetLastError();
if(errorCode != ERR_DATABASE_NO_MORE_DATA)
{
Print("Prop Firm Guard: Could not read schema for ",
tableName,
". Error: ",
errorCode);
}
break;
}
string currentColumn;
// PRAGMA table_info returns the column name at index 1.
if(!DatabaseColumnText(request,
1,
currentColumn))
{
Print("Prop Firm Guard: Could not read database column name. Error: ",
GetLastError());
break;
}
if(currentColumn == columnName)
{
columnFound = true;
break;
}
}
FinalizeDatabaseRequest(request);
return(columnFound);
}
//+------------------------------------------------------------------+
//| Ensure database column exists |
//+------------------------------------------------------------------+
bool EnsureDatabaseColumn(const string tableName,
const string columnName,
const string columnDefinition)
{
if(DatabaseColumnExists(tableName,
columnName))
return(true);
string sql =
"ALTER TABLE " +
tableName +
" ADD COLUMN " +
columnName +
" " +
columnDefinition +
";";
ResetLastError();
if(!DatabaseExecute(g_database,
sql))
{
Print("Prop Firm Guard: Could not add column ",
columnName,
" to ",
tableName,
". Error: ",
GetLastError());
return(false);
}
Print("Prop Firm Guard: Added database column ",
tableName,
".",
columnName,
".");
return(true);
}
//+------------------------------------------------------------------+
//| Ensure runtime daily-session columns exist |
//+------------------------------------------------------------------+
bool EnsureDailySessionRuntimeColumns()
{
if(!EnsureDatabaseColumn("daily_sessions",
"latest_balance",
"REAL NOT NULL DEFAULT 0"))
return(false);
if(!EnsureDatabaseColumn("daily_sessions",
"latest_equity",
"REAL NOT NULL DEFAULT 0"))
return(false);
if(!EnsureDatabaseColumn("daily_sessions",
"daily_loss",
"REAL NOT NULL DEFAULT 0"))
return(false);
if(!EnsureDatabaseColumn("daily_sessions",
"maximum_daily_loss_reached",
"REAL NOT NULL DEFAULT 0"))
return(false);
if(!EnsureDatabaseColumn("daily_sessions",
"target_progress",
"REAL NOT NULL DEFAULT 0"))
return(false);
if(!EnsureDatabaseColumn("daily_sessions",
"session_status",
"TEXT NOT NULL DEFAULT 'AWAITING_CONFIRMATION'"))
return(false);
return(true);
}
//+------------------------------------------------------------------+
//| Ensure database tables exist |
//+------------------------------------------------------------------+
bool EnsureDatabaseTables()
{
string settingsTable =
"CREATE TABLE IF NOT EXISTS settings ("
"account_login INTEGER NOT NULL,"
"account_server TEXT NOT NULL,"
"initial_balance REAL NOT NULL,"
"daily_drawdown_percent REAL NOT NULL,"
"overall_drawdown_percent REAL NOT NULL,"
"profit_target_percent REAL NOT NULL,"
"updated_at INTEGER NOT NULL,"
"PRIMARY KEY(account_login, account_server)"
");";
string dailySessionsTable =
"CREATE TABLE IF NOT EXISTS daily_sessions ("
"account_login INTEGER NOT NULL,"
"account_server TEXT NOT NULL,"
"trading_date TEXT NOT NULL,"
"starting_balance REAL NOT NULL,"
"starting_equity REAL NOT NULL,"
"balance_confirmed INTEGER NOT NULL,"
"latest_balance REAL NOT NULL DEFAULT 0,"
"latest_equity REAL NOT NULL DEFAULT 0,"
"daily_loss REAL NOT NULL DEFAULT 0,"
"maximum_daily_loss_reached REAL NOT NULL DEFAULT 0,"
"target_progress REAL NOT NULL DEFAULT 0,"
"session_status TEXT NOT NULL DEFAULT 'AWAITING_CONFIRMATION',"
"created_at INTEGER NOT NULL,"
"updated_at INTEGER NOT NULL,"
"PRIMARY KEY(account_login, account_server, trading_date)"
");";
ResetLastError();
if(!DatabaseExecute(g_database,
settingsTable))
{
Print("Prop Firm Guard: Could not create settings table. Error: ",
GetLastError());
return(false);
}
ResetLastError();
if(!DatabaseExecute(g_database,
dailySessionsTable))
{
Print("Prop Firm Guard: Could not create daily_sessions table. Error: ",
GetLastError());
return(false);
}
// CREATE TABLE IF NOT EXISTS does not add missing columns to an older
// table, so migrate databases created by earlier EA versions.
if(!EnsureDailySessionRuntimeColumns())
return(false);
return(true);
}
//+------------------------------------------------------------------+
//| Check for new trading day |
//+------------------------------------------------------------------+
void CheckForNewTradingDay()
{
string currentDate = GetTradingDate();
// The current database session already belongs to today.
if(g_hasDailySession &&
g_dailySession.tradingDate == currentDate)
return;
Print("Prop Firm Guard: New trading day detected: ",
currentDate);
// Load today's record if one already exists, or create a new
// unconfirmed session when this is the first launch today.
if(!InitializeDailySession())
{
Print("Prop Firm Guard: Could not initialize the new daily session.");
return;
}
// Rebuild only the starting-balance controls so their state matches
// the newly loaded daily session.
DeleteDailySessionControls();
if(!CreateDailySessionControls())
{
Print("Prop Firm Guard: Could not create daily session controls.");
}
}
//+------------------------------------------------------------------+
//| Confirm daily starting balance |
//+------------------------------------------------------------------+
bool ConfirmDailyStartingBalance()
{
// Never allow normal confirmation logic to overwrite a value that
// has already been confirmed for the current trading day.
if(g_dailySession.balanceConfirmed)
{
Print("Prop Firm Guard: Today's starting balance is already confirmed.");
return(true);
}
string balanceText;
ResetLastError();
// Read the amount entered in the dashboard edit field.
if(!ObjectGetString(0,
g_startBalanceEdit,
OBJPROP_TEXT,
0,
balanceText))
{
Print("Prop Firm Guard: Could not read starting balance field. Error: ",
GetLastError());
return(false);
}
double startingBalance = StringToDouble(balanceText);
if(startingBalance <= 0.0)
{
Alert("Prop Firm Guard: Enter a valid starting balance greater than zero.");
return(false);
}
// Store the user-confirmed starting values in memory.
g_dailySession.startingBalance = startingBalance;
g_dailySession.startingEquity = g_accountState.equity;
g_dailySession.balanceConfirmed = true;
g_dailySession.updatedAt = TimeCurrent();
// Persist the confirmed values immediately.
if(!SaveDailySession())
{
Print("Prop Firm Guard: Could not save confirmed daily session.");
// Do not leave memory showing a confirmed state that SQLite failed
// to preserve.
g_dailySession.balanceConfirmed = false;
return(false);
}
Print("Prop Firm Guard: Daily starting balance confirmed at ",
DoubleToString(g_dailySession.startingBalance, 2),
" for ",
g_dailySession.tradingDate,
".");
// The confirmation controls are no longer required after the value
// has been safely stored.
DeleteDailySessionControls();
UpdateDashboard();
return(true);
}
//+------------------------------------------------------------------+
//| Create daily session controls |
//+------------------------------------------------------------------+
bool CreateDailySessionControls()
{
// A confirmed session does not require editable controls.
if(g_dailySession.balanceConfirmed)
return(true);
int controlX = InpDashboardX + 18;
int controlY = InpDashboardY + 120;
// Suggest the current balance without treating it as confirmed.
string suggestedBalance =
DoubleToString(g_accountState.balance, 2);
if(!CreateStartingBalanceEdit(controlX,
controlY,
suggestedBalance))
return(false);
if(!CreateConfirmBalanceButton(controlX + 130,
controlY))
return(false);
return(true);
}
//+------------------------------------------------------------------+
//| Create starting balance edit |
//+------------------------------------------------------------------+
bool CreateStartingBalanceEdit(const int x,
const int y,
const string suggestedBalance)
{
// Create an editable field where the trader may accept or change
// the suggested starting balance.
if(!ObjectCreate(0,
g_startBalanceEdit,
OBJ_EDIT,
0,
0,
0))
{
Print("Prop Firm Guard: Could not create starting balance field. Error: ",
GetLastError());
return(false);
}
if(!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_CORNER,
CORNER_LEFT_UPPER) ||
!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_XDISTANCE,
x) ||
!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_YDISTANCE,
y) ||
!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_XSIZE,
120) ||
!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_YSIZE,
20) ||
!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_FONTSIZE,
9) ||
!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_COLOR,
clrBlack) ||
!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_BGCOLOR,
clrWhite) ||
!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_BORDER_COLOR,
clrSilver) ||
!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_ALIGN,
ALIGN_RIGHT) ||
!ObjectSetInteger(0,
g_startBalanceEdit,
OBJPROP_HIDDEN,
true))
{
Print("Prop Firm Guard: Could not configure starting balance field. Error: ",
GetLastError());
return(false);
}
if(!ObjectSetString(0,
g_startBalanceEdit,
OBJPROP_FONT,
"Tahoma") ||
!ObjectSetString(0,
g_startBalanceEdit,
OBJPROP_TEXT,
suggestedBalance))
{
Print("Prop Firm Guard: Could not initialize starting balance field. Error: ",
GetLastError());
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Create confirm balance button |
//+------------------------------------------------------------------+
bool CreateConfirmBalanceButton(const int x,
const int y)
{
// Create the button used to persist the daily starting balance.
if(!ObjectCreate(0,
g_confirmButton,
OBJ_BUTTON,
0,
0,
0))
{
Print("Prop Firm Guard: Could not create confirmation button. Error: ",
GetLastError());
return(false);
}
if(!ObjectSetInteger(0,
g_confirmButton,
OBJPROP_CORNER,
CORNER_LEFT_UPPER) ||
!ObjectSetInteger(0,
g_confirmButton,
OBJPROP_XDISTANCE,
x) ||
!ObjectSetInteger(0,
g_confirmButton,
OBJPROP_YDISTANCE,
y) ||
!ObjectSetInteger(0,
g_confirmButton,
OBJPROP_XSIZE,
100) ||
!ObjectSetInteger(0,
g_confirmButton,
OBJPROP_YSIZE,
20) ||
!ObjectSetInteger(0,
g_confirmButton,
OBJPROP_FONTSIZE,
9) ||
!ObjectSetInteger(0,
g_confirmButton,
OBJPROP_COLOR,
clrBlack) ||
!ObjectSetInteger(0,
g_confirmButton,
OBJPROP_BGCOLOR,
clrWhite) ||
!ObjectSetInteger(0,
g_confirmButton,
OBJPROP_BORDER_COLOR,
clrSilver) ||
!ObjectSetInteger(0,
g_confirmButton,
OBJPROP_HIDDEN,
true))
{
Print("Prop Firm Guard: Could not configure confirmation button. Error: ",
GetLastError());
return(false);
}
if(!ObjectSetString(0,
g_confirmButton,
OBJPROP_FONT,
"Tahoma") ||
!ObjectSetString(0,
g_confirmButton,
OBJPROP_TEXT,
"Confirm"))
{
Print("Prop Firm Guard: Could not initialize confirmation button. Error: ",
GetLastError());
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Delete daily session controls |
//+------------------------------------------------------------------+
void DeleteDailySessionControls()
{
// Delete the editable balance field only when it currently exists.
if(ObjectFind(0, g_startBalanceEdit) >= 0)
{
if(!ObjectDelete(0, g_startBalanceEdit))
{
Print("Prop Firm Guard: Could not delete starting balance field. Error: ",
GetLastError());
}
}
// Delete the confirmation button only when it currently exists.
if(ObjectFind(0, g_confirmButton) >= 0)
{
if(!ObjectDelete(0, g_confirmButton))
{
Print("Prop Firm Guard: Could not delete confirmation button. Error: ",
GetLastError());
}
}
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Get trading date |
//+------------------------------------------------------------------+
string GetTradingDate()
{
// TimeCurrent() represents the trading server time known by the terminal.
// Phase 3 will use this server-based date for daily session management.
datetime serverTime = TimeCurrent();
return(TimeToString(serverTime, TIME_DATE));
}
//+------------------------------------------------------------------+
//| Initialize account settings |
//+------------------------------------------------------------------+
bool InitializeAccountSettings()
{
bool settingsFound = LoadAccountSettings();
// Associate these settings with the current trading account.
g_accountSettings.accountLogin = g_accountState.login;
g_accountSettings.accountServer = AccountInfoString(ACCOUNT_SERVER);
// Synchronize the current EA inputs with persistent account settings.
g_accountSettings.initialBalance = InpInitialAccountBalance;
g_accountSettings.dailyDrawdownPercent = InpDailyDrawdownPercent;
g_accountSettings.overallDrawdownPercent = InpOverallDrawdownPercent;
g_accountSettings.profitTargetPercent = InpProfitTargetPercent;
g_accountSettings.updatedAt = TimeCurrent();
// Store the current configuration so SQLite remains synchronized.
if(!SaveAccountSettings())
return(false);
if(settingsFound)
Print("Prop Firm Guard: Account settings restored and synchronized.");
else
Print("Prop Firm Guard: New account settings saved.");
return(true);
}
//+------------------------------------------------------------------+
//| Save account settings |
//+------------------------------------------------------------------+
bool SaveAccountSettings()
{
string sql =
"INSERT OR REPLACE INTO settings ("
"account_login,"
"account_server,"
"initial_balance,"
"daily_drawdown_percent,"
"overall_drawdown_percent,"
"profit_target_percent,"
"updated_at"
") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7);";
int request = DatabasePrepare(g_database, sql);
if(request == INVALID_HANDLE)
{
Print("Prop Firm Guard: Could not prepare settings save request. Error: ",
GetLastError());
return(false);
}
// Bind each value separately so text and numeric values remain data
// rather than being concatenated directly into the SQL statement.
if(!DatabaseBind(request, 0, g_accountSettings.accountLogin) ||
!DatabaseBind(request, 1, g_accountSettings.accountServer) ||
!DatabaseBind(request, 2, g_accountSettings.initialBalance) ||
!DatabaseBind(request, 3, g_accountSettings.dailyDrawdownPercent) ||
!DatabaseBind(request, 4, g_accountSettings.overallDrawdownPercent) ||
!DatabaseBind(request, 5, g_accountSettings.profitTargetPercent) ||
!DatabaseBind(request, 6, g_accountSettings.updatedAt))
{
Print("Prop Firm Guard: Could not bind settings values. Error: ",
GetLastError());
FinalizeDatabaseRequest(request);
return(false);
}
// INSERT/UPDATE statements do not return rows. In MQL5 a successful
// DatabaseRead() therefore finishes with ERR_DATABASE_NO_MORE_DATA.
if(!ExecuteDatabaseRequest(request, "saving account settings"))
return(false);
return(true);
}
//+------------------------------------------------------------------+
//| Load account settings |
//+------------------------------------------------------------------+
bool LoadAccountSettings()
{
string accountServer = AccountInfoString(ACCOUNT_SERVER);
string sql =
"SELECT "
"account_login,"
"account_server,"
"initial_balance,"
"daily_drawdown_percent,"
"overall_drawdown_percent,"
"profit_target_percent,"
"updated_at "
"FROM settings "
"WHERE account_login = ?1 AND account_server = ?2;";
int request = DatabasePrepare(g_database, sql);
if(request == INVALID_HANDLE)
{
Print("Prop Firm Guard: Could not prepare settings load request. Error: ",
GetLastError());
return(false);
}
if(!DatabaseBind(request, 0, g_accountState.login) ||
!DatabaseBind(request, 1, accountServer))
{
Print("Prop Firm Guard: Could not bind settings search values. Error: ",
GetLastError());
FinalizeDatabaseRequest(request);
return(false);
}
ResetLastError();
// A true result means the SELECT statement returned its first row.
if(!DatabaseRead(request))
{
int errorCode = GetLastError();
FinalizeDatabaseRequest(request);
// No row simply means this account has not been saved before.
if(errorCode == ERR_DATABASE_NO_MORE_DATA)
return(false);
Print("Prop Firm Guard: Could not read account settings. Error: ",
errorCode);
return(false);
}
long accountLogin;
string storedServer;
double initialBalance;
double dailyDrawdown;
double overallDrawdown;
double profitTarget;
long updatedAt;
// Read every selected column and verify every database conversion.
if(!DatabaseColumnLong(request, 0, accountLogin) ||
!DatabaseColumnText(request, 1, storedServer) ||
!DatabaseColumnDouble(request, 2, initialBalance) ||
!DatabaseColumnDouble(request, 3, dailyDrawdown) ||
!DatabaseColumnDouble(request, 4, overallDrawdown) ||
!DatabaseColumnDouble(request, 5, profitTarget) ||
!DatabaseColumnLong(request, 6, updatedAt))
{
Print("Prop Firm Guard: Could not read stored settings columns. Error: ",
GetLastError());
FinalizeDatabaseRequest(request);
return(false);
}
g_accountSettings.accountLogin = accountLogin;
g_accountSettings.accountServer = storedServer;
g_accountSettings.initialBalance = initialBalance;
g_accountSettings.dailyDrawdownPercent = dailyDrawdown;
g_accountSettings.overallDrawdownPercent = overallDrawdown;
g_accountSettings.profitTargetPercent = profitTarget;
g_accountSettings.updatedAt = (datetime)updatedAt;
FinalizeDatabaseRequest(request);
return(true);
}
//+------------------------------------------------------------------+
//| Initialize daily session |
//+------------------------------------------------------------------+
bool InitializeDailySession()
{
string tradingDate = GetTradingDate();
2026-08-28 18:38:10 +03:00
if(DailySessionExists(tradingDate))
{
2026-08-28 18:38:10 +03:00
if(!LoadDailySession())
return(false);
g_hasDailySession = true;
Print("Prop Firm Guard: Daily session restored for ",
tradingDate,
2026-08-28 18:38:10 +03:00
".");
return(true);
}
2026-08-28 18:38:10 +03:00
g_dailySession.accountLogin = g_accountState.login;
g_dailySession.accountServer = AccountInfoString(ACCOUNT_SERVER);
g_dailySession.tradingDate = tradingDate;
g_dailySession.startingBalance = 0.0;
g_dailySession.startingEquity = 0.0;
g_dailySession.balanceConfirmed = false;
2026-08-28 18:38:10 +03:00
g_dailySession.latestBalance = g_accountState.balance;
g_dailySession.latestEquity = g_accountState.equity;
g_dailySession.dailyLoss = 0.0;
g_dailySession.maximumDailyLossReached = 0.0;
g_dailySession.targetProgress = 0.0;
g_dailySession.sessionStatus = "AWAITING_CONFIRMATION";
g_dailySession.createdAt = TimeCurrent();
g_dailySession.updatedAt = TimeCurrent();
if(!SaveDailySession())
return(false);
g_hasDailySession = true;
Print("Prop Firm Guard: New daily session created for ",
tradingDate,
2026-08-28 18:38:10 +03:00
".");
return(true);
}
//+------------------------------------------------------------------+
//| Save daily session |
//+------------------------------------------------------------------+
bool SaveDailySession()
{
string sql =
"INSERT OR REPLACE INTO daily_sessions ("
"account_login,"
"account_server,"
"trading_date,"
"starting_balance,"
"starting_equity,"
"balance_confirmed,"
2026-08-28 18:38:10 +03:00
"latest_balance,"
"latest_equity,"
"daily_loss,"
"maximum_daily_loss_reached,"
"target_progress,"
"session_status,"
"created_at,"
"updated_at"
2026-08-28 18:38:10 +03:00
") VALUES ("
"?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14"
");";
2026-08-28 18:38:10 +03:00
ResetLastError();
int request = DatabasePrepare(g_database,
sql,
g_dailySession.accountLogin,
g_dailySession.accountServer,
g_dailySession.tradingDate,
g_dailySession.startingBalance,
g_dailySession.startingEquity,
g_dailySession.balanceConfirmed ? 1 : 0,
g_dailySession.latestBalance,
g_dailySession.latestEquity,
g_dailySession.dailyLoss,
g_dailySession.maximumDailyLossReached,
g_dailySession.targetProgress,
g_dailySession.sessionStatus,
(long)g_dailySession.createdAt,
(long)g_dailySession.updatedAt);
if(request == INVALID_HANDLE)
{
Print("Prop Firm Guard: Could not prepare daily session save request. Error: ",
GetLastError());
return(false);
}
2026-08-28 18:38:10 +03:00
return(ExecuteDatabaseRequest(request,
"saving daily session"));
}
//+------------------------------------------------------------------+
//| Load daily session |
//+------------------------------------------------------------------+
2026-08-28 18:38:10 +03:00
bool LoadDailySession()
{
string sql =
"SELECT "
"account_login,"
"account_server,"
"trading_date,"
"starting_balance,"
"starting_equity,"
"balance_confirmed,"
2026-08-28 18:38:10 +03:00
"latest_balance,"
"latest_equity,"
"daily_loss,"
"maximum_daily_loss_reached,"
"target_progress,"
"session_status,"
"created_at,"
"updated_at "
"FROM daily_sessions "
2026-08-28 18:38:10 +03:00
"WHERE account_login=?1 "
"AND account_server=?2 "
"AND trading_date=?3;";
2026-08-28 18:38:10 +03:00
string tradingDate = GetTradingDate();
string accountServer = AccountInfoString(ACCOUNT_SERVER);
2026-08-28 18:38:10 +03:00
ResetLastError();
2026-08-28 18:38:10 +03:00
int request = DatabasePrepare(g_database,
sql,
g_accountState.login,
accountServer,
tradingDate);
2026-08-28 18:38:10 +03:00
if(request == INVALID_HANDLE)
{
2026-08-28 18:38:10 +03:00
Print("Prop Firm Guard: Could not prepare daily session load request. Error: ",
GetLastError());
return(false);
}
ResetLastError();
if(!DatabaseRead(request))
{
int errorCode = GetLastError();
2026-08-28 18:38:10 +03:00
if(errorCode != ERR_DATABASE_NO_MORE_DATA)
{
Print("Prop Firm Guard: Could not load daily session. Error: ",
errorCode);
}
2026-08-28 18:38:10 +03:00
FinalizeDatabaseRequest(request);
return(false);
}
2026-08-28 18:38:10 +03:00
long accountLogin;
string loadedServer;
string loadedDate;
double startingBalance;
double startingEquity;
2026-08-28 18:38:10 +03:00
int balanceConfirmed;
double latestBalance;
double latestEquity;
double dailyLoss;
double maximumDailyLossReached;
double targetProgress;
string sessionStatus;
long createdAt;
long updatedAt;
bool loaded =
DatabaseColumnLong(request, 0, accountLogin) &&
DatabaseColumnText(request, 1, loadedServer) &&
DatabaseColumnText(request, 2, loadedDate) &&
DatabaseColumnDouble(request, 3, startingBalance) &&
DatabaseColumnDouble(request, 4, startingEquity) &&
DatabaseColumnInteger(request, 5, balanceConfirmed) &&
DatabaseColumnDouble(request, 6, latestBalance) &&
DatabaseColumnDouble(request, 7, latestEquity) &&
DatabaseColumnDouble(request, 8, dailyLoss) &&
DatabaseColumnDouble(request, 9, maximumDailyLossReached) &&
DatabaseColumnDouble(request, 10, targetProgress) &&
DatabaseColumnText(request, 11, sessionStatus) &&
DatabaseColumnLong(request, 12, createdAt) &&
DatabaseColumnLong(request, 13, updatedAt);
if(!loaded)
{
Print("Prop Firm Guard: Could not read daily session columns. Error: ",
GetLastError());
FinalizeDatabaseRequest(request);
2026-08-28 18:38:10 +03:00
return(false);
}
2026-08-28 18:38:10 +03:00
g_dailySession.accountLogin = accountLogin;
g_dailySession.accountServer = loadedServer;
g_dailySession.tradingDate = loadedDate;
g_dailySession.startingBalance = startingBalance;
g_dailySession.startingEquity = startingEquity;
g_dailySession.balanceConfirmed = (balanceConfirmed != 0);
g_dailySession.latestBalance = latestBalance;
g_dailySession.latestEquity = latestEquity;
g_dailySession.dailyLoss = dailyLoss;
g_dailySession.maximumDailyLossReached = maximumDailyLossReached;
g_dailySession.targetProgress = targetProgress;
g_dailySession.sessionStatus = sessionStatus;
g_dailySession.createdAt = (datetime)createdAt;
g_dailySession.updatedAt = (datetime)updatedAt;
FinalizeDatabaseRequest(request);
return(true);
}
//+------------------------------------------------------------------+
//| Check daily session existence |
//+------------------------------------------------------------------+
bool DailySessionExists(const string tradingDate)
{
string accountServer = AccountInfoString(ACCOUNT_SERVER);
string sql =
"SELECT 1 "
"FROM daily_sessions "
"WHERE account_login = ?1 "
"AND account_server = ?2 "
"AND trading_date = ?3 "
"LIMIT 1;";
int request = DatabasePrepare(g_database, sql);
if(request == INVALID_HANDLE)
{
Print("Prop Firm Guard: Could not prepare session existence request. Error: ",
GetLastError());
return(false);
}
if(!DatabaseBind(request, 0, g_accountState.login) ||
!DatabaseBind(request, 1, accountServer) ||
!DatabaseBind(request, 2, tradingDate))
{
Print("Prop Firm Guard: Could not bind session existence values. Error: ",
GetLastError());
FinalizeDatabaseRequest(request);
return(false);
}
ResetLastError();
bool exists = DatabaseRead(request);
int errorCode = GetLastError();
FinalizeDatabaseRequest(request);
if(!exists && errorCode != ERR_DATABASE_NO_MORE_DATA)
{
Print("Prop Firm Guard: Could not check daily session existence. Error: ",
errorCode);
}
return(exists);
}
//+------------------------------------------------------------------+
//| Execute prepared database request |
//+------------------------------------------------------------------+
bool ExecuteDatabaseRequest(const int request,
const string operation)
{
ResetLastError();
bool result = DatabaseRead(request);
int errorCode = GetLastError();
// INSERT and UPDATE statements return no result rows. MQL5 therefore
// reports ERR_DATABASE_NO_MORE_DATA after successful execution.
bool success =
(result || errorCode == ERR_DATABASE_NO_MORE_DATA);
if(!success)
{
Print("Prop Firm Guard: Database error while ",
operation,
". Error: ",
errorCode);
}
FinalizeDatabaseRequest(request);
return(success);
}
//+------------------------------------------------------------------+
//| Finalize database request |
//+------------------------------------------------------------------+
void FinalizeDatabaseRequest(const int request)
{
if(request == INVALID_HANDLE)
return;
ResetLastError();
DatabaseFinalize(request);
int errorCode = GetLastError();
if(errorCode != 0)
{
Print("Prop Firm Guard: Could not finalize database request. Error: ",
errorCode);
}
}
//+------------------------------------------------------------------+
//| Create dashboard |
//+------------------------------------------------------------------+
bool CreateDashboard()
{
string panelName = g_objectPrefix + "Panel";
int leftX = InpDashboardX + 18;
int rightX = InpDashboardX + 275;
if(!ObjectCreate(0,
panelName,
OBJ_RECTANGLE_LABEL,
0,
0,
0))
{
Print("Prop Firm Guard: Could not create dashboard panel. Error: ",
GetLastError());
return(false);
}
if(!ObjectSetInteger(0, panelName, OBJPROP_CORNER, CORNER_LEFT_UPPER) ||
!ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, InpDashboardX) ||
!ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, InpDashboardY) ||
!ObjectSetInteger(0, panelName, OBJPROP_XSIZE, 520) ||
!ObjectSetInteger(0, panelName, OBJPROP_YSIZE, 500) ||
!ObjectSetInteger(0, panelName, OBJPROP_BGCOLOR, clrWhite) ||
!ObjectSetInteger(0, panelName, OBJPROP_BORDER_COLOR, clrSilver) ||
!ObjectSetInteger(0, panelName, OBJPROP_SELECTABLE, false) ||
!ObjectSetInteger(0, panelName, OBJPROP_HIDDEN, true))
{
Print("Prop Firm Guard: Could not configure dashboard panel. Error: ",
GetLastError());
return(false);
}
//--- Main title
if(!CreateDashboardLabel(g_objectPrefix + "Title", "PROP FIRM GUARD", leftX, InpDashboardY + 12, 11, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "OverallStatus", "", rightX, InpDashboardY + 14, 10, clrBlack))
return(false);
//--- SESSION
if(!CreateDashboardLabel(g_objectPrefix + "SessionHeader", "SESSION", leftX, InpDashboardY + 42, 9, clrDimGray))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "TradingDate", "", leftX, InpDashboardY + 62, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "SessionState", "", leftX, InpDashboardY + 82, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "DailyStart", "", leftX, InpDashboardY + 102, 9, clrBlack))
return(false);
//--- ACCOUNT
if(!CreateDashboardLabel(g_objectPrefix + "AccountHeader", "ACCOUNT", leftX, InpDashboardY + 145, 9, clrDimGray))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "AccountLogin", "", leftX, InpDashboardY + 165, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "Balance", "", leftX, InpDashboardY + 185, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "Equity", "", leftX, InpDashboardY + 205, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "FloatingPL", "", leftX, InpDashboardY + 225, 9, clrBlack))
return(false);
//--- DAILY DRAWDOWN
if(!CreateDashboardLabel(g_objectPrefix + "DailyDrawdownHeader", "DAILY DRAWDOWN", leftX, InpDashboardY + 255, 9, clrDimGray))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "DailyLossLimit", "", leftX, InpDashboardY + 275, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "DailyCurrentLoss", "", leftX, InpDashboardY + 295, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "DailyLimitUsage", "", leftX, InpDashboardY + 315, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "DailyRemaining", "", leftX, InpDashboardY + 335, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "DailyDrawdownStatus", "", leftX, InpDashboardY + 355, 9, clrBlack))
return(false);
//--- OVERALL DRAWDOWN
if(!CreateDashboardLabel(g_objectPrefix + "OverallDrawdownHeader", "OVERALL DRAWDOWN", rightX, InpDashboardY + 42, 9, clrDimGray))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "OverallInitialBalance", "", rightX, InpDashboardY + 62, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "OverallFloor", "", rightX, InpDashboardY + 82, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "OverallEquity", "", rightX, InpDashboardY + 102, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "OverallLoss", "", rightX, InpDashboardY + 122, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "OverallLimitUsage", "", rightX, InpDashboardY + 142, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "OverallRemaining", "", rightX, InpDashboardY + 162, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "OverallDrawdownStatus", "", rightX, InpDashboardY + 182, 9, clrBlack))
return(false);
//--- PROFIT TARGET
if(!CreateDashboardLabel(g_objectPrefix + "ProfitTargetHeader", "PROFIT TARGET", rightX, InpDashboardY + 220, 9, clrDimGray))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "TargetBalance", "", rightX, InpDashboardY + 240, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "TargetCurrentProfit", "", rightX, InpDashboardY + 260, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "TargetProgress", "", rightX, InpDashboardY + 280, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "TargetRemaining", "", rightX, InpDashboardY + 300, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "ProfitTargetStatus", "", rightX, InpDashboardY + 320, 9, clrBlack))
return(false);
//--- NEWS
if(!CreateDashboardLabel(g_objectPrefix + "NewsHeader", "NEWS", rightX, InpDashboardY + 350, 9, clrDimGray))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "NewsEvent", "", rightX, InpDashboardY + 370, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "NewsTime", "", rightX, InpDashboardY + 390, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "NewsCountdown", "", rightX, InpDashboardY + 410, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "NewsRule", "", rightX, InpDashboardY + 430, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "NewsStatus", "", rightX, InpDashboardY + 450, 9, clrBlack))
return(false);
//--- STATUS
if(!CreateDashboardLabel(g_objectPrefix + "StatusHeader", "STATUS", leftX, InpDashboardY + 390, 9, clrDimGray))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "StatusDaily", "", leftX, InpDashboardY + 410, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "StatusOverall", "", leftX, InpDashboardY + 430, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "StatusTarget", "", leftX, InpDashboardY + 450, 9, clrBlack))
return(false);
if(!CreateDashboardLabel(g_objectPrefix + "StatusNews", "", leftX, InpDashboardY + 470, 9, clrBlack))
return(false);
if(!CreateDailySessionControls())
return(false);
UpdateDashboard();
ChartRedraw();
return(true);
}
//+------------------------------------------------------------------+
//| Create dashboard label |
//+------------------------------------------------------------------+
bool CreateDashboardLabel(const string name,
const string text,
const int x,
const int y,
const int fontSize,
const color textColor)
{
// Create a fixed text label positioned relative to the chart corner.
if(!ObjectCreate(0,
name,
OBJ_LABEL,
0,
0,
0))
{
Print("Prop Firm Guard: Could not create label ",
name,
". Error: ",
GetLastError());
return(false);
}
// Configure all visual and positioning properties of the label.
if(!ObjectSetInteger(0,
name,
OBJPROP_CORNER,
CORNER_LEFT_UPPER) ||
!ObjectSetInteger(0,
name,
OBJPROP_XDISTANCE,
x) ||
!ObjectSetInteger(0,
name,
OBJPROP_YDISTANCE,
y) ||
!ObjectSetInteger(0,
name,
OBJPROP_FONTSIZE,
fontSize) ||
!ObjectSetInteger(0,
name,
OBJPROP_COLOR,
textColor) ||
!ObjectSetInteger(0,
name,
OBJPROP_SELECTABLE,
false) ||
!ObjectSetInteger(0,
name,
OBJPROP_HIDDEN,
true))
{
Print("Prop Firm Guard: Could not configure label ",
name,
". Error: ",
GetLastError());
return(false);
}
if(!ObjectSetString(0,
name,
OBJPROP_FONT,
"Tahoma") ||
!ObjectSetString(0,
name,
OBJPROP_TEXT,
text))
{
Print("Prop Firm Guard: Could not initialize label ",
name,
". Error: ",
GetLastError());
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Update dashboard text |
//+------------------------------------------------------------------+
void UpdateDashboardText(const string objectName,
const string text)
{
// Update only objects that currently exist on the chart.
if(ObjectFind(0, objectName) < 0)
{
Print("Prop Firm Guard: Dashboard object not found: ",
objectName);
return;
}
if(!ObjectSetString(0,
objectName,
OBJPROP_TEXT,
text))
{
Print("Prop Firm Guard: Could not update dashboard object ",
objectName,
". Error: ",
GetLastError());
}
}
//+------------------------------------------------------------------+
//| Update dashboard color |
//+------------------------------------------------------------------+
void UpdateDashboardColor(const string objectName,
const color textColor)
{
// Change the color only when the dashboard object exists.
if(ObjectFind(0, objectName) < 0)
{
Print("Prop Firm Guard: Dashboard object not found: ",
objectName);
return;
}
if(!ObjectSetInteger(0,
objectName,
OBJPROP_COLOR,
textColor))
{
Print("Prop Firm Guard: Could not update dashboard color for ",
objectName,
". Error: ",
GetLastError());
}
}
//+------------------------------------------------------------------+
//| Update dashboard |
//+------------------------------------------------------------------+
void UpdateDashboard()
{
string sessionState;
string dailyStart;
string headerStatus;
ENUM_GUARD_STATUS guardStatus = GetOverallGuardStatus();
if(g_dailySession.balanceConfirmed)
{
sessionState = "Session: ACTIVE";
dailyStart = "Starting Balance: " + DoubleToString(g_dailySession.startingBalance, 2);
headerStatus = "STATUS: " + GuardStatusToString(guardStatus);
}
else
{
sessionState = "Session: AWAITING CONFIRMATION";
dailyStart = "Starting Balance: Not confirmed";
headerStatus = "STATUS: SETUP REQUIRED";
}
//--- Session
UpdateDashboardText(g_objectPrefix + "TradingDate",
"Trading Date: " + g_dailySession.tradingDate);
UpdateDashboardText(g_objectPrefix + "SessionState",
sessionState);
UpdateDashboardText(g_objectPrefix + "DailyStart",
dailyStart);
//--- Account
UpdateDashboardText(g_objectPrefix + "AccountLogin",
"Account: " + IntegerToString(g_accountState.login));
UpdateDashboardText(g_objectPrefix + "Balance",
"Balance: " + DoubleToString(g_accountState.balance, 2));
UpdateDashboardText(g_objectPrefix + "Equity",
"Equity: " + DoubleToString(g_accountState.equity, 2));
UpdateDashboardText(g_objectPrefix + "FloatingPL",
"Floating P/L: " + DoubleToString(g_accountState.floatingPL, 2));
//--- Daily drawdown
if(g_dailySession.balanceConfirmed)
{
UpdateDashboardText(g_objectPrefix + "DailyLossLimit",
"Maximum Loss: " +
DoubleToString(g_dailyDrawdown.maximumDailyLoss, 2) +
" (" +
DoubleToString(g_accountSettings.dailyDrawdownPercent, 2) +
"%)");
UpdateDashboardText(g_objectPrefix + "DailyCurrentLoss",
"Current Loss: " +
DoubleToString(g_dailyDrawdown.currentDailyLoss, 2) +
" (" +
DoubleToString(g_dailyDrawdown.drawdownPercent, 2) +
"%)");
UpdateDashboardText(g_objectPrefix + "DailyLimitUsage",
"Limit Used: " +
DoubleToString(g_dailyDrawdown.limitUsagePercent, 2) +
"%");
UpdateDashboardText(g_objectPrefix + "DailyRemaining",
"Remaining: " +
DoubleToString(g_dailyDrawdown.remainingAllowance, 2));
UpdateDashboardText(g_objectPrefix + "DailyDrawdownStatus",
"Status: " +
ComplianceStatusToString(g_dailyDrawdown.status));
}
else
{
UpdateDashboardText(g_objectPrefix + "DailyLossLimit", "Maximum Loss: --");
UpdateDashboardText(g_objectPrefix + "DailyCurrentLoss", "Current Loss: --");
UpdateDashboardText(g_objectPrefix + "DailyLimitUsage", "Limit Used: --");
UpdateDashboardText(g_objectPrefix + "DailyRemaining", "Remaining: --");
UpdateDashboardText(g_objectPrefix + "DailyDrawdownStatus", "Status: WAITING");
}
//--- Overall drawdown
UpdateDashboardText(g_objectPrefix + "OverallInitialBalance",
"Initial Balance: " +
DoubleToString(g_overallDrawdown.initialBalance, 2));
UpdateDashboardText(g_objectPrefix + "OverallFloor",
"Equity Floor: " +
DoubleToString(g_overallDrawdown.overallFloor, 2));
UpdateDashboardText(g_objectPrefix + "OverallEquity",
"Current Equity: " +
DoubleToString(g_overallDrawdown.currentEquity, 2));
UpdateDashboardText(g_objectPrefix + "OverallLoss",
"Overall Loss: " +
DoubleToString(g_overallDrawdown.currentOverallLoss, 2));
UpdateDashboardText(g_objectPrefix + "OverallLimitUsage",
"Limit Used: " +
DoubleToString(g_overallDrawdown.limitUsagePercent, 2) +
"%");
UpdateDashboardText(g_objectPrefix + "OverallRemaining",
"Remaining: " +
DoubleToString(g_overallDrawdown.remainingAllowance, 2));
UpdateDashboardText(g_objectPrefix + "OverallDrawdownStatus",
"Status: " +
ComplianceStatusToString(g_overallDrawdown.status));
//--- Profit target
UpdateDashboardText(g_objectPrefix + "TargetBalance",
"Target Balance: " +
DoubleToString(g_profitTarget.targetBalance, 2));
UpdateDashboardText(g_objectPrefix + "TargetCurrentProfit",
"Current Profit: " +
DoubleToString(g_profitTarget.currentProfit, 2));
UpdateDashboardText(g_objectPrefix + "TargetProgress",
"Progress: " +
DoubleToString(g_profitTarget.progressPercent, 2) +
"%");
UpdateDashboardText(g_objectPrefix + "TargetRemaining",
"Remaining: " +
DoubleToString(g_profitTarget.remainingProfit, 2));
UpdateDashboardText(g_objectPrefix + "ProfitTargetStatus",
"Status: " +
TargetStatusToString(g_profitTarget.status));
//--- News
if(g_newsState.status == NEWS_DISABLED)
{
UpdateDashboardText(g_objectPrefix + "NewsEvent",
"Filter: Disabled");
UpdateDashboardText(g_objectPrefix + "NewsTime",
"Trading Window: DISABLED");
UpdateDashboardText(g_objectPrefix + "NewsCountdown",
"Countdown: --");
UpdateDashboardText(g_objectPrefix + "NewsRule",
"Holding: --");
}
else if(g_newsState.status == NEWS_NO_EXPOSURE)
{
UpdateDashboardText(g_objectPrefix + "NewsEvent",
"No supported active exposure");
UpdateDashboardText(g_objectPrefix + "NewsTime",
"Trading Window: SAFE");
UpdateDashboardText(g_objectPrefix + "NewsCountdown",
"Countdown: --");
UpdateDashboardText(g_objectPrefix + "NewsRule",
"Holding: " +
GetNewsHoldingRuleText());
}
else if(!g_newsState.eventFound)
{
UpdateDashboardText(g_objectPrefix + "NewsEvent",
"No relevant event found");
UpdateDashboardText(g_objectPrefix + "NewsTime",
"Trading Window: SAFE");
UpdateDashboardText(g_objectPrefix + "NewsCountdown",
"Currencies: " +
g_newsState.currencyList);
UpdateDashboardText(g_objectPrefix + "NewsRule",
"Holding: " +
GetNewsHoldingRuleText());
}
else
{
UpdateDashboardText(g_objectPrefix + "NewsEvent",
g_newsState.eventCurrency +
" | " +
g_newsState.eventName +
" | " +
CalendarImportanceToString(g_newsState.importance));
UpdateDashboardText(g_objectPrefix + "NewsTime",
"Trading Window: " +
GetNewsTradingWindowText());
UpdateDashboardText(g_objectPrefix + "NewsCountdown",
"In: " +
FormatNewsCountdown(g_newsState.secondsToEvent));
UpdateDashboardText(g_objectPrefix + "NewsRule",
"Holding: " +
GetNewsHoldingRuleText() +
" | Affected: " +
IntegerToString(g_newsState.affectedPositions));
}
UpdateDashboardText(g_objectPrefix + "NewsStatus",
"Status: " +
NewsHoldingStatusToString(g_newsState.holdingStatus));
//--- Main header
UpdateDashboardText(g_objectPrefix + "OverallStatus",
headerStatus);
if(g_dailySession.balanceConfirmed)
UpdateDashboardColor(g_objectPrefix + "OverallStatus",
GetGuardStatusColor(guardStatus));
else
UpdateDashboardColor(g_objectPrefix + "OverallStatus",
clrDarkOrange);
//--- Status summary
UpdateDashboardText(g_objectPrefix + "StatusDaily",
"Daily DD: " +
(g_dailySession.balanceConfirmed ?
ComplianceStatusToString(g_dailyDrawdown.status) :
"WAITING"));
UpdateDashboardText(g_objectPrefix + "StatusOverall",
"Overall DD: " +
ComplianceStatusToString(g_overallDrawdown.status));
UpdateDashboardText(g_objectPrefix + "StatusTarget",
"Target: " +
TargetStatusToString(g_profitTarget.status));
UpdateDashboardText(g_objectPrefix + "StatusNews",
"News: " +
NewsHoldingStatusToString(g_newsState.holdingStatus));
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Delete dashboard |
//+------------------------------------------------------------------+
void DeleteDashboard()
{
// Delete only chart objects created by Prop Firm Guard.
int deletedObjects = ObjectsDeleteAll(0, g_objectPrefix);
if(deletedObjects < 0)
{
Print("Prop Firm Guard: Could not delete dashboard objects. Error: ",
GetLastError());
}
ChartRedraw();
}
2026-08-28 18:38:10 +03:00
//+------------------------------------------------------------------+
//| Send guard notification |
//+------------------------------------------------------------------+
void SendGuardNotification(const string message)
{
Print("Prop Firm Guard: ",
message);
if(InpEnableAlerts)
Alert("Prop Firm Guard: ",
message);
if(!InpEnablePushNotifications)
return;
if(!TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))
{
Print("Prop Firm Guard: Push notifications are enabled in the EA ",
"but disabled in the terminal settings.");
return;
}
ResetLastError();
if(!SendNotification("Prop Firm Guard: " + message))
{
Print("Prop Firm Guard: Could not send push notification. Error: ",
GetLastError());
}
}
//+------------------------------------------------------------------+
//| Build daily drawdown alert |
//+------------------------------------------------------------------+
string BuildDailyDrawdownAlert(const ENUM_COMPLIANCE_STATUS status)
{
switch(status)
{
case COMPLIANCE_WARNING:
return("Daily drawdown reached WARNING level.");
case COMPLIANCE_CRITICAL:
return("CRITICAL: Only " +
DoubleToString(g_dailyDrawdown.remainingAllowance, 2) +
" remains before the daily loss limit.");
case COMPLIANCE_BREACHED:
return("Daily drawdown limit breached.");
case COMPLIANCE_SAFE:
default:
return("");
}
}
//+------------------------------------------------------------------+
//| Build overall drawdown alert |
//+------------------------------------------------------------------+
string BuildOverallDrawdownAlert(const ENUM_COMPLIANCE_STATUS status)
{
switch(status)
{
case COMPLIANCE_WARNING:
return("Overall drawdown reached WARNING level.");
case COMPLIANCE_CRITICAL:
return("CRITICAL: Only " +
DoubleToString(g_overallDrawdown.remainingAllowance, 2) +
" remains before the overall loss limit.");
case COMPLIANCE_BREACHED:
return("Overall drawdown limit breached.");
case COMPLIANCE_SAFE:
default:
return("");
}
}
//+------------------------------------------------------------------+
//| Check daily drawdown alert |
//+------------------------------------------------------------------+
void CheckDailyDrawdownAlert()
{
if(!g_dailySession.balanceConfirmed)
return;
if(g_dailyDrawdown.status == g_alertState.dailyDrawdownStatus)
return;
g_alertState.dailyDrawdownStatus = g_dailyDrawdown.status;
string message = BuildDailyDrawdownAlert(g_dailyDrawdown.status);
if(message == "")
return;
g_alertState.dashboardMessage = message;
SendGuardNotification(message);
}
//+------------------------------------------------------------------+
//| Check overall drawdown alert |
//+------------------------------------------------------------------+
void CheckOverallDrawdownAlert()
{
if(g_overallDrawdown.status == g_alertState.overallDrawdownStatus)
return;
g_alertState.overallDrawdownStatus = g_overallDrawdown.status;
string message = BuildOverallDrawdownAlert(g_overallDrawdown.status);
if(message == "")
return;
g_alertState.dashboardMessage = message;
SendGuardNotification(message);
}
//+------------------------------------------------------------------+
//| Check profit target alert |
//+------------------------------------------------------------------+
void CheckProfitTargetAlert()
{
if(g_profitTarget.status == g_alertState.profitTargetStatus)
return;
g_alertState.profitTargetStatus = g_profitTarget.status;
if(g_profitTarget.status != TARGET_REACHED)
return;
string message = "Profit target reached.";
g_alertState.dashboardMessage = message;
SendGuardNotification(message);
}
//+------------------------------------------------------------------+
//| Check economic news alerts |
//+------------------------------------------------------------------+
void CheckNewsAlert()
{
if(!InpEnableNewsFilter ||
!g_newsState.eventFound ||
g_newsState.affectedPositions == 0)
return;
// A new calendar event receives a fresh alert state.
if(g_alertState.newsEventId != g_newsState.eventId)
{
g_alertState.newsEventId = g_newsState.eventId;
g_alertState.newsApproachAlerted = false;
g_alertState.newsRestrictedAlerted = false;
}
long warningThreshold =
(long)InpMinutesBeforeNews * 60;
// Issue one advance warning when the event reaches the configured
// pre-news interval.
if(g_newsState.secondsToEvent > 0 &&
g_newsState.secondsToEvent <= warningThreshold &&
!g_alertState.newsApproachAlerted)
{
string message =
CalendarImportanceToString(g_newsState.importance) +
"-impact " +
g_newsState.eventCurrency +
" news in " +
IntegerToString((int)MathCeil((double)g_newsState.secondsToEvent / 60.0)) +
" minute(s): " +
g_newsState.eventName +
".";
g_alertState.newsApproachAlerted = true;
g_alertState.dashboardMessage = message;
SendGuardNotification(message);
}
// If holding is prohibited, report entry into the restricted window.
if(g_newsState.holdingStatus == NEWS_HOLDING_PROTECTION &&
!g_alertState.newsRestrictedAlerted)
{
string message =
"News holding restriction active for " +
g_newsState.eventCurrency +
" " +
g_newsState.eventName +
".";
g_alertState.newsRestrictedAlerted = true;
g_alertState.dashboardMessage = message;
SendGuardNotification(message);
}
}
//+------------------------------------------------------------------+
//| Manage alerts |
//+------------------------------------------------------------------+
void ManageAlerts()
{
CheckDailyDrawdownAlert();
CheckOverallDrawdownAlert();
CheckProfitTargetAlert();
CheckNewsAlert();
}
//+------------------------------------------------------------------+
//| Get dashboard alert message |
//+------------------------------------------------------------------+
string GetDashboardAlertMessage()
{
if(g_alertState.dashboardMessage != "")
return(g_alertState.dashboardMessage);
return("Monitoring account...");
}
//+------------------------------------------------------------------+
//| Get daily session status |
//+------------------------------------------------------------------+
string GetDailySessionStatus()
{
if(!g_dailySession.balanceConfirmed)
return("AWAITING_CONFIRMATION");
if(g_dailyDrawdown.status == COMPLIANCE_BREACHED ||
g_overallDrawdown.status == COMPLIANCE_BREACHED)
return("BREACHED");
if(g_profitTarget.status == TARGET_REACHED)
return("TARGET_REACHED");
if(g_dailyDrawdown.status == COMPLIANCE_CRITICAL ||
g_overallDrawdown.status == COMPLIANCE_CRITICAL)
return("CRITICAL");
if(g_dailyDrawdown.status == COMPLIANCE_WARNING ||
g_overallDrawdown.status == COMPLIANCE_WARNING)
return("WARNING");
return("ACTIVE");
}
//+------------------------------------------------------------------+
//| Update runtime daily-session state |
//+------------------------------------------------------------------+
bool UpdateDailySessionRuntimeState()
{
if(!g_hasDailySession)
return(false);
g_dailySession.latestBalance = g_accountState.balance;
g_dailySession.latestEquity = g_accountState.equity;
g_dailySession.targetProgress = g_profitTarget.progressPercent;
g_dailySession.sessionStatus = GetDailySessionStatus();
g_dailySession.updatedAt = TimeCurrent();
if(g_dailySession.balanceConfirmed)
{
g_dailySession.dailyLoss = g_dailyDrawdown.currentDailyLoss;
// Preserve the worst actual loss observed during this trading day.
if(g_dailyDrawdown.currentDailyLoss >
g_dailySession.maximumDailyLossReached)
{
g_dailySession.maximumDailyLossReached =
g_dailyDrawdown.currentDailyLoss;
}
}
else
{
g_dailySession.dailyLoss = 0.0;
}
return(SaveDailySession());
}
//+------------------------------------------------------------------+
//| Update runtime database when due |
//+------------------------------------------------------------------+
void UpdateRuntimeDatabase()
{
datetime currentTime = TimeCurrent();
// Runtime snapshots do not need the dashboard's one-second frequency.
if(g_lastRuntimeDatabaseUpdate != 0 &&
currentTime - g_lastRuntimeDatabaseUpdate < 10)
return;
if(UpdateDailySessionRuntimeState())
g_lastRuntimeDatabaseUpdate = currentTime;
}
//+------------------------------------------------------------------+