PropFirmGuard/PropFirmGuard.mq5
2026-10-14 14:14:05 +03:00

1979 lines
No EOL
65 KiB
MQL5

//+------------------------------------------------------------------+
//| PropFirmGuard.mq5 |
//| Copyright 2026, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
//+------------------------------------------------------------------+
//| Standard Library |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| Enumerations |
//+------------------------------------------------------------------+
//| Represents the general compliance condition of the account. |
//+------------------------------------------------------------------+
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
};
//+------------------------------------------------------------------+
//| Daily session status |
//+------------------------------------------------------------------+
//| Represents the lifecycle state of one persistent daily session. |
//+------------------------------------------------------------------+
enum ENUM_DAILY_SESSION_STATUS
{
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
};
//+------------------------------------------------------------------+
//| 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
};
//+------------------------------------------------------------------+
//| 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 "Daily Session"
input int InpDailyResetHour = 0; // Daily reset hour in broker server time
input int InpDailyResetMinute = 0; // Daily reset minute in broker server time
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
};
//+------------------------------------------------------------------+
//| Daily session structure |
//+------------------------------------------------------------------+
//| Stores the persistent reference for one configured trading day. |
//+------------------------------------------------------------------+
struct SDailySession
{
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
};
//+------------------------------------------------------------------+
//| Global variables |
//+------------------------------------------------------------------+
SAccountState g_accountState; // Latest monitored account state
SAccountSettings g_accountSettings; // Persisted prop-firm settings
SDailySession g_dailySession; // Current configured daily session
string g_startBalanceEdit = "PFG_StartBalanceEdit"; // Starting balance input field
string g_confirmButton = "PFG_ConfirmBalance"; // Daily balance confirmation button
string g_databaseName = "prop_firm_compliance.sqlite"; // SQLite database file
string g_instanceLockName = ""; // Account-specific terminal instance lock
int g_database = INVALID_HANDLE; // Active SQLite database handle
bool g_hasDailySession = false; // Whether a daily session is currently loaded
bool g_instanceLockAcquired = false; // Whether this EA owns the instance lock
//+------------------------------------------------------------------+
//| Validate compliance inputs |
//+------------------------------------------------------------------+
bool ValidateComplianceInputs()
{
//--- 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);
}
//--- Validate the initial account balance
if(InpInitialAccountBalance <= 0.0)
{
Print("Prop Firm Guard: Initial account balance must be greater than zero.");
return(false);
}
//--- Validate the daily drawdown percentage
if(InpDailyDrawdownPercent <= 0.0 ||
InpDailyDrawdownPercent > 100.0)
{
Print("Prop Firm Guard: Daily drawdown percentage must be between 0 and 100.");
return(false);
}
//--- Validate the overall drawdown percentage
if(InpOverallDrawdownPercent <= 0.0 ||
InpOverallDrawdownPercent > 100.0)
{
Print("Prop Firm Guard: Overall drawdown percentage must be between 0 and 100.");
return(false);
}
//--- Validate the profit target percentage
if(InpProfitTargetPercent <= 0.0 ||
InpProfitTargetPercent > 100.0)
{
Print("Prop Firm Guard: Profit target percentage must be between 0 and 100.");
return(false);
}
//--- Validate the warning threshold
if(InpWarningLevelPercent <= 0.0 ||
InpWarningLevelPercent >= InpCriticalLevelPercent)
{
Print("Prop Firm Guard: Warning level must be greater than 0 and below the critical level.");
return(false);
}
//--- Validate the critical threshold
if(InpCriticalLevelPercent <= InpWarningLevelPercent ||
InpCriticalLevelPercent >= 100.0)
{
Print("Prop Firm Guard: Critical level must be above the warning level and below 100.");
return(false);
}
//--- Validate the news restriction windows
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 current account values
g_accountState.login = AccountInfoInteger(ACCOUNT_LOGIN);
g_accountState.balance = AccountInfoDouble(ACCOUNT_BALANCE);
g_accountState.equity = AccountInfoDouble(ACCOUNT_EQUITY);
//--- Derive the current floating profit or loss
g_accountState.floatingPL = g_accountState.equity - g_accountState.balance;
//--- Initialize compliance status before rule calculations are added
g_accountState.status = COMPLIANCE_SAFE;
}
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+
//| 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));
}
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+
//| Open database |
//+------------------------------------------------------------------+
bool OpenDatabase()
{
//--- Open or create the SQLite database
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()
{
//--- Ignore the request when no database connection is active
if(g_database == INVALID_HANDLE)
return;
//--- Close the active SQLite connection
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.");
}
//--- Clear the stored database handle
g_database = INVALID_HANDLE;
}
//+------------------------------------------------------------------+
//| Ensure database tables exist |
//+------------------------------------------------------------------+
bool EnsureDatabaseTables()
{
//--- 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);
}
//--- 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)"
");";
//--- 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
ResetLastError();
if(!DatabaseExecute(g_database,
settingsTable))
{
Print("Prop Firm Guard: Could not create settings table. Error: ",
GetLastError());
return(false);
}
//--- 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);
}
return(true);
}
//+------------------------------------------------------------------+
//| Finalize database request |
//+------------------------------------------------------------------+
void FinalizeDatabaseRequest(const int request)
{
//--- Ignore invalid database requests
if(request == INVALID_HANDLE)
return;
//--- Release the prepared database request
ResetLastError();
DatabaseFinalize(request);
int errorCode = GetLastError();
if(errorCode != 0)
{
Print("Prop Firm Guard: Could not finalize database request. Error: ",
errorCode);
}
}
//+------------------------------------------------------------------+
//| Execute prepared database statement |
//+------------------------------------------------------------------+
bool ExecuteDatabaseRequest(const int request,
const string operation)
{
//--- Execute the prepared database statement
ResetLastError();
bool result = DatabaseRead(request);
int errorCode = GetLastError();
//--- Accept normal completion when no result row is returned
bool success =
(result || errorCode == ERR_DATABASE_NO_MORE_DATA);
if(!success)
{
Print("Prop Firm Guard: Database error while ",
operation,
". Error: ",
errorCode);
}
//--- Release the request after execution
FinalizeDatabaseRequest(request);
return(success);
}
//+------------------------------------------------------------------+
//| Save account settings |
//+------------------------------------------------------------------+
bool SaveAccountSettings()
{
//--- 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);";
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 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))
{
Print("Prop Firm Guard: Could not bind settings values. Error: ",
GetLastError());
FinalizeDatabaseRequest(request);
return(false);
}
//--- Execute and finalize the prepared write request
if(!ExecuteDatabaseRequest(request, "saving account settings"))
return(false);
return(true);
}
//+------------------------------------------------------------------+
//| Load account settings |
//+------------------------------------------------------------------+
bool LoadAccountSettings()
{
//--- Build the current account/server lookup
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);
}
//--- Bind the current account identity
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);
}
//--- Read the matching settings record
ResetLastError();
if(!DatabaseRead(request))
{
int errorCode = GetLastError();
FinalizeDatabaseRequest(request);
//--- 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);
return(false);
}
//--- Read and validate the stored column values
long accountLogin;
string storedServer;
double initialBalance;
double dailyDrawdown;
double overallDrawdown;
double profitTarget;
long updatedAt;
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);
}
//--- 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;
//--- Release the completed read request
FinalizeDatabaseRequest(request);
return(true);
}
//+------------------------------------------------------------------+
//| Initialize account settings |
//+------------------------------------------------------------------+
bool InitializeAccountSettings()
{
//--- Check whether this account/server pair already has stored settings
bool settingsFound = LoadAccountSettings();
//--- Associate the settings with the current trading account
g_accountSettings.accountLogin = g_accountState.login;
g_accountSettings.accountServer = AccountInfoString(ACCOUNT_SERVER);
//--- 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();
//--- Save the synchronized configuration
if(!SaveAccountSettings())
return(false);
//--- 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.");
return(true);
}
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Validate the complete Part 1 and Part 2 input configuration
if(!ValidateComplianceInputs())
return(INIT_PARAMETERS_INCORRECT);
//--- Refresh the account identity and current financial state
UpdateAccountState();
//--- Enforce one Prop Firm Guard instance for the trading account
if(!AcquireInstanceLock())
return(INIT_FAILED);
//--- Open the SQLite persistence layer
if(!OpenDatabase())
{
Print("Prop Firm Guard: Database initialization failed.");
if(!ReleaseInstanceLock())
Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
return(INIT_FAILED);
}
//--- Ensure all persistence tables required through Part 2 exist
if(!EnsureDatabaseTables())
{
Print("Prop Firm Guard: Could not initialize database tables.");
CloseDatabase();
if(!ReleaseInstanceLock())
Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
return(INIT_FAILED);
}
//--- Restore and synchronize the Part 1 account settings
if(!InitializeAccountSettings())
{
Print("Prop Firm Guard: Could not initialize account settings.");
CloseDatabase();
if(!ReleaseInstanceLock())
Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
return(INIT_FAILED);
}
//--- Restore or create the current configured daily session
if(!InitializeDailySession())
{
Print("Prop Firm Guard: Could not initialize daily session.");
CloseDatabase();
if(!ReleaseInstanceLock())
Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
return(INIT_FAILED);
}
//--- 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.");
return(INIT_FAILED);
}
Print("Prop Firm Guard initialized successfully.");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- 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
CloseDatabase();
//--- Release this trading account's terminal instance lock
if(!ReleaseInstanceLock())
{
Print("Prop Firm Guard: Instance lock cleanup was incomplete.");
}
//--- Record the MetaTrader deinitialization reason
Print("Prop Firm Guard removed. Deinitialization reason: ",
reason);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
//| 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.");
}
}
//+------------------------------------------------------------------+
//| 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();
}
//+------------------------------------------------------------------+