2026-08-13 09:00:54 -07:00 | | | //+------------------------------------------------------------------+
|
| | | //| PropFirmGuard.mq5 |
|
| | | //| Copyright 2026, MetaQuotes Ltd. |
|
| | | //| https://www.mql5.com |
|
| | | //+------------------------------------------------------------------+
|
2026-08-15 02:44:23 -07:00 | | | #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. |
|
| | | //+------------------------------------------------------------------+
|
| | | 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
|
| | | };
|
2026-08-30 05:53:24 +03:00 | | |
|
2026-08-15 02:44:23 -07:00 | | | //+------------------------------------------------------------------+
|
| | | //| Profit target status |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Represents progress toward the configured account profit target. |
|
| | | //+------------------------------------------------------------------+
|
| | | enum ENUM_TARGET_STATUS
|
| | | {
|
2026-08-30 05:53:24 +03:00 | | | 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
|
2026-08-15 02:44:23 -07:00 | | | };
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-09-03 10:16:43 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Protection action |
|
2026-09-07 04:17:47 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Defines what Prop Firm Guard should do when a monitored rule |
|
| | | //| requires protective action. |
|
2026-09-07 04:17:47 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | enum ENUM_PROTECTION_ACTION
|
2026-09-07 04:17:47 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | 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
|
2026-09-07 04:17:47 +03:00 | | | };
|
2026-10-12 12:58:53 +03:00 | | |
|
2026-08-15 02:44:23 -07:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Daily session status |
|
2026-08-15 02:44:23 -07:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Represents the lifecycle state of one persistent daily session. |
|
2026-08-15 02:44:23 -07:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | enum ENUM_DAILY_SESSION_STATUS
|
2026-08-15 02:44:23 -07:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | SESSION_AWAITING_CONFIRMATION = 0, // Daily starting balance is not yet confirmed
|
| | | SESSION_ACTIVE, // Daily starting balance has been confirmed
|
| | | SESSION_CLOSED // Session has been finalized after day rollover
|
2026-08-28 18:38:10 +03:00 | | | };
|
2026-10-14 09:50:20 +03:00 | | |
|
2026-10-12 13:46:46 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Daily session load result |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Distinguishes a restored session from absence and database error. |
|
| | | //+------------------------------------------------------------------+
|
| | | enum ENUM_DAILY_SESSION_LOAD_RESULT
|
| | | {
|
| | | SESSION_LOAD_ERROR = -1, // Database operation failed
|
| | | SESSION_LOAD_NOT_FOUND = 0, // No matching daily session exists
|
| | | SESSION_LOAD_FOUND = 1 // Matching daily session was restored
|
| | | };
|
2026-08-31 18:18:41 +03:00 | | |
|
2026-08-30 06:30:53 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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
|
| | |
|
2026-10-14 10:21:30 +03:00 | | | input group "Daily Session"
|
| | | input int InpDailyResetHour = 0; // Daily reset hour in broker server time
|
| | | input int InpDailyResetMinute = 0; // Daily reset minute in broker server time
|
| | |
|
2026-08-30 06:30:53 +03:00 | | | input group "Prop Firm Rules"
|
2026-10-12 12:58:53 +03:00 | | | input double InpInitialAccountBalance = 20000.0; // Initial prop-firm account balance
|
2026-08-30 06:30:53 +03:00 | | | 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
|
| | |
|
2026-08-30 11:53:44 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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
|
| | | };
|
| | |
|
2026-08-30 06:30:53 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Account settings structure |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Stores the main prop-firm rules associated with this account. |
|
| | | //+------------------------------------------------------------------+
|
| | | struct SAccountSettings
|
| | | {
|
2026-08-31 18:18:41 +03:00 | | | 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
|
2026-08-30 06:30:53 +03:00 | | | };
|
2026-10-12 12:58:53 +03:00 | | |
|
2026-09-01 06:44:36 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Daily session structure |
|
| | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Stores the persistent reference for one configured trading day. |
|
2026-09-01 06:44:36 +03:00 | | | //+------------------------------------------------------------------+
|
| | | struct SDailySession
|
| | | {
|
2026-10-12 12:58:53 +03:00 | | | long accountLogin; // Trading account login
|
| | | string accountServer; // Broker trading server
|
| | | string sessionDate; // Configured trading-day session key
|
| | | double startingBalance; // Trader-confirmed daily starting balance
|
| | | double startingEquity; // Equity captured when confirmation occurs
|
| | | bool balanceConfirmed; // Whether the daily baseline is confirmed
|
| | | ENUM_DAILY_SESSION_STATUS status; // Current daily session lifecycle state
|
| | | datetime createdAt; // Time the session record was created
|
| | | datetime updatedAt; // Time the session record was last updated
|
2026-09-03 03:00:04 +03:00 | | | };
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-09-03 03:00:04 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Global variables |
|
2026-09-03 03:00:04 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | SAccountState g_accountState; // Latest monitored account state
|
| | | SAccountSettings g_accountSettings; // Persisted prop-firm settings
|
| | | SDailySession g_dailySession; // Current configured daily session
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | 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
|
2026-10-14 14:14:05 +03:00 | | | string g_instanceLockName = ""; // Account-specific terminal instance lock
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-10-14 14:14:05 +03:00 | | | int g_database = INVALID_HANDLE; // Active SQLite database handle
|
| | | bool g_hasDailySession = false; // Whether a daily session is currently loaded
|
| | | bool g_instanceLockAcquired = false; // Whether this EA owns the instance lock
|
2026-09-19 14:21:11 +03:00 | | |
|
2026-09-08 15:01:59 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Validate compliance inputs |
|
2026-09-08 15:01:59 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | bool ValidateComplianceInputs()
|
2026-09-08 15:01:59 +03:00 | | | {
|
2026-10-14 10:21:30 +03:00 | | | //--- Validate the monitoring interval
|
| | | if(InpTimerSeconds < 1)
|
| | | {
|
| | | Print("Prop Firm Guard: Timer interval must be at least 1 second.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Validate the configured daily reset hour
|
| | | if(InpDailyResetHour < 0 ||
|
| | | InpDailyResetHour > 23)
|
| | | {
|
| | | Print("Prop Firm Guard: Daily reset hour must be between 0 and 23.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Validate the configured daily reset minute
|
| | | if(InpDailyResetMinute < 0 ||
|
| | | InpDailyResetMinute > 59)
|
| | | {
|
| | | Print("Prop Firm Guard: Daily reset minute must be between 0 and 59.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
2026-08-31 19:38:11 +03:00 | | | //--- Validate the initial account balance
|
2026-08-30 12:36:36 +03:00 | | | if(InpInitialAccountBalance <= 0.0)
|
| | | {
|
| | | Print("Prop Firm Guard: Initial account balance must be greater than zero.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
2026-08-31 19:38:11 +03:00 | | | //--- Validate the daily drawdown percentage
|
2026-08-30 12:36:36 +03:00 | | | if(InpDailyDrawdownPercent <= 0.0 ||
|
| | | InpDailyDrawdownPercent > 100.0)
|
| | | {
|
| | | Print("Prop Firm Guard: Daily drawdown percentage must be between 0 and 100.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
2026-08-31 19:38:11 +03:00 | | | //--- Validate the overall drawdown percentage
|
2026-08-30 12:36:36 +03:00 | | | if(InpOverallDrawdownPercent <= 0.0 ||
|
| | | InpOverallDrawdownPercent > 100.0)
|
| | | {
|
| | | Print("Prop Firm Guard: Overall drawdown percentage must be between 0 and 100.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
2026-08-31 19:38:11 +03:00 | | | //--- Validate the profit target percentage
|
2026-08-30 12:36:36 +03:00 | | | if(InpProfitTargetPercent <= 0.0 ||
|
| | | InpProfitTargetPercent > 100.0)
|
| | | {
|
| | | Print("Prop Firm Guard: Profit target percentage must be between 0 and 100.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
2026-08-31 19:38:11 +03:00 | | | //--- Validate the warning threshold
|
2026-08-30 12:36:36 +03:00 | | | if(InpWarningLevelPercent <= 0.0 ||
|
| | | InpWarningLevelPercent >= InpCriticalLevelPercent)
|
| | | {
|
| | | Print("Prop Firm Guard: Warning level must be greater than 0 and below the critical level.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
2026-08-31 19:38:11 +03:00 | | | //--- Validate the critical threshold
|
2026-08-30 12:36:36 +03:00 | | | if(InpCriticalLevelPercent <= InpWarningLevelPercent ||
|
| | | InpCriticalLevelPercent >= 100.0)
|
| | | {
|
| | | Print("Prop Firm Guard: Critical level must be above the warning level and below 100.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
2026-08-31 19:38:11 +03:00 | | | //--- Validate the news restriction windows
|
2026-08-30 12:36:36 +03:00 | | | if(InpMinutesBeforeNews < 0 ||
|
| | | InpMinutesAfterNews < 0)
|
| | | {
|
| | | Print("Prop Firm Guard: News restriction minutes cannot be negative.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | return(true);
|
| | | }
|
2026-09-19 14:21:11 +03:00 | | |
|
2026-08-30 11:53:44 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Update account state |
|
| | | //+------------------------------------------------------------------+
|
| | | void UpdateAccountState()
|
| | | {
|
2026-08-31 19:38:11 +03:00 | | | //--- Read the current account values
|
2026-08-30 11:53:44 +03:00 | | | g_accountState.login = AccountInfoInteger(ACCOUNT_LOGIN);
|
| | | g_accountState.balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
| | | g_accountState.equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
| | |
|
2026-08-31 19:38:11 +03:00 | | | //--- Derive the current floating profit or loss
|
2026-08-30 11:53:44 +03:00 | | | g_accountState.floatingPL = g_accountState.equity - g_accountState.balance;
|
| | |
|
2026-08-31 19:38:11 +03:00 | | | //--- Initialize compliance status before rule calculations are added
|
2026-08-30 11:53:44 +03:00 | | | g_accountState.status = COMPLIANCE_SAFE;
|
| | | }
|
2026-10-14 10:21:30 +03:00 | | |
|
2026-10-14 14:14:05 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Acquire instance lock |
|
| | | //+------------------------------------------------------------------+
|
| | | bool AcquireInstanceLock()
|
| | | {
|
| | | //--- Require a valid trading account identity
|
| | | if(g_accountState.login <= 0)
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot acquire an instance lock without a valid account login.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Build one terminal-wide lock name for the current account
|
| | | g_instanceLockName =
|
| | | StringFormat("PFG_INSTANCE_LOCK_%I64d",
|
| | | g_accountState.login);
|
| | |
|
| | | //--- Create the temporary lock only when it does not already exist
|
| | | if(!GlobalVariableCheck(g_instanceLockName))
|
| | | {
|
| | | ResetLastError();
|
| | |
|
| | | if(!GlobalVariableTemp(g_instanceLockName))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not create the terminal instance lock. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | return(false);
|
| | | }
|
| | | }
|
| | |
|
| | | //--- Atomically claim an available lock whose current value is zero
|
| | | ResetLastError();
|
| | |
|
| | | if(!GlobalVariableSetOnCondition(g_instanceLockName,
|
| | | 1.0,
|
| | | 0.0))
|
| | | {
|
| | | int errorCode = GetLastError();
|
| | |
|
| | | if(errorCode == 0)
|
| | | {
|
| | | Print("Prop Firm Guard: Another instance is already active for account ",
|
| | | g_accountState.login,
|
| | | ". Only one instance per trading account is allowed.");
|
| | | }
|
| | | else
|
| | | {
|
| | | Print("Prop Firm Guard: Could not acquire the terminal instance lock. Error: ",
|
| | | errorCode);
|
| | | }
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Record ownership only after the atomic acquisition succeeds
|
| | | g_instanceLockAcquired = true;
|
| | |
|
| | | Print("Prop Firm Guard: Instance lock acquired for account ",
|
| | | g_accountState.login,
|
| | | ".");
|
| | |
|
| | | return(true);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Release instance lock |
|
| | | //+------------------------------------------------------------------+
|
| | | bool ReleaseInstanceLock()
|
| | | {
|
| | | //--- Ignore cleanup when this EA never acquired the lock
|
| | | if(!g_instanceLockAcquired)
|
| | | return(true);
|
| | |
|
| | | //--- Treat a missing lock variable as already released
|
| | | if(!GlobalVariableCheck(g_instanceLockName))
|
| | | {
|
| | | g_instanceLockAcquired = false;
|
| | |
|
| | | return(true);
|
| | | }
|
| | |
|
| | | //--- Atomically return this owned lock to the available state
|
| | | ResetLastError();
|
| | |
|
| | | if(!GlobalVariableSetOnCondition(g_instanceLockName,
|
| | | 0.0,
|
| | | 1.0))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not release the terminal instance lock. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Clear local ownership after successful release
|
| | | g_instanceLockAcquired = false;
|
| | |
|
| | | Print("Prop Firm Guard: Instance lock released for account ",
|
| | | g_accountState.login,
|
| | | ".");
|
| | |
|
| | | return(true);
|
| | | }
|
| | |
|
2026-10-14 10:21:30 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Get current daily session date |
|
| | | //+------------------------------------------------------------------+
|
| | | string GetCurrentSessionDate()
|
| | | {
|
| | | //--- Read the server time currently known by the terminal
|
| | | datetime serverTime = TimeCurrent();
|
| | |
|
| | | //--- Convert the configured reset boundary to seconds from midnight
|
| | | int resetOffsetSeconds =
|
| | | (InpDailyResetHour * 60 * 60) +
|
| | | (InpDailyResetMinute * 60);
|
| | |
|
| | | //--- Shift the server time so the configured reset becomes midnight
|
| | | datetime sessionTime =
|
| | | serverTime - resetOffsetSeconds;
|
| | |
|
| | | //--- Use the shifted calendar date as the persistent session key
|
| | | return(TimeToString(sessionTime,
|
| | | TIME_DATE));
|
| | | }
|
2026-10-14 11:43:36 +03:00 | | |
|
2026-10-14 12:59:23 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Get account currency digits |
|
| | | //+------------------------------------------------------------------+
|
| | | int GetAccountCurrencyDigits()
|
| | | {
|
| | | //--- Read the precision configured for the account currency
|
| | | ResetLastError();
|
| | |
|
| | | long currencyDigits =
|
| | | AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);
|
| | |
|
| | | int errorCode = GetLastError();
|
| | |
|
| | | if(errorCode != 0)
|
| | | {
|
| | | Print("Prop Firm Guard: Could not read account currency digits. Error: ",
|
| | | errorCode);
|
| | |
|
| | | return(-1);
|
| | | }
|
| | |
|
| | | //--- Reject an invalid precision returned by the terminal
|
| | | if(currencyDigits < 0 ||
|
| | | currencyDigits > 8)
|
| | | {
|
| | | Print("Prop Firm Guard: Account currency digits are outside the supported range.");
|
| | |
|
| | | return(-1);
|
| | | }
|
| | |
|
| | | return((int)currencyDigits);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Format account monetary value |
|
| | | //+------------------------------------------------------------------+
|
| | | bool FormatAccountMoney(const double value,
|
| | | string &formattedValue)
|
| | | {
|
| | | //--- Reject invalid numeric input
|
| | | if(!MathIsValidNumber(value))
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot format an invalid monetary value.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Read the account currency precision
|
| | | int currencyDigits = GetAccountCurrencyDigits();
|
| | |
|
| | | if(currencyDigits < 0)
|
| | | return(false);
|
| | |
|
| | | //--- Format the value using the account currency precision
|
| | | formattedValue = DoubleToString(value,
|
| | | currencyDigits);
|
| | |
|
| | | return(true);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Parse starting balance |
|
| | | //+------------------------------------------------------------------+
|
| | | bool ParseStartingBalance(const string balanceText,
|
| | | double &startingBalance)
|
| | | {
|
| | | //--- Start with a neutral output value
|
| | | startingBalance = 0.0;
|
| | |
|
| | | //--- Remove surrounding whitespace before validation
|
| | | string normalizedText = balanceText;
|
| | |
|
| | | StringTrimLeft(normalizedText);
|
| | | StringTrimRight(normalizedText);
|
| | |
|
| | | int textLength = StringLen(normalizedText);
|
| | |
|
| | | if(textLength == 0)
|
| | | return(false);
|
| | |
|
| | | bool decimalPointFound = false;
|
| | | int digitCount = 0;
|
| | | int fractionalDigits = 0;
|
| | |
|
| | | //--- Validate the complete input before numeric conversion
|
| | | for(int i = 0; i < textLength; i++)
|
| | | {
|
| | | ushort character =
|
| | | (ushort)StringGetCharacter(normalizedText,
|
| | | i);
|
| | |
|
| | | if(character >= '0' &&
|
| | | character <= '9')
|
| | | {
|
| | | digitCount++;
|
| | |
|
| | | if(decimalPointFound)
|
| | | fractionalDigits++;
|
| | |
|
| | | continue;
|
| | | }
|
| | |
|
| | | if(character == '.')
|
| | | {
|
| | | if(decimalPointFound)
|
| | | return(false);
|
| | |
|
| | | if(i == 0 ||
|
| | | i == textLength - 1)
|
| | | return(false);
|
| | |
|
| | | decimalPointFound = true;
|
| | |
|
| | | continue;
|
| | | }
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | if(digitCount == 0)
|
| | | return(false);
|
| | |
|
| | | //--- Enforce the account currency precision
|
| | | int currencyDigits = GetAccountCurrencyDigits();
|
| | |
|
| | | if(currencyDigits < 0)
|
| | | return(false);
|
| | |
|
| | | if(fractionalDigits > currencyDigits)
|
| | | return(false);
|
| | |
|
| | | //--- Convert only after the whole string has passed validation
|
| | | double parsedValue =
|
| | | StringToDouble(normalizedText);
|
| | |
|
| | | if(!MathIsValidNumber(parsedValue) ||
|
| | | parsedValue <= 0.0)
|
| | | return(false);
|
| | |
|
| | | startingBalance = parsedValue;
|
| | |
|
| | | return(true);
|
| | | }
|
| | |
|
2026-10-14 11:43:36 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Check daily session state consistency |
|
| | | //+------------------------------------------------------------------+
|
| | | bool IsDailySessionStateConsistent(const SDailySession &session)
|
| | | {
|
| | | //--- An unconfirmed session must remain in the waiting state
|
| | | if(session.status == SESSION_AWAITING_CONFIRMATION)
|
| | | return(!session.balanceConfirmed);
|
| | |
|
| | | //--- An active session requires a confirmed daily baseline
|
| | | if(session.status == SESSION_ACTIVE)
|
| | | return(session.balanceConfirmed);
|
| | |
|
| | | //--- A closed session may have ended with or without confirmation
|
| | | if(session.status == SESSION_CLOSED)
|
| | | return(true);
|
| | |
|
| | | return(false);
|
| | | }
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-09-03 04:14:56 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Open database |
|
2026-09-03 04:14:56 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | bool OpenDatabase()
|
2026-09-03 04:14:56 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | //--- Open or create the SQLite database
|
| | | ResetLastError();
|
2026-09-03 04:14:56 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | g_database = DatabaseOpen(g_databaseName,
|
| | | DATABASE_OPEN_READWRITE |
|
| | | DATABASE_OPEN_CREATE);
|
2026-09-03 04:14:56 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | if(g_database == INVALID_HANDLE)
|
2026-09-03 04:14:56 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Could not open database ",
|
| | | g_databaseName,
|
| | | ". Error: ",
|
| | | GetLastError());
|
2026-09-03 04:14:56 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | return(false);
|
| | | }
|
2026-09-03 04:14:56 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Database opened successfully: ",
|
| | | g_databaseName);
|
2026-09-03 04:14:56 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | return(true);
|
2026-09-03 04:14:56 +03:00 | | | }
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-09-03 09:12:20 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Close database |
|
2026-09-03 09:12:20 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | void CloseDatabase()
|
2026-09-03 09:12:20 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | //--- Ignore the request when no database connection is active
|
| | | if(g_database == INVALID_HANDLE)
|
| | | return;
|
2026-09-03 09:12:20 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Close the active SQLite connection
|
| | | ResetLastError();
|
2026-09-03 09:12:20 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | DatabaseClose(g_database);
|
2026-09-03 09:12:20 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | int errorCode = GetLastError();
|
2026-09-03 09:12:20 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | if(errorCode != 0)
|
2026-09-03 09:12:20 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Database close reported error: ",
|
| | | errorCode);
|
2026-09-03 09:12:20 +03:00 | | | }
|
| | | else
|
| | | {
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Database closed successfully.");
|
2026-09-03 09:12:20 +03:00 | | | }
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Clear the stored database handle
|
| | | g_database = INVALID_HANDLE;
|
2026-09-03 09:12:20 +03:00 | | | }
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-09-04 05:24:37 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Ensure database tables exist |
|
2026-09-04 05:24:37 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | bool EnsureDatabaseTables()
|
2026-09-04 05:24:37 +03:00 | | | {
|
2026-10-14 11:43:36 +03:00 | | | //--- Require an active SQLite connection before creating tables
|
| | | if(g_database == INVALID_HANDLE)
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot ensure database tables without an active database.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Define persistent storage for each 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)"
|
| | | ");";
|
2026-09-04 05:24:37 +03:00 | | |
|
2026-10-14 11:43:36 +03:00 | | | //--- Define persistent storage for each configured daily session
|
| | | string dailySessionsTable =
|
| | | "CREATE TABLE IF NOT EXISTS daily_sessions ("
|
| | | "account_login INTEGER NOT NULL,"
|
| | | "account_server TEXT NOT NULL,"
|
| | | "session_date TEXT NOT NULL,"
|
| | | "starting_balance REAL NOT NULL,"
|
| | | "starting_equity REAL NOT NULL,"
|
| | | "balance_confirmed INTEGER NOT NULL,"
|
| | | "session_status INTEGER NOT NULL,"
|
| | | "created_at INTEGER NOT NULL,"
|
| | | "updated_at INTEGER NOT NULL,"
|
| | | "PRIMARY KEY(account_login, account_server, session_date)"
|
| | | ");";
|
| | |
|
| | | //--- Ensure the existing account settings table is available
|
2026-10-12 12:58:53 +03:00 | | | ResetLastError();
|
2026-09-19 14:21:11 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | if(!DatabaseExecute(g_database,
|
| | | settingsTable))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not create settings table. Error: ",
|
| | | GetLastError());
|
2026-09-08 15:01:59 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | return(false);
|
| | | }
|
2026-09-17 20:37:41 +03:00 | | |
|
2026-10-14 11:43:36 +03:00 | | | //--- Ensure the daily session table is available
|
| | | ResetLastError();
|
| | |
|
| | | if(!DatabaseExecute(g_database,
|
| | | dailySessionsTable))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not create daily_sessions table. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | return(true);
|
2026-09-08 15:01:59 +03:00 | | | }
|
2026-09-19 14:21:11 +03:00 | | |
|
2026-09-08 15:01:59 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Finalize database request |
|
2026-09-08 15:01:59 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | void FinalizeDatabaseRequest(const int request)
|
2026-09-08 15:01:59 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | //--- Ignore invalid database requests
|
| | | if(request == INVALID_HANDLE)
|
2026-09-08 15:01:59 +03:00 | | | return;
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Release the prepared database request
|
| | | ResetLastError();
|
2026-09-08 15:01:59 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | DatabaseFinalize(request);
|
2026-09-08 15:01:59 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | int errorCode = GetLastError();
|
2026-09-08 15:01:59 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | if(errorCode != 0)
|
2026-09-08 15:01:59 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Could not finalize database request. Error: ",
|
| | | errorCode);
|
2026-09-08 15:01:59 +03:00 | | | }
|
| | | }
|
2026-09-19 14:21:11 +03:00 | | |
|
2026-09-09 09:43:34 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Execute prepared database statement |
|
2026-09-09 09:43:34 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | bool ExecuteDatabaseRequest(const int request,
|
| | | const string operation)
|
2026-09-09 09:43:34 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | //--- Execute the prepared database statement
|
| | | ResetLastError();
|
2026-09-09 09:43:34 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | bool result = DatabaseRead(request);
|
2026-09-09 09:43:34 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | int errorCode = GetLastError();
|
2026-09-09 09:43:34 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Accept normal completion when no result row is returned
|
| | | bool success =
|
| | | (result || errorCode == ERR_DATABASE_NO_MORE_DATA);
|
2026-09-19 14:21:11 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | if(!success)
|
2026-09-09 09:43:34 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Database error while ",
|
| | | operation,
|
| | | ". Error: ",
|
| | | errorCode);
|
2026-09-09 09:43:34 +03:00 | | | }
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Release the request after execution
|
| | | FinalizeDatabaseRequest(request);
|
2026-09-09 09:43:34 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | return(success);
|
2026-09-09 09:43:34 +03:00 | | | }
|
2026-09-19 14:21:11 +03:00 | | |
|
2026-09-09 09:43:34 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Save account settings |
|
2026-09-09 09:43:34 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | bool SaveAccountSettings()
|
2026-09-09 09:43:34 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | //--- Prepare the account-settings write statement
|
| | | 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);";
|
2026-09-09 09:43:34 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | int request = DatabasePrepare(g_database, sql);
|
2026-09-09 09:43:34 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | if(request == INVALID_HANDLE)
|
2026-09-09 09:43:34 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Could not prepare settings save request. Error: ",
|
| | | GetLastError());
|
2026-09-09 09:43:34 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | return(false);
|
2026-09-09 09:43:34 +03:00 | | | }
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Bind the current account settings to the prepared 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))
|
2026-09-09 09:43:34 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Could not bind settings values. Error: ",
|
| | | GetLastError());
|
2026-09-09 09:43:34 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | FinalizeDatabaseRequest(request);
|
| | | return(false);
|
2026-09-09 09:43:34 +03:00 | | | }
|
2026-09-04 06:11:32 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Execute and finalize the prepared write request
|
| | | if(!ExecuteDatabaseRequest(request, "saving account settings"))
|
| | | return(false);
|
2026-09-04 06:11:32 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | return(true);
|
2026-09-04 06:11:32 +03:00 | | | }
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-09-04 06:11:32 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Load account settings |
|
2026-09-04 06:11:32 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | bool LoadAccountSettings()
|
2026-09-04 06:11:32 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | //--- Build the current account/server lookup
|
| | | string accountServer = AccountInfoString(ACCOUNT_SERVER);
|
2026-09-04 06:11:32 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | 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;";
|
2026-09-04 06:11:32 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | int request = DatabasePrepare(g_database, sql);
|
2026-09-04 06:11:32 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | if(request == INVALID_HANDLE)
|
2026-09-04 06:11:32 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Could not prepare settings load request. Error: ",
|
| | | GetLastError());
|
2026-09-04 06:11:32 +03:00 | | |
|
| | | return(false);
|
| | | }
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Bind the current account identity
|
| | | if(!DatabaseBind(request, 0, g_accountState.login) ||
|
| | | !DatabaseBind(request, 1, accountServer))
|
2026-09-04 06:29:56 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Could not bind settings search values. Error: ",
|
2026-09-04 06:29:56 +03:00 | | | GetLastError());
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | FinalizeDatabaseRequest(request);
|
2026-09-04 06:29:56 +03:00 | | | return(false);
|
| | | }
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Read the matching settings record
|
2026-09-04 06:29:56 +03:00 | | | ResetLastError();
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | if(!DatabaseRead(request))
|
2026-09-04 06:29:56 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | int errorCode = GetLastError();
|
2026-09-04 06:29:56 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | FinalizeDatabaseRequest(request);
|
2026-09-04 06:29:56 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | //--- No row means this account/server pair has not been saved yet
|
| | | if(errorCode == ERR_DATABASE_NO_MORE_DATA)
|
| | | return(false);
|
| | |
|
| | | Print("Prop Firm Guard: Could not read account settings. Error: ",
|
| | | errorCode);
|
2026-09-04 06:29:56 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | return(false);
|
2026-09-04 06:29:56 +03:00 | | | }
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Read and validate the stored column values
|
| | | long accountLogin;
|
| | | string storedServer;
|
| | | double initialBalance;
|
| | | double dailyDrawdown;
|
| | | double overallDrawdown;
|
| | | double profitTarget;
|
| | | long updatedAt;
|
2026-09-04 06:29:56 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | 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))
|
2026-09-04 06:29:56 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | Print("Prop Firm Guard: Could not read stored settings columns. Error: ",
|
2026-09-04 06:29:56 +03:00 | | | GetLastError());
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | FinalizeDatabaseRequest(request);
|
2026-09-04 06:29:56 +03:00 | | | return(false);
|
| | | }
|
| | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Restore the stored values into the account settings structure
|
| | | 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;
|
2026-09-04 06:29:56 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Release the completed read request
|
| | | FinalizeDatabaseRequest(request);
|
2026-09-04 06:29:56 +03:00 | | |
|
| | | return(true);
|
| | | }
|
2026-09-07 04:17:47 +03:00 | | |
|
| | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Initialize account settings |
|
2026-09-07 04:17:47 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | bool InitializeAccountSettings()
|
2026-09-07 04:17:47 +03:00 | | | {
|
2026-10-12 12:58:53 +03:00 | | | //--- Check whether this account/server pair already has stored settings
|
| | | bool settingsFound = LoadAccountSettings();
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Associate the settings with the current trading account
|
| | | g_accountSettings.accountLogin = g_accountState.login;
|
| | | g_accountSettings.accountServer = AccountInfoString(ACCOUNT_SERVER);
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Synchronize persistence with the current Inputs values
|
| | | g_accountSettings.initialBalance = InpInitialAccountBalance;
|
| | | g_accountSettings.dailyDrawdownPercent = InpDailyDrawdownPercent;
|
| | | g_accountSettings.overallDrawdownPercent = InpOverallDrawdownPercent;
|
| | | g_accountSettings.profitTargetPercent = InpProfitTargetPercent;
|
| | | g_accountSettings.updatedAt = TimeCurrent();
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Save the synchronized configuration
|
| | | if(!SaveAccountSettings())
|
| | | return(false);
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | //--- Report whether the record was created or synchronized
|
| | | if(settingsFound)
|
| | | Print("Prop Firm Guard: Account settings restored and synchronized.");
|
| | | else
|
| | | Print("Prop Firm Guard: New account settings saved.");
|
2026-09-07 04:17:47 +03:00 | | |
|
2026-10-12 12:58:53 +03:00 | | | return(true);
|
2026-09-07 04:17:47 +03:00 | | | }
|
2026-10-14 11:43:36 +03:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Save daily session |
|
| | | //+------------------------------------------------------------------+
|
| | | bool SaveDailySession()
|
| | | {
|
| | | //--- Require an active SQLite connection
|
| | | if(g_database == INVALID_HANDLE)
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot save daily session without an active database.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Reject an incomplete session identity
|
| | | if(g_dailySession.accountLogin <= 0 ||
|
| | | g_dailySession.accountServer == "" ||
|
| | | g_dailySession.sessionDate == "")
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot save daily session with an incomplete identity.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Prevent contradictory confirmation and lifecycle states
|
| | | if(!IsDailySessionStateConsistent(g_dailySession))
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot save an inconsistent daily session state.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Insert a new session or update only its mutable fields
|
| | | string sql =
|
| | | "INSERT INTO daily_sessions ("
|
| | | "account_login,"
|
| | | "account_server,"
|
| | | "session_date,"
|
| | | "starting_balance,"
|
| | | "starting_equity,"
|
| | | "balance_confirmed,"
|
| | | "session_status,"
|
| | | "created_at,"
|
| | | "updated_at"
|
| | | ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) "
|
| | | "ON CONFLICT(account_login, account_server, session_date) "
|
| | | "DO UPDATE SET "
|
| | | "starting_balance = excluded.starting_balance,"
|
| | | "starting_equity = excluded.starting_equity,"
|
| | | "balance_confirmed = excluded.balance_confirmed,"
|
| | | "session_status = excluded.session_status,"
|
| | | "updated_at = excluded.updated_at;";
|
| | |
|
| | | ResetLastError();
|
| | |
|
| | | int request = DatabasePrepare(g_database,
|
| | | sql);
|
| | |
|
| | | if(request == INVALID_HANDLE)
|
| | | {
|
| | | Print("Prop Firm Guard: Could not prepare daily session save request. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Bind the complete daily session to the prepared statement
|
| | | if(!DatabaseBind(request, 0, g_dailySession.accountLogin) ||
|
| | | !DatabaseBind(request, 1, g_dailySession.accountServer) ||
|
| | | !DatabaseBind(request, 2, g_dailySession.sessionDate) ||
|
| | | !DatabaseBind(request, 3, g_dailySession.startingBalance) ||
|
| | | !DatabaseBind(request, 4, g_dailySession.startingEquity) ||
|
| | | !DatabaseBind(request, 5, g_dailySession.balanceConfirmed ? 1 : 0) ||
|
| | | !DatabaseBind(request, 6, (int)g_dailySession.status) ||
|
| | | !DatabaseBind(request, 7, (long)g_dailySession.createdAt) ||
|
| | | !DatabaseBind(request, 8, (long)g_dailySession.updatedAt))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not bind daily session values. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | FinalizeDatabaseRequest(request);
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Execute the UPSERT and release the prepared request
|
| | | if(!ExecuteDatabaseRequest(request,
|
| | | "saving daily session"))
|
| | | return(false);
|
| | |
|
| | | return(true);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Load daily session |
|
| | | //+------------------------------------------------------------------+
|
| | | ENUM_DAILY_SESSION_LOAD_RESULT LoadDailySession(const string sessionDate)
|
| | | {
|
| | | //--- Require an active SQLite connection
|
| | | if(g_database == INVALID_HANDLE)
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot load daily session without an active database.");
|
| | |
|
| | | return(SESSION_LOAD_ERROR);
|
| | | }
|
| | |
|
| | | //--- Reject an empty session key
|
| | | if(sessionDate == "")
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot load a daily session with an empty session date.");
|
| | |
|
| | | return(SESSION_LOAD_ERROR);
|
| | | }
|
| | |
|
| | | //--- Build the current account and session lookup
|
| | | string accountServer = AccountInfoString(ACCOUNT_SERVER);
|
| | |
|
| | | string sql =
|
| | | "SELECT "
|
| | | "account_login,"
|
| | | "account_server,"
|
| | | "session_date,"
|
| | | "starting_balance,"
|
| | | "starting_equity,"
|
| | | "balance_confirmed,"
|
| | | "session_status,"
|
| | | "created_at,"
|
| | | "updated_at "
|
| | | "FROM daily_sessions "
|
| | | "WHERE account_login = ?1 "
|
| | | "AND account_server = ?2 "
|
| | | "AND session_date = ?3 "
|
| | | "LIMIT 1;";
|
| | |
|
| | | ResetLastError();
|
| | |
|
| | | int request = DatabasePrepare(g_database,
|
| | | sql);
|
| | |
|
| | | if(request == INVALID_HANDLE)
|
| | | {
|
| | | Print("Prop Firm Guard: Could not prepare daily session load request. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | return(SESSION_LOAD_ERROR);
|
| | | }
|
| | |
|
| | | //--- Bind the complete session identity
|
| | | if(!DatabaseBind(request, 0, g_accountState.login) ||
|
| | | !DatabaseBind(request, 1, accountServer) ||
|
| | | !DatabaseBind(request, 2, sessionDate))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not bind daily session search values. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | FinalizeDatabaseRequest(request);
|
| | |
|
| | | return(SESSION_LOAD_ERROR);
|
| | | }
|
| | |
|
| | | //--- Attempt to read the matching session in the same database request
|
| | | ResetLastError();
|
| | |
|
| | | if(!DatabaseRead(request))
|
| | | {
|
| | | int errorCode = GetLastError();
|
| | |
|
| | | FinalizeDatabaseRequest(request);
|
| | |
|
| | | //--- Absence is a normal result when the configured day is new
|
| | | if(errorCode == ERR_DATABASE_NO_MORE_DATA)
|
| | | return(SESSION_LOAD_NOT_FOUND);
|
| | |
|
| | | Print("Prop Firm Guard: Could not read daily session. Error: ",
|
| | | errorCode);
|
| | |
|
| | | return(SESSION_LOAD_ERROR);
|
| | | }
|
| | |
|
| | | //--- Read every column before changing the active in-memory session
|
| | | long accountLogin;
|
| | | string storedServer;
|
| | | string storedSessionDate;
|
| | | double startingBalance;
|
| | | double startingEquity;
|
| | | int balanceConfirmed;
|
| | | int sessionStatus;
|
| | | long createdAt;
|
| | | long updatedAt;
|
| | |
|
| | | bool loaded =
|
| | | DatabaseColumnLong(request, 0, accountLogin) &&
|
| | | DatabaseColumnText(request, 1, storedServer) &&
|
| | | DatabaseColumnText(request, 2, storedSessionDate) &&
|
| | | DatabaseColumnDouble(request, 3, startingBalance) &&
|
| | | DatabaseColumnDouble(request, 4, startingEquity) &&
|
| | | DatabaseColumnInteger(request, 5, balanceConfirmed) &&
|
| | | DatabaseColumnInteger(request, 6, sessionStatus) &&
|
| | | DatabaseColumnLong(request, 7, createdAt) &&
|
| | | DatabaseColumnLong(request, 8, updatedAt);
|
| | |
|
| | | if(!loaded)
|
| | | {
|
| | | Print("Prop Firm Guard: Could not read daily session columns. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | FinalizeDatabaseRequest(request);
|
| | |
|
| | | return(SESSION_LOAD_ERROR);
|
| | | }
|
| | |
|
| | | //--- Release the completed read request
|
| | | FinalizeDatabaseRequest(request);
|
| | |
|
| | | //--- Validate persisted boolean and enum representations
|
| | | if(balanceConfirmed != 0 &&
|
| | | balanceConfirmed != 1)
|
| | | {
|
| | | Print("Prop Firm Guard: Stored daily session has an invalid confirmation value.");
|
| | |
|
| | | return(SESSION_LOAD_ERROR);
|
| | | }
|
| | |
|
| | | if(sessionStatus < (int)SESSION_AWAITING_CONFIRMATION ||
|
| | | sessionStatus > (int)SESSION_CLOSED)
|
| | | {
|
| | | Print("Prop Firm Guard: Stored daily session has an invalid status value.");
|
| | |
|
| | | return(SESSION_LOAD_ERROR);
|
| | | }
|
| | |
|
| | | //--- Build a temporary session before replacing the active state
|
| | | SDailySession loadedSession;
|
| | |
|
| | | loadedSession.accountLogin = accountLogin;
|
| | | loadedSession.accountServer = storedServer;
|
| | | loadedSession.sessionDate = storedSessionDate;
|
| | | loadedSession.startingBalance = startingBalance;
|
| | | loadedSession.startingEquity = startingEquity;
|
| | | loadedSession.balanceConfirmed = (balanceConfirmed == 1);
|
| | | loadedSession.status =
|
| | | (ENUM_DAILY_SESSION_STATUS)sessionStatus;
|
| | | loadedSession.createdAt = (datetime)createdAt;
|
| | | loadedSession.updatedAt = (datetime)updatedAt;
|
| | |
|
| | | //--- Reject a persisted state that contradicts its confirmation flag
|
| | | if(!IsDailySessionStateConsistent(loadedSession))
|
| | | {
|
| | | Print("Prop Firm Guard: Stored daily session contains an inconsistent state.");
|
| | |
|
| | | return(SESSION_LOAD_ERROR);
|
| | | }
|
| | |
|
| | | //--- Restore the validated session only after the complete read succeeds
|
| | | g_dailySession = loadedSession;
|
| | |
|
| | | return(SESSION_LOAD_FOUND);
|
| | | }
|
2026-10-14 11:54:45 +03:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Initialize daily session |
|
| | | //+------------------------------------------------------------------+
|
| | | bool InitializeDailySession()
|
| | | {
|
| | | //--- Derive the session key from the configured reset boundary
|
| | | string sessionDate = GetCurrentSessionDate();
|
| | |
|
| | | if(sessionDate == "")
|
| | | {
|
| | | Print("Prop Firm Guard: Could not derive the current daily session date.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Attempt to restore the session for the current account and day
|
| | | ENUM_DAILY_SESSION_LOAD_RESULT loadResult =
|
| | | LoadDailySession(sessionDate);
|
| | |
|
| | | if(loadResult == SESSION_LOAD_FOUND)
|
| | | {
|
| | | //--- Mark the restored session as available to the runtime
|
| | | g_hasDailySession = true;
|
| | |
|
| | | Print("Prop Firm Guard: Daily session restored for ",
|
| | | sessionDate,
|
| | | ".");
|
| | |
|
| | | return(true);
|
| | | }
|
| | |
|
| | | //--- Stop initialization when the database lookup failed
|
| | | if(loadResult == SESSION_LOAD_ERROR)
|
| | | {
|
| | | Print("Prop Firm Guard: Could not restore the current daily session.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Build a new unconfirmed session when no stored record exists
|
| | | datetime currentTime = TimeCurrent();
|
| | |
|
| | | g_dailySession.accountLogin = g_accountState.login;
|
| | | g_dailySession.accountServer = AccountInfoString(ACCOUNT_SERVER);
|
| | | g_dailySession.sessionDate = sessionDate;
|
| | | g_dailySession.startingBalance = 0.0;
|
| | | g_dailySession.startingEquity = 0.0;
|
| | | g_dailySession.balanceConfirmed = false;
|
| | | g_dailySession.status = SESSION_AWAITING_CONFIRMATION;
|
| | | g_dailySession.createdAt = currentTime;
|
| | | g_dailySession.updatedAt = currentTime;
|
| | |
|
| | | //--- Persist the newly created session before exposing it as active
|
| | | if(!SaveDailySession())
|
| | | {
|
| | | Print("Prop Firm Guard: Could not save the new daily session.");
|
| | |
|
| | | g_hasDailySession = false;
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Mark the persisted session as available to the runtime
|
| | | g_hasDailySession = true;
|
| | |
|
| | | Print("Prop Firm Guard: New daily session created for ",
|
| | | sessionDate,
|
| | | ".");
|
| | |
|
| | | return(true);
|
| | | }
|
2026-10-14 12:59:23 +03:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Delete daily session controls |
|
| | | //+------------------------------------------------------------------+
|
| | | bool DeleteDailySessionControls()
|
| | | {
|
| | | bool success = true;
|
| | |
|
| | | //--- Remove the starting balance edit field when present
|
| | | if(ObjectFind(0,
|
| | | g_startBalanceEdit) >= 0)
|
| | | {
|
| | | ResetLastError();
|
| | |
|
| | | if(!ObjectDelete(0,
|
| | | g_startBalanceEdit))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not delete starting balance field. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | success = false;
|
| | | }
|
| | | }
|
| | |
|
| | | //--- Remove the confirmation button when present
|
| | | if(ObjectFind(0,
|
| | | g_confirmButton) >= 0)
|
| | | {
|
| | | ResetLastError();
|
| | |
|
| | | if(!ObjectDelete(0,
|
| | | g_confirmButton))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not delete confirmation button. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | success = false;
|
| | | }
|
| | | }
|
| | |
|
| | | return(success);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Create starting balance edit |
|
| | | //+------------------------------------------------------------------+
|
| | | bool CreateStartingBalanceEdit(const int x,
|
| | | const int y,
|
| | | const string suggestedBalance)
|
| | | {
|
| | | //--- Create the editable starting balance field
|
| | | ResetLastError();
|
| | |
|
| | | if(!ObjectCreate(0,
|
| | | g_startBalanceEdit,
|
| | | OBJ_EDIT,
|
| | | 0,
|
| | | 0,
|
| | | 0))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not create starting balance field. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Configure the field used to review or change the suggestion
|
| | | 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());
|
| | |
|
| | | if(!ObjectDelete(0,
|
| | | g_startBalanceEdit))
|
| | | Print("Prop Firm Guard: Could not remove incomplete starting balance field.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Apply the suggested balance and font
|
| | | 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());
|
| | |
|
| | | if(!ObjectDelete(0,
|
| | | g_startBalanceEdit))
|
| | | Print("Prop Firm Guard: Could not remove incomplete starting balance field.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | return(true);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Create confirm balance button |
|
| | | //+------------------------------------------------------------------+
|
| | | bool CreateConfirmBalanceButton(const int x,
|
| | | const int y)
|
| | | {
|
| | | //--- Create the action control for daily balance confirmation
|
| | | ResetLastError();
|
| | |
|
| | | if(!ObjectCreate(0,
|
| | | g_confirmButton,
|
| | | OBJ_BUTTON,
|
| | | 0,
|
| | | 0,
|
| | | 0))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not create confirmation button. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Configure the confirmation button
|
| | | 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());
|
| | |
|
| | | if(!ObjectDelete(0,
|
| | | g_confirmButton))
|
| | | Print("Prop Firm Guard: Could not remove incomplete confirmation button.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Apply the button label and font
|
| | | 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());
|
| | |
|
| | | if(!ObjectDelete(0,
|
| | | g_confirmButton))
|
| | | Print("Prop Firm Guard: Could not remove incomplete confirmation button.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | return(true);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Synchronize daily session controls |
|
| | | //+------------------------------------------------------------------+
|
| | | bool SynchronizeDailySessionControls()
|
| | | {
|
| | | //--- Require a loaded session before changing its interface
|
| | | if(!g_hasDailySession)
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot synchronize controls without a daily session.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Confirmed sessions must not expose confirmation controls
|
| | | if(g_dailySession.balanceConfirmed)
|
| | | return(DeleteDailySessionControls());
|
| | |
|
| | | //--- Preserve an existing complete control set and any edited text
|
| | | bool editExists =
|
| | | (ObjectFind(0,
|
| | | g_startBalanceEdit) >= 0);
|
| | |
|
| | | bool buttonExists =
|
| | | (ObjectFind(0,
|
| | | g_confirmButton) >= 0);
|
| | |
|
| | | if(editExists &&
|
| | | buttonExists)
|
| | | return(true);
|
| | |
|
| | | //--- Remove a partial or stale owned control set before rebuilding
|
| | | if(editExists ||
|
| | | buttonExists)
|
| | | {
|
| | | if(!DeleteDailySessionControls())
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Format the current balance only when a new field is required
|
| | | string suggestedBalance;
|
| | |
|
| | | if(!FormatAccountMoney(g_accountState.balance,
|
| | | suggestedBalance))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not format the suggested starting balance.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | int controlX = InpDashboardX + 18;
|
| | | int controlY = InpDashboardY + 120;
|
| | |
|
| | | //--- Build the complete confirmation interface
|
| | | if(!CreateStartingBalanceEdit(controlX,
|
| | | controlY,
|
| | | suggestedBalance))
|
| | | return(false);
|
| | |
|
| | | if(!CreateConfirmBalanceButton(controlX + 130,
|
| | | controlY))
|
| | | {
|
| | | DeleteDailySessionControls();
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Refresh the chart after the complete control set is created
|
| | | ChartRedraw(0);
|
| | |
|
| | | return(true);
|
| | | }
|
2026-10-14 13:31:12 +03:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Confirm daily starting balance |
|
| | | //+------------------------------------------------------------------+
|
| | | bool ConfirmDailyStartingBalance()
|
| | | {
|
| | | //--- Require an initialized daily session
|
| | | if(!g_hasDailySession)
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot confirm a starting balance without a daily session.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Accept confirmation only while the session is awaiting it
|
| | | if(g_dailySession.status != SESSION_AWAITING_CONFIRMATION)
|
| | | {
|
| | | Print("Prop Firm Guard: Current daily session is not awaiting balance confirmation.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Protect an already confirmed baseline from accidental overwrite
|
| | | if(g_dailySession.balanceConfirmed)
|
| | | {
|
| | | Print("Prop Firm Guard: Daily starting balance is already confirmed.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Require the starting balance field to exist
|
| | | if(ObjectFind(0,
|
| | | g_startBalanceEdit) < 0)
|
| | | {
|
| | | Print("Prop Firm Guard: Starting balance field is not available.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Read the complete text entered by the trader
|
| | | string balanceText;
|
| | |
|
| | | ResetLastError();
|
| | |
|
| | | if(!ObjectGetString(0,
|
| | | g_startBalanceEdit,
|
| | | OBJPROP_TEXT,
|
| | | 0,
|
| | | balanceText))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not read starting balance input. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Validate the complete input before changing session state
|
| | | double startingBalance = 0.0;
|
| | |
|
| | | if(!ParseStartingBalance(balanceText,
|
| | | startingBalance))
|
| | | {
|
| | | Print("Prop Firm Guard: Starting balance must be a valid positive monetary value.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Preserve the previous session state for persistence rollback
|
| | | SDailySession previousSession = g_dailySession;
|
| | |
|
| | | //--- Refresh live account values immediately before capturing equity
|
| | | UpdateAccountState();
|
| | |
|
| | | //--- Convert the waiting session into a confirmed active session
|
| | | g_dailySession.startingBalance = startingBalance;
|
| | | g_dailySession.startingEquity = g_accountState.equity;
|
| | | g_dailySession.balanceConfirmed = true;
|
| | | g_dailySession.status = SESSION_ACTIVE;
|
| | | g_dailySession.updatedAt = TimeCurrent();
|
| | |
|
| | | //--- Persist the confirmed state before changing presentation state
|
| | | if(!SaveDailySession())
|
| | | {
|
| | | //--- Restore the previous in-memory state after persistence failure
|
| | | g_dailySession = previousSession;
|
| | |
|
| | | Print("Prop Firm Guard: Starting balance confirmation was not saved. "
|
| | | "The previous session state has been restored.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Format the confirmed monetary values for reporting
|
| | | string formattedBalance;
|
| | | string formattedEquity;
|
| | |
|
| | | if(!FormatAccountMoney(g_dailySession.startingBalance,
|
| | | formattedBalance))
|
| | | formattedBalance = DoubleToString(g_dailySession.startingBalance);
|
| | |
|
| | | if(!FormatAccountMoney(g_dailySession.startingEquity,
|
| | | formattedEquity))
|
| | | formattedEquity = DoubleToString(g_dailySession.startingEquity);
|
| | |
|
| | | //--- Synchronize presentation only after persistence succeeds
|
| | | if(!SynchronizeDailySessionControls())
|
| | | {
|
| | | Print("Prop Firm Guard: Starting balance was confirmed and saved, "
|
| | | "but the confirmation controls could not be synchronized.");
|
| | |
|
| | | return(true);
|
| | | }
|
| | |
|
| | | Print("Prop Firm Guard: Daily starting balance confirmed at ",
|
| | | formattedBalance,
|
| | | ". Equity captured at confirmation: ",
|
| | | formattedEquity,
|
| | | ".");
|
| | |
|
| | | return(true);
|
| | | }
|
2026-10-14 13:43:20 +03:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Check for new trading day |
|
| | | //+------------------------------------------------------------------+
|
| | | bool CheckForNewTradingDay()
|
| | | {
|
| | | //--- Require an initialized daily session before checking rollover
|
| | | if(!g_hasDailySession)
|
| | | {
|
| | | Print("Prop Firm Guard: Cannot check session rollover without a daily session.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Derive the session key from the currently known server time
|
| | | string currentSessionDate = GetCurrentSessionDate();
|
| | |
|
| | | if(currentSessionDate == "")
|
| | | {
|
| | | Print("Prop Firm Guard: Could not derive the current session date.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Keep the current session while its configured day is unchanged
|
| | | if(currentSessionDate == g_dailySession.sessionDate)
|
| | | return(true);
|
| | |
|
| | | //--- Preserve the active state in case closing persistence fails
|
| | | SDailySession previousSession = g_dailySession;
|
| | |
|
| | | //--- Close the previous session before changing the active day
|
| | | g_dailySession.status = SESSION_CLOSED;
|
| | | g_dailySession.updatedAt = TimeCurrent();
|
| | |
|
| | | if(!SaveDailySession())
|
| | | {
|
| | | g_dailySession = previousSession;
|
| | |
|
| | | Print("Prop Firm Guard: Could not close daily session ",
|
| | | previousSession.sessionDate,
|
| | | ". Rollover has been postponed.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | //--- Preserve the exact closed state that was successfully persisted
|
| | | SDailySession closedSession = g_dailySession;
|
| | |
|
| | | g_hasDailySession = false;
|
| | |
|
| | | //--- Restore or create the session for the new configured day
|
| | | if(!InitializeDailySession())
|
| | | {
|
| | | g_dailySession = closedSession;
|
| | | g_hasDailySession = true;
|
| | |
|
| | | if(!DeleteDailySessionControls())
|
| | | Print("Prop Firm Guard: Could not remove stale session controls after rollover failure.");
|
| | |
|
| | | Print("Prop Firm Guard: Previous session was closed, but the new daily session could not be initialized.");
|
| | |
|
| | | return(false);
|
| | | }
|
| | |
|
| | | Print("Prop Firm Guard: Daily session rollover completed from ",
|
| | | previousSession.sessionDate,
|
| | | " to ",
|
| | | g_dailySession.sessionDate,
|
| | | ".");
|
| | |
|
| | | return(true);
|
| | | }
|
2026-10-14 09:50:20 +03:00 | | |
|
2026-10-12 13:46:46 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-14 09:50:20 +03:00 | | | //| Expert initialization function |
|
2026-10-12 13:46:46 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-14 09:50:20 +03:00 | | | int OnInit()
|
2026-10-12 13:46:46 +03:00 | | | {
|
2026-10-14 14:14:05 +03:00 | | | //--- Validate the complete Part 1 and Part 2 input configuration
|
2026-10-14 09:50:20 +03:00 | | | if(!ValidateComplianceInputs())
|
| | | return(INIT_PARAMETERS_INCORRECT);
|
2026-10-12 13:46:46 +03:00 | | |
|
2026-10-14 14:14:05 +03:00 | | | //--- Refresh the account identity and current financial state
|
2026-10-14 09:50:20 +03:00 | | | UpdateAccountState();
|
2026-10-12 13:46:46 +03:00 | | |
|
2026-10-14 14:14:05 +03:00 | | | //--- Enforce one Prop Firm Guard instance for the trading account
|
| | | if(!AcquireInstanceLock())
|
| | | return(INIT_FAILED);
|
| | |
|
2026-10-14 09:50:20 +03:00 | | | //--- Open the SQLite persistence layer
|
| | | if(!OpenDatabase())
|
2026-10-12 13:46:46 +03:00 | | | {
|
2026-10-14 09:50:20 +03:00 | | | Print("Prop Firm Guard: Database initialization failed.");
|
2026-10-12 13:46:46 +03:00 | | |
|
2026-10-14 14:14:05 +03:00 | | | if(!ReleaseInstanceLock())
|
| | | Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
|
| | |
|
2026-10-14 09:50:20 +03:00 | | | return(INIT_FAILED);
|
2026-10-12 13:46:46 +03:00 | | | }
|
| | |
|
2026-10-14 14:14:05 +03:00 | | | //--- Ensure all persistence tables required through Part 2 exist
|
2026-10-14 09:50:20 +03:00 | | | if(!EnsureDatabaseTables())
|
2026-10-12 13:46:46 +03:00 | | | {
|
2026-10-14 09:50:20 +03:00 | | | Print("Prop Firm Guard: Could not initialize database tables.");
|
2026-10-12 13:46:46 +03:00 | | |
|
2026-10-14 09:50:20 +03:00 | | | CloseDatabase();
|
| | |
|
2026-10-14 14:14:05 +03:00 | | | if(!ReleaseInstanceLock())
|
| | | Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
|
| | |
|
2026-10-14 09:50:20 +03:00 | | | return(INIT_FAILED);
|
2026-10-12 13:46:46 +03:00 | | | }
|
| | |
|
2026-10-14 14:14:05 +03:00 | | | //--- Restore and synchronize the Part 1 account settings
|
2026-10-14 09:50:20 +03:00 | | | if(!InitializeAccountSettings())
|
2026-10-12 13:46:46 +03:00 | | | {
|
2026-10-14 09:50:20 +03:00 | | | Print("Prop Firm Guard: Could not initialize account settings.");
|
2026-10-12 13:46:46 +03:00 | | |
|
2026-10-14 09:50:20 +03:00 | | | CloseDatabase();
|
2026-10-12 13:46:46 +03:00 | | |
|
2026-10-14 14:14:05 +03:00 | | | if(!ReleaseInstanceLock())
|
| | | Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
|
| | |
|
| | | return(INIT_FAILED);
|
| | | }
|
| | |
|
| | | //--- Restore or create the current configured daily session
|
| | | if(!InitializeDailySession())
|
| | | {
|
| | | Print("Prop Firm Guard: Could not initialize daily session.");
|
| | |
|
| | | CloseDatabase();
|
| | |
|
| | | if(!ReleaseInstanceLock())
|
| | | Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
|
| | |
|
| | | return(INIT_FAILED);
|
| | | }
|
| | |
|
| | | //--- Match the confirmation controls to the current session state
|
| | | if(!SynchronizeDailySessionControls())
|
| | | {
|
| | | Print("Prop Firm Guard: Could not synchronize daily session controls.");
|
| | |
|
| | | if(!DeleteDailySessionControls())
|
| | | Print("Prop Firm Guard: Daily session control cleanup was incomplete.");
|
| | |
|
| | | CloseDatabase();
|
| | |
|
| | | if(!ReleaseInstanceLock())
|
| | | Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
|
| | |
|
| | | return(INIT_FAILED);
|
| | | }
|
| | |
|
| | | //--- Start periodic daily session maintenance
|
| | | ResetLastError();
|
| | |
|
| | | if(!EventSetTimer(InpTimerSeconds))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not start the session timer. Error: ",
|
| | | GetLastError());
|
| | |
|
| | | if(!DeleteDailySessionControls())
|
| | | Print("Prop Firm Guard: Daily session control cleanup was incomplete.");
|
| | |
|
| | | CloseDatabase();
|
| | |
|
| | | if(!ReleaseInstanceLock())
|
| | | Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
|
| | |
|
2026-10-14 09:50:20 +03:00 | | | return(INIT_FAILED);
|
2026-10-12 13:46:46 +03:00 | | | }
|
| | |
|
2026-10-14 09:50:20 +03:00 | | | Print("Prop Firm Guard initialized successfully.");
|
2026-10-12 13:46:46 +03:00 | | |
|
2026-10-14 09:50:20 +03:00 | | | return(INIT_SUCCEEDED);
|
2026-10-12 13:46:46 +03:00 | | | }
|
2026-10-14 09:50:20 +03:00 | | |
|
2026-10-12 13:46:46 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-14 09:50:20 +03:00 | | | //| Expert deinitialization function |
|
2026-10-12 13:46:46 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-14 09:50:20 +03:00 | | | void OnDeinit(const int reason)
|
2026-10-12 13:46:46 +03:00 | | | {
|
2026-10-14 14:14:05 +03:00 | | | //--- Stop periodic session maintenance first
|
| | | EventKillTimer();
|
| | |
|
| | | //--- Remove chart objects owned by the Part 2 session workflow
|
| | | if(!DeleteDailySessionControls())
|
| | | {
|
| | | Print("Prop Firm Guard: Daily session control cleanup was incomplete.");
|
| | | }
|
| | |
|
| | | //--- Close the SQLite persistence layer
|
2026-10-14 09:50:20 +03:00 | | | CloseDatabase();
|
2026-10-12 13:46:46 +03:00 | | |
|
2026-10-14 14:14:05 +03:00 | | | //--- Release this trading account's terminal instance lock
|
| | | if(!ReleaseInstanceLock())
|
| | | {
|
| | | Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
|
| | | }
|
| | |
|
2026-10-14 09:50:20 +03:00 | | | //--- Record the MetaTrader deinitialization reason
|
2026-10-14 14:14:05 +03:00 | | | Print("Prop Firm Guard removed. Deinitialization reason: ",
|
| | | reason);
|
2026-10-14 09:50:20 +03:00 | | | }
|
2026-09-19 14:21:11 +03:00 | | |
|
2026-09-18 15:50:45 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | //| Expert tick function |
|
2026-09-18 15:50:45 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-12 12:58:53 +03:00 | | | void OnTick()
|
2026-09-18 15:50:45 +03:00 | | | {
|
| | | }
|
2026-10-14 13:43:20 +03:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Timer function |
|
| | | //+------------------------------------------------------------------+
|
| | | void OnTimer()
|
| | | {
|
| | | //--- Refresh the account snapshot used by the session workflow
|
| | | UpdateAccountState();
|
| | |
|
| | | //--- Maintain the configured daily session boundary
|
| | | if(!CheckForNewTradingDay())
|
| | | {
|
| | | Print("Prop Firm Guard: Daily session maintenance could not be completed.");
|
| | |
|
| | | return;
|
| | | }
|
| | |
|
| | | //--- Keep chart controls aligned with the authoritative session state
|
| | | if(!SynchronizeDailySessionControls())
|
| | | {
|
| | | Print("Prop Firm Guard: Daily session controls could not be synchronized.");
|
| | | }
|
| | | }
|
2026-10-14 14:14:05 +03:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Chart event function |
|
| | | //+------------------------------------------------------------------+
|
| | | void OnChartEvent(const int id,
|
| | | const long &lparam,
|
| | | const double &dparam,
|
| | | const string &sparam)
|
| | | {
|
| | | //--- Ignore events that are not graphical object clicks
|
| | | if(id != CHARTEVENT_OBJECT_CLICK)
|
| | | return;
|
| | |
|
| | | //--- Ignore every chart object except the owned confirmation button
|
| | | if(sparam != g_confirmButton)
|
| | | return;
|
| | |
|
| | | //--- Release the button's pressed visual state when it still exists
|
| | | if(ObjectFind(0,
|
| | | g_confirmButton) >= 0)
|
| | | {
|
| | | ResetLastError();
|
| | |
|
| | | if(!ObjectSetInteger(0,
|
| | | g_confirmButton,
|
| | | OBJPROP_STATE,
|
| | | false))
|
| | | {
|
| | | Print("Prop Firm Guard: Could not reset confirmation button state. Error: ",
|
| | | GetLastError());
|
| | | }
|
| | | }
|
| | |
|
| | | //--- Delegate validation, state transition, and persistence
|
| | | ConfirmDailyStartingBalance();
|
| | | }
|
2026-09-19 14:21:11 +03:00 | | | //+------------------------------------------------------------------+
|