PropFirmGuard/PropFirmGuard.mq5

607 lines
No EOL
22 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
};
//+------------------------------------------------------------------+
//| Inputs |
//+------------------------------------------------------------------+
input group "General"
input int InpTimerSeconds = 1; // Dashboard update interval in seconds
input int InpDashboardX = 10; // Dashboard horizontal position
input int InpDashboardY = 105; // Dashboard vertical position
input bool InpEnableAlerts = true; // Enable terminal alerts
input bool InpEnablePushNotifications = false; // Enable mobile push notifications
input group "Prop Firm Rules"
input double InpInitialAccountBalance = 20000.0; // Initial prop-firm account balance
input double InpDailyDrawdownPercent = 5.0; // Maximum permitted daily drawdown
input double InpOverallDrawdownPercent = 10.0; // Maximum permitted overall drawdown
input double InpProfitTargetPercent = 8.0; // Required profit target
input group "Warning Levels"
input double InpWarningLevelPercent = 70.0; // Loss limit usage that triggers warning
input double InpCriticalLevelPercent = 90.0; // Loss limit usage that triggers critical state
input group "News Filter"
input bool InpEnableNewsFilter = true; // Enable economic news monitoring
input ENUM_CALENDAR_EVENT_IMPORTANCE InpNewsImportance = CALENDAR_IMPORTANCE_HIGH; // Minimum monitored importance
input int InpMinutesBeforeNews = 10; // Restricted minutes before news
input int InpMinutesAfterNews = 10; // Restricted minutes after news
input bool InpAllowNewsHolding = true; // Allow positions to remain open during news
input group "Protection Actions"
input ENUM_PROTECTION_ACTION InpDailyDrawdownAction = PROTECTION_WARN_ONLY; // Daily breach action
input ENUM_PROTECTION_ACTION InpOverallDrawdownAction = PROTECTION_WARN_ONLY; // Overall breach action
input ENUM_PROTECTION_ACTION InpNewsRestrictionAction = PROTECTION_WARN_ONLY; // News restriction action
//+------------------------------------------------------------------+
//| Account state structure |
//+------------------------------------------------------------------+
//| Holds the live account values required by the monitoring logic. |
//+------------------------------------------------------------------+
struct SAccountState
{
long login; // Trading account login number
double balance; // Current account balance
double equity; // Current account equity
double floatingPL; // Current floating profit or loss
ENUM_COMPLIANCE_STATUS status; // Current overall compliance status
};
//+------------------------------------------------------------------+
//| Account settings structure |
//+------------------------------------------------------------------+
//| Stores the main prop-firm rules associated with this account. |
//+------------------------------------------------------------------+
struct SAccountSettings
{
long accountLogin; // Trading account login
string accountServer; // Broker trading server
double initialBalance; // Initial prop-firm account balance
double dailyDrawdownPercent; // Maximum daily drawdown
double overallDrawdownPercent; // Maximum overall drawdown
double profitTargetPercent; // Required profit target
datetime updatedAt; // Last settings update time
};
//+------------------------------------------------------------------+
//| Global variables |
//+------------------------------------------------------------------+
SAccountState g_accountState; // Latest monitored account state
SAccountSettings g_accountSettings; // Persisted prop-firm settings
string g_databaseName = "prop_firm_compliance.sqlite"; // SQLite database file
int g_database = INVALID_HANDLE; // Active SQLite database handle
//+------------------------------------------------------------------+
//| Validate compliance inputs |
//+------------------------------------------------------------------+
bool ValidateComplianceInputs()
{
//--- 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;
}
//+------------------------------------------------------------------+
//| 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()
{
//--- 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)"
");";
//--- Create the settings table when it does not already exist
ResetLastError();
if(!DatabaseExecute(g_database,
settingsTable))
{
Print("Prop Firm Guard: Could not create settings table. Error: ",
GetLastError());
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Finalize database request |
//+------------------------------------------------------------------+
void FinalizeDatabaseRequest(const int request)
{
//--- 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 request |
//+------------------------------------------------------------------+
bool ExecuteDatabaseRequest(const int request,
const string operation)
{
//--- Execute the prepared database statement
ResetLastError();
bool result = DatabaseRead(request);
int errorCode = GetLastError();
//--- Write statements return no result rows after successful execution
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);
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Validate the future monitoring interval
if(InpTimerSeconds < 1)
{
Print("Prop Firm Guard: Timer interval must be at least 1 second.");
return(INIT_PARAMETERS_INCORRECT);
}
//--- Reject invalid compliance rules before initialization continues
if(!ValidateComplianceInputs())
return(INIT_PARAMETERS_INCORRECT);
//--- Collect the current account identity and financial state
UpdateAccountState();
//--- Open the SQLite persistence layer
if(!OpenDatabase())
{
Print("Prop Firm Guard: Database initialization failed.");
return(INIT_FAILED);
}
//--- Ensure the required persistence schema exists
if(!EnsureDatabaseTables())
{
Print("Prop Firm Guard: Could not initialize database tables.");
CloseDatabase();
return(INIT_FAILED);
}
//--- Restore and synchronize the account-specific settings
if(!InitializeAccountSettings())
{
Print("Prop Firm Guard: Could not initialize account settings.");
CloseDatabase();
return(INIT_FAILED);
}
Print("Prop Firm Guard initialized successfully.");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- Close the SQLite connection
CloseDatabase();
//--- Record the MetaTrader deinitialization reason
Print("Prop Firm Guard removed. Deinitialization reason: ", reason);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+