//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ //| Represents the general compliance condition of the account. | //+------------------------------------------------------------------+ 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 }; //+------------------------------------------------------------------+ //| 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" 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 = 20000.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 //+------------------------------------------------------------------+ //| Account state structure | //+------------------------------------------------------------------+ //| Holds the live account values required by the monitoring logic. | //+------------------------------------------------------------------+ 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 }; //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ SAccountState g_accountState; // Latest monitored account state SAccountSettings g_accountSettings; // Persisted prop-firm settings string g_databaseName = "prop_firm_compliance.sqlite"; // SQLite database file int g_database = INVALID_HANDLE; // Active SQLite database handle //+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+ //| Update account state | //+------------------------------------------------------------------+ void UpdateAccountState() { // Read the account identifier so persisted data can later 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; // Compliance calculations have not been introduced yet. g_accountState.status = COMPLIANCE_SAFE; } //+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+ //| Ensure database tables exist | //+------------------------------------------------------------------+ bool EnsureDatabaseTables() { // Store one configuration for each trading account/server pair. 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)" ");"; ResetLastError(); if(!DatabaseExecute(g_database, settingsTable)) { Print("Prop Firm Guard: Could not create settings table. Error: ", GetLastError()); return(false); } return(true); } //+------------------------------------------------------------------+ //| 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); } } //+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+ //| 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 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); } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // The monitoring timer introduced later requires an interval of // at least one second. if(InpTimerSeconds < 1) { Print("Prop Firm Guard: Timer interval must be at least 1 second."); return(INIT_PARAMETERS_INCORRECT); } // Reject invalid prop-firm rules before any account or persistence // component begins using the configured values. if(!ValidateComplianceInputs()) return(INIT_PARAMETERS_INCORRECT); // Collect the current account identity and live financial values. UpdateAccountState(); // Open the SQLite database used for account-specific persistence. if(!OpenDatabase()) { Print("Prop Firm Guard: Database initialization failed."); return(INIT_FAILED); } // Ensure the persistence schema exists before settings are accessed. if(!EnsureDatabaseTables()) { Print("Prop Firm Guard: Could not initialize database tables."); CloseDatabase(); return(INIT_FAILED); } // Restore the current account/server record when available and // synchronize it with the settings supplied through the Inputs tab. if(!InitializeAccountSettings()) { Print("Prop Firm Guard: Could not initialize account settings."); CloseDatabase(); return(INIT_FAILED); } Print("Prop Firm Guard initialized successfully."); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Close the SQLite connection after all Part 1 persistence work // has finished. CloseDatabase(); // Record why MetaTrader removed or restarted the EA. Print("Prop Firm Guard removed. Deinitialization reason: ", reason); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { }