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
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Overall guard status |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
enum ENUM_GUARD_STATUS
|
|
|
|
|
{
|
|
|
|
|
GUARD_SAFE = 0, // All monitored rules are within safe limits
|
|
|
|
|
GUARD_TARGET_REACHED, // Profit target has been achieved
|
|
|
|
|
GUARD_WARNING, // At least one rule is in warning state
|
|
|
|
|
GUARD_CRITICAL, // At least one rule is in critical state
|
|
|
|
|
GUARD_BREACHED // At least one drawdown rule has been breached
|
|
|
|
|
};
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 05:14:11 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| News monitoring status |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
enum ENUM_NEWS_STATUS
|
|
|
|
|
{
|
|
|
|
|
NEWS_DISABLED = 0, // Economic news monitoring is disabled
|
|
|
|
|
NEWS_NO_EXPOSURE, // No supported open positions require news monitoring
|
|
|
|
|
NEWS_NO_EVENT, // No relevant event was found
|
|
|
|
|
NEWS_SAFE, // Relevant event exists but restriction is inactive
|
|
|
|
|
NEWS_RESTRICTED, // Current time is inside the restricted news window
|
|
|
|
|
NEWS_UNAVAILABLE // Calendar data could not be retrieved
|
|
|
|
|
};
|
2026-08-30 05:53:24 +03:00
|
|
|
|
2026-09-07 04:17:47 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| News position match status |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Describes whether an open position is affected by the selected |
|
|
|
|
|
//| Economic Calendar event. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
enum ENUM_NEWS_POSITION_STATUS
|
|
|
|
|
{
|
|
|
|
|
NEWS_POSITION_NOT_AFFECTED = 0, // Position does not use the event currency
|
|
|
|
|
NEWS_POSITION_AFFECTED, // Position is exposed to the event currency
|
|
|
|
|
NEWS_POSITION_UNSUPPORTED // Position is outside current Forex/XAUUSD scope
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| News holding status |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Describes the account's current holding-rule condition around |
|
|
|
|
|
//| the selected Economic Calendar event. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
enum ENUM_NEWS_HOLDING_STATUS
|
|
|
|
|
{
|
|
|
|
|
NEWS_HOLDING_CLEAR = 0, // No active holding restriction
|
|
|
|
|
NEWS_HOLDING_ALLOWED, // Restricted window active but holding is permitted
|
|
|
|
|
NEWS_HOLDING_WARNING, // Event is approaching and affected positions exist
|
|
|
|
|
NEWS_HOLDING_PROTECTION // Restricted window active and protection is required
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-15 02:44:23 -07:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Protection action |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Defines what Prop Firm Guard should do when a monitored rule |
|
|
|
|
|
//| requires protective action. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
enum ENUM_PROTECTION_ACTION
|
|
|
|
|
{
|
2026-08-30 05:53:24 +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-08-28 18:38:10 +03:00
|
|
|
};
|
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
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
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-09-07 04:17:47 +03:00
|
|
|
|
2026-09-01 06:44:36 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Daily session structure |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Represents the persistent state associated with one trading day. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
struct SDailySession
|
|
|
|
|
{
|
2026-09-07 04:17:47 +03:00
|
|
|
long accountLogin; // Trading account login
|
|
|
|
|
string accountServer; // Broker trading server
|
|
|
|
|
string tradingDate; // Trading date represented by this session
|
|
|
|
|
double startingBalance; // Confirmed daily starting balance
|
|
|
|
|
double startingEquity; // Equity captured when the baseline is confirmed
|
|
|
|
|
bool balanceConfirmed; // Whether the starting balance has been confirmed
|
|
|
|
|
double latestBalance; // Most recently recorded account balance
|
|
|
|
|
double latestEquity; // Most recently recorded account equity
|
|
|
|
|
double dailyLoss; // Persisted daily loss value
|
|
|
|
|
double maximumDailyLossReached; // Highest daily loss recorded during the session
|
|
|
|
|
double targetProgress; // Persisted profit-target progress
|
|
|
|
|
string sessionStatus; // Current persisted session status
|
|
|
|
|
datetime createdAt; // Time the session record was created
|
|
|
|
|
datetime updatedAt; // Time the session record was last updated
|
2026-09-01 06:44:36 +03:00
|
|
|
};
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-03 03:00:04 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Daily drawdown structure |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Holds the calculated daily risk values for the active session. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
struct SDailyDrawdownState
|
|
|
|
|
{
|
|
|
|
|
double startingBalance; // Confirmed daily starting balance
|
|
|
|
|
double currentEquity; // Current live account equity
|
|
|
|
|
double maximumDailyLoss; // Maximum money that may be lost today
|
|
|
|
|
double currentDailyLoss; // Current loss measured from starting balance
|
|
|
|
|
double drawdownPercent; // Current loss as percentage of starting balance
|
|
|
|
|
double limitUsagePercent; // Percentage of daily loss allowance already used
|
|
|
|
|
double remainingAllowance; // Money remaining before daily breach
|
|
|
|
|
ENUM_COMPLIANCE_STATUS status; // Current daily drawdown status
|
|
|
|
|
};
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-03 03:00:04 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Overall drawdown structure |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Holds the live calculations for the static overall loss rule. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
struct SOverallDrawdownState
|
|
|
|
|
{
|
|
|
|
|
double initialBalance; // Initial prop-firm account balance
|
|
|
|
|
double currentEquity; // Current live account equity
|
|
|
|
|
double maximumOverallLoss; // Maximum permitted overall loss
|
|
|
|
|
double overallFloor; // Minimum permitted account equity
|
|
|
|
|
double currentOverallLoss; // Current loss from initial balance
|
|
|
|
|
double limitUsagePercent; // Percentage of overall allowance used
|
|
|
|
|
double remainingAllowance; // Money remaining before breach
|
|
|
|
|
ENUM_COMPLIANCE_STATUS status; // Current overall drawdown status
|
|
|
|
|
};
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-03 03:00:04 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Profit target structure |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Holds the live calculations used to track challenge progress. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
struct SProfitTargetState
|
|
|
|
|
{
|
|
|
|
|
double initialBalance; // Initial prop-firm account balance
|
|
|
|
|
double currentBalance; // Current realized account balance
|
|
|
|
|
double targetAmount; // Profit required in account currency
|
|
|
|
|
double targetBalance; // Balance required to complete the target
|
|
|
|
|
double currentProfit; // Realized profit above initial balance
|
|
|
|
|
double progressPercent; // Percentage of target already completed
|
|
|
|
|
double remainingProfit; // Profit still required to reach target
|
|
|
|
|
ENUM_TARGET_STATUS status; // Current target progress state
|
|
|
|
|
};
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 05:14:11 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| News monitoring structure |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
struct SNewsState
|
|
|
|
|
{
|
2026-09-07 04:17:47 +03:00
|
|
|
bool hasExposure; // Whether supported open positions exist
|
|
|
|
|
int monitoredCurrencies; // Number of unique monitored currencies
|
|
|
|
|
string currencyList; // Monitored currency list
|
|
|
|
|
bool eventFound; // Whether a relevant event was located
|
|
|
|
|
string eventCurrency; // Currency affected by selected event
|
|
|
|
|
ulong eventId; // Economic Calendar event identifier
|
|
|
|
|
string eventName; // Economic event name
|
|
|
|
|
ENUM_CALENDAR_EVENT_IMPORTANCE importance; // Event importance
|
|
|
|
|
datetime eventTime; // Event time in trade-server time
|
|
|
|
|
long secondsToEvent; // Signed distance to event
|
|
|
|
|
datetime restrictionStart; // Beginning of restricted news window
|
|
|
|
|
datetime restrictionEnd; // End of restricted news window
|
|
|
|
|
bool restrictedWindow; // Whether restriction is currently active
|
|
|
|
|
int affectedPositions; // Positions affected by selected event
|
|
|
|
|
int protectionPositions; // Affected positions requiring protection
|
|
|
|
|
ENUM_NEWS_STATUS status; // Current news-monitoring status
|
|
|
|
|
ENUM_NEWS_HOLDING_STATUS holdingStatus; // Current news-holding condition
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| News position match structure |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
struct SNewsPositionMatch
|
|
|
|
|
{
|
|
|
|
|
ulong ticket; // Position ticket
|
|
|
|
|
string symbol; // Position symbol
|
|
|
|
|
string currency1; // First relevant currency
|
|
|
|
|
string currency2; // Second relevant currency
|
|
|
|
|
ENUM_NEWS_POSITION_STATUS status; // Event-currency classification
|
|
|
|
|
bool protectionRequired; // Whether protection is required
|
2026-09-04 05:14:11 +03:00
|
|
|
};
|
2026-08-31 18:18:41 +03:00
|
|
|
|
2026-08-30 11:53:44 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Global variables |
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-09-07 04:17:47 +03:00
|
|
|
SAccountState g_accountState; // Latest monitored account state
|
|
|
|
|
SAccountSettings g_accountSettings; // Persisted prop-firm settings
|
|
|
|
|
SDailySession g_dailySession; // Current trading-day record
|
|
|
|
|
SDailyDrawdownState g_dailyDrawdown; // Current daily drawdown calculations
|
|
|
|
|
SOverallDrawdownState g_overallDrawdown; // Current overall drawdown calculations
|
|
|
|
|
SProfitTargetState g_profitTarget; // Current profit-target calculations
|
|
|
|
|
SNewsState g_newsState; // Current economic news state
|
|
|
|
|
SNewsPositionMatch g_newsPositionMatches[]; // Classification of open positions
|
2026-09-02 08:29:37 +03:00
|
|
|
|
2026-09-07 04:17:47 +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-09-02 08:29:37 +03:00
|
|
|
|
2026-09-07 04:17:47 +03:00
|
|
|
int g_database = INVALID_HANDLE; // Active SQLite database handle
|
|
|
|
|
bool g_hasDailySession = false; // Whether a daily session is loaded
|
2026-08-30 11:53:44 +03:00
|
|
|
|
2026-08-30 12:36:36 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Validate compliance inputs |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool ValidateComplianceInputs()
|
|
|
|
|
{
|
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-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-09-07 04:17:47 +03:00
|
|
|
|
2026-09-03 04:04:08 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Calculate daily drawdown |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void CalculateDailyDrawdown()
|
|
|
|
|
{
|
|
|
|
|
//--- Reset the state until the daily starting balance is confirmed
|
|
|
|
|
if(!g_dailySession.balanceConfirmed ||
|
|
|
|
|
g_dailySession.startingBalance <= 0.0)
|
|
|
|
|
{
|
|
|
|
|
g_dailyDrawdown.startingBalance = 0.0;
|
|
|
|
|
g_dailyDrawdown.currentEquity = g_accountState.equity;
|
|
|
|
|
g_dailyDrawdown.maximumDailyLoss = 0.0;
|
|
|
|
|
g_dailyDrawdown.currentDailyLoss = 0.0;
|
|
|
|
|
g_dailyDrawdown.drawdownPercent = 0.0;
|
|
|
|
|
g_dailyDrawdown.limitUsagePercent = 0.0;
|
|
|
|
|
g_dailyDrawdown.remainingAllowance = 0.0;
|
|
|
|
|
g_dailyDrawdown.status = COMPLIANCE_SAFE;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Read the active daily reference and current equity
|
|
|
|
|
g_dailyDrawdown.startingBalance = g_dailySession.startingBalance;
|
|
|
|
|
g_dailyDrawdown.currentEquity = g_accountState.equity;
|
|
|
|
|
|
|
|
|
|
//--- Calculate the permitted daily loss
|
|
|
|
|
g_dailyDrawdown.maximumDailyLoss =
|
|
|
|
|
g_dailyDrawdown.startingBalance *
|
|
|
|
|
g_accountSettings.dailyDrawdownPercent / 100.0;
|
|
|
|
|
|
|
|
|
|
//--- Calculate the current daily loss
|
|
|
|
|
g_dailyDrawdown.currentDailyLoss =
|
|
|
|
|
g_dailyDrawdown.startingBalance -
|
|
|
|
|
g_dailyDrawdown.currentEquity;
|
|
|
|
|
|
|
|
|
|
if(g_dailyDrawdown.currentDailyLoss < 0.0)
|
|
|
|
|
g_dailyDrawdown.currentDailyLoss = 0.0;
|
|
|
|
|
|
|
|
|
|
//--- Calculate the loss percentage
|
|
|
|
|
g_dailyDrawdown.drawdownPercent =
|
|
|
|
|
g_dailyDrawdown.currentDailyLoss /
|
|
|
|
|
g_dailyDrawdown.startingBalance * 100.0;
|
|
|
|
|
|
|
|
|
|
//--- Calculate daily limit usage
|
|
|
|
|
if(g_dailyDrawdown.maximumDailyLoss > 0.0)
|
|
|
|
|
{
|
|
|
|
|
g_dailyDrawdown.limitUsagePercent =
|
|
|
|
|
g_dailyDrawdown.currentDailyLoss /
|
|
|
|
|
g_dailyDrawdown.maximumDailyLoss * 100.0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_dailyDrawdown.limitUsagePercent = 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Calculate the remaining allowance
|
|
|
|
|
g_dailyDrawdown.remainingAllowance =
|
|
|
|
|
g_dailyDrawdown.maximumDailyLoss -
|
|
|
|
|
g_dailyDrawdown.currentDailyLoss;
|
|
|
|
|
|
|
|
|
|
if(g_dailyDrawdown.remainingAllowance < 0.0)
|
|
|
|
|
g_dailyDrawdown.remainingAllowance = 0.0;
|
|
|
|
|
|
|
|
|
|
//--- Classify the current daily drawdown state
|
|
|
|
|
if(g_dailyDrawdown.limitUsagePercent >= 100.0)
|
|
|
|
|
g_dailyDrawdown.status = COMPLIANCE_BREACHED;
|
|
|
|
|
else
|
2026-09-07 04:17:47 +03:00
|
|
|
if(g_dailyDrawdown.limitUsagePercent >= InpCriticalLevelPercent)
|
|
|
|
|
g_dailyDrawdown.status = COMPLIANCE_CRITICAL;
|
|
|
|
|
else
|
|
|
|
|
if(g_dailyDrawdown.limitUsagePercent >= InpWarningLevelPercent)
|
|
|
|
|
g_dailyDrawdown.status = COMPLIANCE_WARNING;
|
|
|
|
|
else
|
|
|
|
|
g_dailyDrawdown.status = COMPLIANCE_SAFE;
|
2026-09-03 04:04:08 +03:00
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-03 04:14:56 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Calculate overall drawdown |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void CalculateOverallDrawdown()
|
|
|
|
|
{
|
|
|
|
|
//--- Read the fixed challenge reference and current equity
|
|
|
|
|
g_overallDrawdown.initialBalance = g_accountSettings.initialBalance;
|
|
|
|
|
g_overallDrawdown.currentEquity = g_accountState.equity;
|
|
|
|
|
|
|
|
|
|
//--- Calculate the maximum permitted overall loss
|
|
|
|
|
g_overallDrawdown.maximumOverallLoss =
|
|
|
|
|
g_overallDrawdown.initialBalance *
|
|
|
|
|
g_accountSettings.overallDrawdownPercent / 100.0;
|
|
|
|
|
|
|
|
|
|
//--- Calculate the static overall equity floor
|
|
|
|
|
g_overallDrawdown.overallFloor =
|
|
|
|
|
g_overallDrawdown.initialBalance -
|
|
|
|
|
g_overallDrawdown.maximumOverallLoss;
|
|
|
|
|
|
|
|
|
|
//--- Calculate the current loss from the initial balance
|
|
|
|
|
g_overallDrawdown.currentOverallLoss =
|
|
|
|
|
g_overallDrawdown.initialBalance -
|
|
|
|
|
g_overallDrawdown.currentEquity;
|
|
|
|
|
|
|
|
|
|
if(g_overallDrawdown.currentOverallLoss < 0.0)
|
|
|
|
|
g_overallDrawdown.currentOverallLoss = 0.0;
|
|
|
|
|
|
|
|
|
|
//--- Calculate overall limit usage
|
|
|
|
|
if(g_overallDrawdown.maximumOverallLoss > 0.0)
|
|
|
|
|
{
|
|
|
|
|
g_overallDrawdown.limitUsagePercent =
|
|
|
|
|
g_overallDrawdown.currentOverallLoss /
|
|
|
|
|
g_overallDrawdown.maximumOverallLoss * 100.0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_overallDrawdown.limitUsagePercent = 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Calculate the remaining overall allowance
|
|
|
|
|
g_overallDrawdown.remainingAllowance =
|
|
|
|
|
g_overallDrawdown.maximumOverallLoss -
|
|
|
|
|
g_overallDrawdown.currentOverallLoss;
|
|
|
|
|
|
|
|
|
|
if(g_overallDrawdown.remainingAllowance < 0.0)
|
|
|
|
|
g_overallDrawdown.remainingAllowance = 0.0;
|
|
|
|
|
|
|
|
|
|
//--- Classify the current overall drawdown state
|
|
|
|
|
if(g_overallDrawdown.limitUsagePercent >= 100.0)
|
|
|
|
|
g_overallDrawdown.status = COMPLIANCE_BREACHED;
|
|
|
|
|
else
|
2026-09-07 04:17:47 +03:00
|
|
|
if(g_overallDrawdown.limitUsagePercent >= InpCriticalLevelPercent)
|
|
|
|
|
g_overallDrawdown.status = COMPLIANCE_CRITICAL;
|
|
|
|
|
else
|
|
|
|
|
if(g_overallDrawdown.limitUsagePercent >= InpWarningLevelPercent)
|
|
|
|
|
g_overallDrawdown.status = COMPLIANCE_WARNING;
|
|
|
|
|
else
|
|
|
|
|
g_overallDrawdown.status = COMPLIANCE_SAFE;
|
2026-09-03 04:14:56 +03:00
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-03 09:12:20 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Calculate profit target |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void CalculateProfitTarget()
|
|
|
|
|
{
|
|
|
|
|
//--- Read the fixed challenge reference and realized balance
|
|
|
|
|
g_profitTarget.initialBalance = g_accountSettings.initialBalance;
|
|
|
|
|
g_profitTarget.currentBalance = g_accountState.balance;
|
|
|
|
|
|
|
|
|
|
//--- Calculate the required profit and target balance
|
|
|
|
|
g_profitTarget.targetAmount =
|
|
|
|
|
g_profitTarget.initialBalance *
|
|
|
|
|
g_accountSettings.profitTargetPercent / 100.0;
|
|
|
|
|
|
|
|
|
|
g_profitTarget.targetBalance =
|
|
|
|
|
g_profitTarget.initialBalance +
|
|
|
|
|
g_profitTarget.targetAmount;
|
|
|
|
|
|
|
|
|
|
//--- Calculate realized profit above the initial balance
|
|
|
|
|
g_profitTarget.currentProfit =
|
|
|
|
|
g_profitTarget.currentBalance -
|
|
|
|
|
g_profitTarget.initialBalance;
|
|
|
|
|
|
|
|
|
|
if(g_profitTarget.currentProfit < 0.0)
|
|
|
|
|
g_profitTarget.currentProfit = 0.0;
|
|
|
|
|
|
|
|
|
|
//--- Calculate target progress
|
|
|
|
|
if(g_profitTarget.targetAmount > 0.0)
|
|
|
|
|
{
|
|
|
|
|
g_profitTarget.progressPercent =
|
|
|
|
|
g_profitTarget.currentProfit /
|
|
|
|
|
g_profitTarget.targetAmount * 100.0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_profitTarget.progressPercent = 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(g_profitTarget.progressPercent > 100.0)
|
|
|
|
|
g_profitTarget.progressPercent = 100.0;
|
|
|
|
|
|
|
|
|
|
//--- Calculate the remaining required profit
|
|
|
|
|
g_profitTarget.remainingProfit =
|
|
|
|
|
g_profitTarget.targetAmount -
|
|
|
|
|
g_profitTarget.currentProfit;
|
|
|
|
|
|
|
|
|
|
if(g_profitTarget.remainingProfit < 0.0)
|
|
|
|
|
g_profitTarget.remainingProfit = 0.0;
|
|
|
|
|
|
|
|
|
|
//--- Classify the current target state
|
|
|
|
|
if(g_profitTarget.currentBalance >= g_profitTarget.targetBalance)
|
|
|
|
|
g_profitTarget.status = TARGET_REACHED;
|
|
|
|
|
else
|
2026-09-07 04:17:47 +03:00
|
|
|
if(g_profitTarget.progressPercent >= 90.0)
|
|
|
|
|
g_profitTarget.status = TARGET_NEARING;
|
|
|
|
|
else
|
|
|
|
|
g_profitTarget.status = TARGET_IN_PROGRESS;
|
2026-09-03 09:12:20 +03:00
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 05:24:37 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Initialize news state |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void InitializeNewsState()
|
|
|
|
|
{
|
|
|
|
|
//--- Clear account-exposure information
|
|
|
|
|
g_newsState.hasExposure = false;
|
|
|
|
|
g_newsState.monitoredCurrencies = 0;
|
|
|
|
|
g_newsState.currencyList = "";
|
|
|
|
|
|
|
|
|
|
//--- Clear the previously selected calendar event
|
|
|
|
|
g_newsState.eventFound = false;
|
|
|
|
|
g_newsState.eventCurrency = "";
|
|
|
|
|
g_newsState.eventId = 0;
|
|
|
|
|
g_newsState.eventName = "";
|
|
|
|
|
g_newsState.importance = CALENDAR_IMPORTANCE_NONE;
|
|
|
|
|
g_newsState.eventTime = 0;
|
|
|
|
|
|
|
|
|
|
//--- Reset event timing and restriction-window state
|
|
|
|
|
g_newsState.secondsToEvent = 0;
|
|
|
|
|
g_newsState.restrictionStart = 0;
|
|
|
|
|
g_newsState.restrictionEnd = 0;
|
|
|
|
|
g_newsState.restrictedWindow = false;
|
|
|
|
|
|
|
|
|
|
//--- Set the initial monitoring condition
|
|
|
|
|
g_newsState.status =
|
|
|
|
|
InpEnableNewsFilter ? NEWS_NO_EXPOSURE : NEWS_DISABLED;
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 06:11:32 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Check whether currency already exists in array |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CurrencyExists(const string ¤cies[],
|
|
|
|
|
const string currency)
|
|
|
|
|
{
|
|
|
|
|
int count = ArraySize(currencies);
|
|
|
|
|
|
|
|
|
|
//--- Search the current currency set
|
|
|
|
|
for(int i = 0; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
if(currencies[i] == currency)
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 06:11:32 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Add unique currency to array |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool AddUniqueCurrency(string ¤cies[],
|
|
|
|
|
const string currency)
|
|
|
|
|
{
|
|
|
|
|
//--- Ignore an empty currency value
|
|
|
|
|
if(currency == "")
|
|
|
|
|
return(true);
|
|
|
|
|
|
|
|
|
|
//--- Keep each monitored currency only once
|
|
|
|
|
if(CurrencyExists(currencies, currency))
|
|
|
|
|
return(true);
|
|
|
|
|
|
|
|
|
|
int currentSize = ArraySize(currencies);
|
|
|
|
|
|
|
|
|
|
//--- Extend the currency array
|
|
|
|
|
if(ArrayResize(currencies, currentSize + 1) != currentSize + 1)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not expand monitored currency array.");
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currencies[currentSize] = currency;
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 06:29:56 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Get symbol currencies relevant to news |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool GetSymbolNewsCurrencies(const string symbol,
|
|
|
|
|
string ¤cy1,
|
|
|
|
|
string ¤cy2)
|
|
|
|
|
{
|
|
|
|
|
string baseCurrency;
|
|
|
|
|
string profitCurrency;
|
|
|
|
|
long calculationMode = 0;
|
|
|
|
|
|
|
|
|
|
currency1 = "";
|
|
|
|
|
currency2 = "";
|
|
|
|
|
|
|
|
|
|
//--- Read the currencies defined by the broker for this symbol
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
if(!SymbolInfoString(symbol,
|
|
|
|
|
SYMBOL_CURRENCY_BASE,
|
|
|
|
|
baseCurrency))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not read base currency for ",
|
|
|
|
|
symbol,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
if(!SymbolInfoString(symbol,
|
|
|
|
|
SYMBOL_CURRENCY_PROFIT,
|
|
|
|
|
profitCurrency))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not read profit currency for ",
|
|
|
|
|
symbol,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Treat XAUUSD as USD calendar exposure
|
|
|
|
|
if(baseCurrency == "XAU" &&
|
|
|
|
|
profitCurrency == "USD")
|
|
|
|
|
{
|
|
|
|
|
currency1 = "USD";
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Read the symbol's trading calculation model
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
if(!SymbolInfoInteger(symbol,
|
|
|
|
|
SYMBOL_TRADE_CALC_MODE,
|
|
|
|
|
calculationMode))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not read calculation mode for ",
|
|
|
|
|
symbol,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Ignore instruments outside the supported Forex calculation modes
|
|
|
|
|
if(calculationMode != SYMBOL_CALC_MODE_FOREX &&
|
|
|
|
|
calculationMode != SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE)
|
|
|
|
|
return(true);
|
|
|
|
|
|
|
|
|
|
currency1 = baseCurrency;
|
|
|
|
|
currency2 = profitCurrency;
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Match open positions to selected news event |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void MatchOpenPositionsToNews()
|
|
|
|
|
{
|
|
|
|
|
//--- Remove classifications from the previous monitoring cycle
|
|
|
|
|
ArrayResize(g_newsPositionMatches, 0);
|
|
|
|
|
|
|
|
|
|
//--- Position matching requires an actual selected calendar event
|
|
|
|
|
if(!g_newsState.eventFound ||
|
|
|
|
|
g_newsState.eventCurrency == "")
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
int totalPositions = PositionsTotal();
|
|
|
|
|
|
|
|
|
|
//--- Classify every currently open account position
|
|
|
|
|
for(int i = 0; i < totalPositions; i++)
|
|
|
|
|
{
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
ulong ticket = PositionGetTicket(i);
|
|
|
|
|
|
|
|
|
|
if(ticket == 0)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not retrieve position ticket at index ",
|
|
|
|
|
i,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string symbol;
|
|
|
|
|
|
|
|
|
|
if(!PositionGetString(POSITION_SYMBOL,
|
|
|
|
|
symbol))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not retrieve symbol for position ",
|
|
|
|
|
ticket,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string currency1;
|
|
|
|
|
string currency2;
|
|
|
|
|
|
|
|
|
|
if(!GetSymbolNewsCurrencies(symbol,
|
|
|
|
|
currency1,
|
|
|
|
|
currency2))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
SNewsPositionMatch match;
|
|
|
|
|
|
|
|
|
|
match.ticket = ticket;
|
|
|
|
|
match.symbol = symbol;
|
|
|
|
|
match.currency1 = currency1;
|
|
|
|
|
match.currency2 = currency2;
|
|
|
|
|
match.status = NEWS_POSITION_NOT_AFFECTED;
|
|
|
|
|
match.protectionRequired = false;
|
|
|
|
|
|
|
|
|
|
//--- Classify the position against the selected event currency
|
|
|
|
|
if(currency1 == "" &&
|
|
|
|
|
currency2 == "")
|
|
|
|
|
{
|
|
|
|
|
match.status = NEWS_POSITION_UNSUPPORTED;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
if(currency1 == g_newsState.eventCurrency ||
|
|
|
|
|
currency2 == g_newsState.eventCurrency)
|
|
|
|
|
{
|
|
|
|
|
match.status = NEWS_POSITION_AFFECTED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int currentSize = ArraySize(g_newsPositionMatches);
|
|
|
|
|
|
|
|
|
|
//--- Store the ticket-level classification
|
|
|
|
|
if(ArrayResize(g_newsPositionMatches,
|
|
|
|
|
currentSize + 1) != currentSize + 1)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not expand news position match array.");
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_newsPositionMatches[currentSize] = match;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-07 05:34:09 +03:00
|
|
|
|
2026-09-07 05:05:32 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Evaluate news holding rules |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void EvaluateNewsHoldingRules()
|
|
|
|
|
{
|
|
|
|
|
//--- Reset the account-level holding result
|
|
|
|
|
g_newsState.affectedPositions = 0;
|
|
|
|
|
g_newsState.protectionPositions = 0;
|
|
|
|
|
g_newsState.holdingStatus = NEWS_HOLDING_CLEAR;
|
|
|
|
|
|
|
|
|
|
int totalMatches = ArraySize(g_newsPositionMatches);
|
|
|
|
|
|
|
|
|
|
//--- Stop when no selected event or classified positions exist
|
|
|
|
|
if(!g_newsState.eventFound ||
|
|
|
|
|
totalMatches == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
//--- Count positions affected by the selected event
|
|
|
|
|
for(int i = 0; i < totalMatches; i++)
|
|
|
|
|
{
|
|
|
|
|
g_newsPositionMatches[i].protectionRequired = false;
|
|
|
|
|
|
|
|
|
|
if(g_newsPositionMatches[i].status == NEWS_POSITION_AFFECTED)
|
|
|
|
|
g_newsState.affectedPositions++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- No affected positions means no holding restriction applies
|
|
|
|
|
if(g_newsState.affectedPositions == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
//--- Respect configurations that permit positions to remain open
|
|
|
|
|
if(InpAllowNewsHolding)
|
|
|
|
|
{
|
|
|
|
|
if(g_newsState.restrictedWindow)
|
|
|
|
|
g_newsState.holdingStatus = NEWS_HOLDING_ALLOWED;
|
|
|
|
|
else
|
|
|
|
|
g_newsState.holdingStatus = NEWS_HOLDING_WARNING;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Warn while affected positions approach the restricted window
|
|
|
|
|
if(!g_newsState.restrictedWindow)
|
|
|
|
|
{
|
|
|
|
|
g_newsState.holdingStatus = NEWS_HOLDING_WARNING;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Mark affected positions when holding is prohibited in the window
|
|
|
|
|
for(int i = 0; i < totalMatches; i++)
|
|
|
|
|
{
|
|
|
|
|
if(g_newsPositionMatches[i].status != NEWS_POSITION_AFFECTED)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
g_newsPositionMatches[i].protectionRequired = true;
|
|
|
|
|
g_newsState.protectionPositions++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_newsState.holdingStatus = NEWS_HOLDING_PROTECTION;
|
|
|
|
|
}
|
2026-09-07 05:34:09 +03:00
|
|
|
|
2026-09-07 05:05:32 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Update news position matches |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void UpdateNewsPositionMatches()
|
|
|
|
|
{
|
|
|
|
|
//--- Classify all currently open positions against the selected event
|
|
|
|
|
MatchOpenPositionsToNews();
|
|
|
|
|
|
|
|
|
|
//--- Apply the configured holding rule to those classifications
|
|
|
|
|
EvaluateNewsHoldingRules();
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 06:50:45 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Add currencies exposed by position symbol |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool AddPositionCurrencies(const string symbol,
|
|
|
|
|
string ¤cies[])
|
|
|
|
|
{
|
|
|
|
|
string currency1;
|
|
|
|
|
string currency2;
|
|
|
|
|
|
|
|
|
|
//--- Determine the supported news currencies for the position symbol
|
|
|
|
|
if(!GetSymbolNewsCurrencies(symbol,
|
|
|
|
|
currency1,
|
|
|
|
|
currency2))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
//--- Ignore instruments outside the supported monitoring scope
|
|
|
|
|
if(currency1 == "" &&
|
|
|
|
|
currency2 == "")
|
|
|
|
|
return(true);
|
|
|
|
|
|
|
|
|
|
//--- Add the exposed currencies without creating duplicates
|
|
|
|
|
if(!AddUniqueCurrency(currencies, currency1))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
if(!AddUniqueCurrency(currencies, currency2))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 06:50:45 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Collect currencies from active positions |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CollectExposureCurrencies(string ¤cies[])
|
|
|
|
|
{
|
|
|
|
|
//--- Start each collection with an empty exposure set
|
|
|
|
|
ArrayResize(currencies, 0);
|
|
|
|
|
|
|
|
|
|
int totalPositions = PositionsTotal();
|
|
|
|
|
|
|
|
|
|
//--- No open positions means there is currently no news exposure
|
|
|
|
|
if(totalPositions == 0)
|
|
|
|
|
return(true);
|
|
|
|
|
|
|
|
|
|
//--- Inspect every currently open account position
|
|
|
|
|
for(int i = 0; i < totalPositions; i++)
|
|
|
|
|
{
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
string symbol = PositionGetSymbol(i);
|
|
|
|
|
|
|
|
|
|
if(symbol == "")
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not retrieve position symbol at index ",
|
|
|
|
|
i,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!AddPositionCurrencies(symbol, currencies))
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 08:18:26 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Build monitored currency list |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string BuildCurrencyList(const string ¤cies[])
|
|
|
|
|
{
|
|
|
|
|
string result = "";
|
|
|
|
|
int count = ArraySize(currencies);
|
|
|
|
|
|
|
|
|
|
//--- Append each currency using a comma-separated format
|
|
|
|
|
for(int i = 0; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
if(i > 0)
|
|
|
|
|
result += ", ";
|
|
|
|
|
|
|
|
|
|
result += currencies[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return(result);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 08:38:56 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Find next news event for currency |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool FindNextNewsForCurrency(const string currency,
|
|
|
|
|
const datetime timeFrom,
|
|
|
|
|
const datetime timeTo,
|
|
|
|
|
MqlCalendarValue &bestValue,
|
|
|
|
|
MqlCalendarEvent &bestEvent)
|
|
|
|
|
{
|
|
|
|
|
MqlCalendarValue values[];
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
//--- Request calendar values for the specified currency and time range
|
|
|
|
|
int valueCount = CalendarValueHistory(values,
|
|
|
|
|
timeFrom,
|
|
|
|
|
timeTo,
|
|
|
|
|
NULL,
|
|
|
|
|
currency);
|
|
|
|
|
|
|
|
|
|
if(valueCount < 0)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not retrieve calendar values for ",
|
|
|
|
|
currency,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool eventFound = false;
|
|
|
|
|
datetime earliestTime = 0;
|
|
|
|
|
|
|
|
|
|
//--- Inspect the calendar values returned for this currency
|
|
|
|
|
for(int i = 0; i < valueCount; i++)
|
|
|
|
|
{
|
|
|
|
|
MqlCalendarEvent event;
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
if(!CalendarEventById(values[i].event_id,
|
|
|
|
|
event))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not retrieve calendar event ",
|
|
|
|
|
values[i].event_id,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Reject events below the configured minimum importance
|
|
|
|
|
if(event.importance < InpNewsImportance)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
//--- Require a calendar event with an exact release time
|
|
|
|
|
if(event.time_mode != CALENDAR_TIMEMODE_DATETIME)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
//--- Keep the earliest qualifying event in the search interval
|
|
|
|
|
if(!eventFound ||
|
|
|
|
|
values[i].time < earliestTime)
|
|
|
|
|
{
|
|
|
|
|
bestValue = values[i];
|
|
|
|
|
bestEvent = event;
|
|
|
|
|
earliestTime = values[i].time;
|
|
|
|
|
eventFound = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return(eventFound);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-04 09:35:25 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Update news state |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void UpdateNewsState()
|
|
|
|
|
{
|
|
|
|
|
//--- Clear the previous exposure and event state
|
|
|
|
|
InitializeNewsState();
|
|
|
|
|
|
|
|
|
|
//--- Stop immediately when economic-news monitoring is disabled
|
|
|
|
|
if(!InpEnableNewsFilter)
|
|
|
|
|
{
|
|
|
|
|
g_newsState.status = NEWS_DISABLED;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string currencies[];
|
|
|
|
|
|
|
|
|
|
//--- Collect one unique currency set from all supported open positions
|
|
|
|
|
if(!CollectExposureCurrencies(currencies))
|
|
|
|
|
{
|
|
|
|
|
g_newsState.status = NEWS_UNAVAILABLE;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int currencyCount = ArraySize(currencies);
|
|
|
|
|
|
|
|
|
|
//--- No supported exposure means there is nothing to search
|
|
|
|
|
if(currencyCount == 0)
|
|
|
|
|
{
|
|
|
|
|
g_newsState.hasExposure = false;
|
|
|
|
|
g_newsState.monitoredCurrencies = 0;
|
|
|
|
|
g_newsState.currencyList = "";
|
|
|
|
|
g_newsState.status = NEWS_NO_EXPOSURE;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Store the current account-exposure summary
|
|
|
|
|
g_newsState.hasExposure = true;
|
|
|
|
|
g_newsState.monitoredCurrencies = currencyCount;
|
|
|
|
|
g_newsState.currencyList = BuildCurrencyList(currencies);
|
|
|
|
|
|
|
|
|
|
//--- Use trade-server time for calendar and restriction calculations
|
|
|
|
|
datetime currentTime = TimeTradeServer();
|
|
|
|
|
|
|
|
|
|
datetime searchFrom =
|
|
|
|
|
currentTime -
|
|
|
|
|
InpMinutesAfterNews * 60;
|
|
|
|
|
|
|
|
|
|
datetime searchTo =
|
|
|
|
|
currentTime +
|
|
|
|
|
24 * 60 * 60;
|
|
|
|
|
|
|
|
|
|
bool foundAny = false;
|
|
|
|
|
|
|
|
|
|
ulong selectedEventId = 0;
|
|
|
|
|
string selectedEventName = "";
|
|
|
|
|
string selectedCurrency = "";
|
|
|
|
|
ENUM_CALENDAR_EVENT_IMPORTANCE selectedImportance =
|
|
|
|
|
CALENDAR_IMPORTANCE_NONE;
|
|
|
|
|
datetime selectedEventTime = 0;
|
|
|
|
|
|
|
|
|
|
//--- Search each exposed currency and retain the nearest event
|
|
|
|
|
for(int i = 0; i < currencyCount; i++)
|
|
|
|
|
{
|
|
|
|
|
MqlCalendarValue candidateValue;
|
|
|
|
|
MqlCalendarEvent candidateEvent;
|
|
|
|
|
|
|
|
|
|
if(!FindNextNewsForCurrency(currencies[i],
|
|
|
|
|
searchFrom,
|
|
|
|
|
searchTo,
|
|
|
|
|
candidateValue,
|
|
|
|
|
candidateEvent))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if(!foundAny ||
|
|
|
|
|
candidateValue.time < selectedEventTime)
|
|
|
|
|
{
|
|
|
|
|
selectedEventId = candidateEvent.id;
|
|
|
|
|
selectedEventName = candidateEvent.name;
|
|
|
|
|
selectedCurrency = currencies[i];
|
|
|
|
|
selectedImportance = candidateEvent.importance;
|
|
|
|
|
selectedEventTime = candidateValue.time;
|
|
|
|
|
foundAny = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Report an exposed account with no qualifying event
|
|
|
|
|
if(!foundAny)
|
|
|
|
|
{
|
|
|
|
|
g_newsState.status = NEWS_NO_EVENT;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Store the nearest qualifying event across all exposed currencies
|
|
|
|
|
g_newsState.eventFound = true;
|
|
|
|
|
g_newsState.eventCurrency = selectedCurrency;
|
|
|
|
|
g_newsState.eventId = selectedEventId;
|
|
|
|
|
g_newsState.eventName = selectedEventName;
|
|
|
|
|
g_newsState.importance = selectedImportance;
|
|
|
|
|
g_newsState.eventTime = selectedEventTime;
|
|
|
|
|
|
|
|
|
|
//--- Calculate the signed distance from the current server time
|
|
|
|
|
g_newsState.secondsToEvent =
|
|
|
|
|
(long)(g_newsState.eventTime - currentTime);
|
|
|
|
|
|
|
|
|
|
//--- Build the configured restriction interval
|
|
|
|
|
g_newsState.restrictionStart =
|
|
|
|
|
g_newsState.eventTime -
|
|
|
|
|
InpMinutesBeforeNews * 60;
|
|
|
|
|
|
|
|
|
|
g_newsState.restrictionEnd =
|
|
|
|
|
g_newsState.eventTime +
|
|
|
|
|
InpMinutesAfterNews * 60;
|
|
|
|
|
|
|
|
|
|
//--- Determine whether the current time is inside that interval
|
|
|
|
|
g_newsState.restrictedWindow =
|
|
|
|
|
(currentTime >= g_newsState.restrictionStart &&
|
|
|
|
|
currentTime <= g_newsState.restrictionEnd);
|
|
|
|
|
|
|
|
|
|
if(g_newsState.restrictedWindow)
|
|
|
|
|
g_newsState.status = NEWS_RESTRICTED;
|
|
|
|
|
else
|
|
|
|
|
g_newsState.status = NEWS_SAFE;
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-05 16:56:39 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Format news countdown |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string FormatNewsCountdown(const long secondsToEvent)
|
|
|
|
|
{
|
|
|
|
|
long absoluteSeconds = secondsToEvent;
|
|
|
|
|
|
|
|
|
|
//--- Convert the signed value into a duration for formatting
|
|
|
|
|
if(absoluteSeconds < 0)
|
|
|
|
|
absoluteSeconds = -absoluteSeconds;
|
|
|
|
|
|
|
|
|
|
int hours = (int)(absoluteSeconds / 3600);
|
|
|
|
|
int minutes = (int)((absoluteSeconds % 3600) / 60);
|
|
|
|
|
int seconds = (int)(absoluteSeconds % 60);
|
|
|
|
|
|
|
|
|
|
string formatted =
|
|
|
|
|
StringFormat("%02d:%02d:%02d",
|
|
|
|
|
hours,
|
|
|
|
|
minutes,
|
|
|
|
|
seconds);
|
|
|
|
|
|
|
|
|
|
//--- Prefix elapsed post-event time with a plus sign
|
|
|
|
|
if(secondsToEvent >= 0)
|
|
|
|
|
return(formatted);
|
|
|
|
|
|
|
|
|
|
return("+" + formatted);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-01 07:11:00 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Get trading date |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string GetTradingDate()
|
|
|
|
|
{
|
|
|
|
|
//--- Build the session date from trading-server time
|
|
|
|
|
datetime serverTime = TimeCurrent();
|
|
|
|
|
|
|
|
|
|
return(TimeToString(serverTime, TIME_DATE));
|
|
|
|
|
}
|
2026-08-31 18:18:41 +03:00
|
|
|
|
2026-08-30 13:06:01 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Open database |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool OpenDatabase()
|
|
|
|
|
{
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Open or create the SQLite database
|
2026-08-30 13:06:01 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
g_database = DatabaseOpen(g_databaseName,
|
|
|
|
|
DATABASE_OPEN_READWRITE |
|
|
|
|
|
DATABASE_OPEN_CREATE);
|
|
|
|
|
|
|
|
|
|
if(g_database == INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not open database ",
|
|
|
|
|
g_databaseName,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Print("Prop Firm Guard: Database opened successfully: ",
|
|
|
|
|
g_databaseName);
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-08-31 18:18:41 +03:00
|
|
|
|
2026-08-30 13:06:01 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Close database |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void CloseDatabase()
|
|
|
|
|
{
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Ignore the request when no database connection is active
|
2026-08-30 13:06:01 +03:00
|
|
|
if(g_database == INVALID_HANDLE)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Close the active SQLite connection
|
2026-08-30 13:06:01 +03:00
|
|
|
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.");
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Clear the stored database handle
|
2026-08-30 13:06:01 +03:00
|
|
|
g_database = INVALID_HANDLE;
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-01 07:36:24 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Check whether database column exists |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool DatabaseColumnExists(const string tableName,
|
|
|
|
|
const string columnName)
|
|
|
|
|
{
|
|
|
|
|
//--- Inspect the current table schema
|
|
|
|
|
string sql = "PRAGMA table_info(" + tableName + ");";
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
int request = DatabasePrepare(g_database,
|
|
|
|
|
sql);
|
|
|
|
|
|
|
|
|
|
if(request == INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not inspect table ",
|
|
|
|
|
tableName,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool columnFound = false;
|
|
|
|
|
|
|
|
|
|
//--- Search the returned schema rows for the requested column
|
|
|
|
|
while(true)
|
|
|
|
|
{
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
if(!DatabaseRead(request))
|
|
|
|
|
{
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
if(errorCode != ERR_DATABASE_NO_MORE_DATA)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not read schema for ",
|
|
|
|
|
tableName,
|
|
|
|
|
". Error: ",
|
|
|
|
|
errorCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string currentColumn;
|
|
|
|
|
|
|
|
|
|
//--- PRAGMA table_info returns the column name at index 1
|
|
|
|
|
if(!DatabaseColumnText(request,
|
|
|
|
|
1,
|
|
|
|
|
currentColumn))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not read database column name. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(currentColumn == columnName)
|
|
|
|
|
{
|
|
|
|
|
columnFound = true;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Release the schema request
|
|
|
|
|
FinalizeDatabaseRequest(request);
|
|
|
|
|
|
|
|
|
|
return(columnFound);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Ensure database column exists |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool EnsureDatabaseColumn(const string tableName,
|
|
|
|
|
const string columnName,
|
|
|
|
|
const string columnDefinition)
|
|
|
|
|
{
|
|
|
|
|
//--- Leave an existing column unchanged
|
|
|
|
|
if(DatabaseColumnExists(tableName,
|
|
|
|
|
columnName))
|
|
|
|
|
return(true);
|
|
|
|
|
|
|
|
|
|
//--- Add the missing column
|
|
|
|
|
string sql =
|
|
|
|
|
"ALTER TABLE " +
|
|
|
|
|
tableName +
|
|
|
|
|
" ADD COLUMN " +
|
|
|
|
|
columnName +
|
|
|
|
|
" " +
|
|
|
|
|
columnDefinition +
|
|
|
|
|
";";
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
if(!DatabaseExecute(g_database,
|
|
|
|
|
sql))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not add column ",
|
|
|
|
|
columnName,
|
|
|
|
|
" to ",
|
|
|
|
|
tableName,
|
|
|
|
|
". Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Print("Prop Firm Guard: Added database column ",
|
|
|
|
|
tableName,
|
|
|
|
|
".",
|
|
|
|
|
columnName,
|
|
|
|
|
".");
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Ensure runtime daily-session columns exist |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool EnsureDailySessionRuntimeColumns()
|
|
|
|
|
{
|
|
|
|
|
//--- Add runtime persistence columns when missing
|
|
|
|
|
if(!EnsureDatabaseColumn("daily_sessions",
|
|
|
|
|
"latest_balance",
|
|
|
|
|
"REAL NOT NULL DEFAULT 0"))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
if(!EnsureDatabaseColumn("daily_sessions",
|
|
|
|
|
"latest_equity",
|
|
|
|
|
"REAL NOT NULL DEFAULT 0"))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
if(!EnsureDatabaseColumn("daily_sessions",
|
|
|
|
|
"daily_loss",
|
|
|
|
|
"REAL NOT NULL DEFAULT 0"))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
if(!EnsureDatabaseColumn("daily_sessions",
|
|
|
|
|
"maximum_daily_loss_reached",
|
|
|
|
|
"REAL NOT NULL DEFAULT 0"))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
if(!EnsureDatabaseColumn("daily_sessions",
|
|
|
|
|
"target_progress",
|
|
|
|
|
"REAL NOT NULL DEFAULT 0"))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
if(!EnsureDatabaseColumn("daily_sessions",
|
|
|
|
|
"session_status",
|
|
|
|
|
"TEXT NOT NULL DEFAULT 'AWAITING_CONFIRMATION'"))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-08-31 18:18:41 +03:00
|
|
|
|
2026-08-30 14:24:21 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Ensure database tables exist |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool EnsureDatabaseTables()
|
|
|
|
|
{
|
2026-09-01 07:36:24 +03:00
|
|
|
//--- Define account-level settings storage
|
2026-08-30 14:24:21 +03:00
|
|
|
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-01 07:36:24 +03:00
|
|
|
//--- Define persistent trading-day storage
|
|
|
|
|
string dailySessionsTable =
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS daily_sessions ("
|
|
|
|
|
"account_login INTEGER NOT NULL,"
|
|
|
|
|
"account_server TEXT NOT NULL,"
|
|
|
|
|
"trading_date TEXT NOT NULL,"
|
|
|
|
|
"starting_balance REAL NOT NULL,"
|
|
|
|
|
"starting_equity REAL NOT NULL,"
|
|
|
|
|
"balance_confirmed INTEGER NOT NULL,"
|
|
|
|
|
"latest_balance REAL NOT NULL DEFAULT 0,"
|
|
|
|
|
"latest_equity REAL NOT NULL DEFAULT 0,"
|
|
|
|
|
"daily_loss REAL NOT NULL DEFAULT 0,"
|
|
|
|
|
"maximum_daily_loss_reached REAL NOT NULL DEFAULT 0,"
|
|
|
|
|
"target_progress REAL NOT NULL DEFAULT 0,"
|
|
|
|
|
"session_status TEXT NOT NULL DEFAULT 'AWAITING_CONFIRMATION',"
|
|
|
|
|
"created_at INTEGER NOT NULL,"
|
|
|
|
|
"updated_at INTEGER NOT NULL,"
|
|
|
|
|
"PRIMARY KEY(account_login, account_server, trading_date)"
|
|
|
|
|
");";
|
|
|
|
|
|
|
|
|
|
//--- Ensure the account settings table exists
|
2026-08-30 14:24:21 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
if(!DatabaseExecute(g_database,
|
|
|
|
|
settingsTable))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not create settings table. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-01 07:36:24 +03:00
|
|
|
//--- Ensure the daily-session table exists
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
if(!DatabaseExecute(g_database,
|
|
|
|
|
dailySessionsTable))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not create daily_sessions table. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Extend an existing daily-session table when required
|
|
|
|
|
if(!EnsureDailySessionRuntimeColumns())
|
|
|
|
|
return(false);
|
|
|
|
|
|
2026-08-30 14:24:21 +03:00
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-02 07:54:42 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Check for new trading day |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void CheckForNewTradingDay()
|
|
|
|
|
{
|
|
|
|
|
//--- Read the current server-based trading date
|
|
|
|
|
string currentDate = GetTradingDate();
|
|
|
|
|
|
|
|
|
|
//--- Keep the active session when it already belongs to today
|
|
|
|
|
if(g_hasDailySession &&
|
|
|
|
|
g_dailySession.tradingDate == currentDate)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Print("Prop Firm Guard: New trading day detected: ",
|
|
|
|
|
currentDate);
|
|
|
|
|
|
2026-09-02 08:29:37 +03:00
|
|
|
//--- Restore or create the session for the new trading date
|
2026-09-02 07:54:42 +03:00
|
|
|
if(!InitializeDailySession())
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not initialize the new daily session.");
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-09-02 08:29:37 +03:00
|
|
|
|
|
|
|
|
//--- Rebuild the confirmation controls for the newly loaded session
|
|
|
|
|
DeleteDailySessionControls();
|
|
|
|
|
|
|
|
|
|
if(!CreateDailySessionControls())
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not create daily session controls.");
|
|
|
|
|
}
|
2026-09-02 07:54:42 +03:00
|
|
|
}
|
2026-08-31 18:18:41 +03:00
|
|
|
|
2026-08-30 14:24:21 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Finalize database request |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void FinalizeDatabaseRequest(const int request)
|
|
|
|
|
{
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Ignore invalid database requests
|
2026-08-30 14:24:21 +03:00
|
|
|
if(request == INVALID_HANDLE)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Release the prepared database request
|
2026-08-30 14:24:21 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
DatabaseFinalize(request);
|
|
|
|
|
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
if(errorCode != 0)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not finalize database request. Error: ",
|
|
|
|
|
errorCode);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-31 18:18:41 +03:00
|
|
|
|
2026-08-30 14:24:21 +03:00
|
|
|
//+------------------------------------------------------------------+
|
2026-09-03 03:00:04 +03:00
|
|
|
//| Execute prepared database statement |
|
2026-08-30 14:24:21 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool ExecuteDatabaseRequest(const int request,
|
|
|
|
|
const string operation)
|
|
|
|
|
{
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Execute the prepared database statement
|
2026-08-30 14:24:21 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
bool result = DatabaseRead(request);
|
|
|
|
|
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
2026-09-03 03:00:04 +03:00
|
|
|
//--- Accept normal completion when no result row is returned
|
2026-08-30 14:24:21 +03:00
|
|
|
bool success =
|
|
|
|
|
(result || errorCode == ERR_DATABASE_NO_MORE_DATA);
|
|
|
|
|
|
|
|
|
|
if(!success)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Database error while ",
|
|
|
|
|
operation,
|
|
|
|
|
". Error: ",
|
|
|
|
|
errorCode);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Release the request after execution
|
2026-08-30 14:24:21 +03:00
|
|
|
FinalizeDatabaseRequest(request);
|
|
|
|
|
|
|
|
|
|
return(success);
|
|
|
|
|
}
|
2026-08-31 18:18:41 +03:00
|
|
|
|
2026-08-30 19:24:48 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Save account settings |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool SaveAccountSettings()
|
|
|
|
|
{
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Prepare the account-settings write statement
|
2026-08-30 19:24:48 +03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Bind the current account settings to the prepared statement
|
2026-08-30 19:24:48 +03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Execute and finalize the prepared write request
|
2026-08-30 19:24:48 +03:00
|
|
|
if(!ExecuteDatabaseRequest(request, "saving account settings"))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-08-31 18:18:41 +03:00
|
|
|
|
2026-08-30 19:24:48 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Load account settings |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool LoadAccountSettings()
|
|
|
|
|
{
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Build the current account/server lookup
|
2026-08-30 19:24:48 +03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Bind the current account identity
|
2026-08-30 19:24:48 +03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Read the matching settings record
|
2026-08-30 19:24:48 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
if(!DatabaseRead(request))
|
|
|
|
|
{
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
FinalizeDatabaseRequest(request);
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- No row means this account/server pair has not been saved yet
|
2026-08-30 19:24:48 +03:00
|
|
|
if(errorCode == ERR_DATABASE_NO_MORE_DATA)
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
Print("Prop Firm Guard: Could not read account settings. Error: ",
|
|
|
|
|
errorCode);
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Read and validate the stored column values
|
2026-08-30 19:24:48 +03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Restore the stored values into the account settings structure
|
2026-08-30 19:24:48 +03:00
|
|
|
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-08-31 19:38:11 +03:00
|
|
|
//--- Release the completed read request
|
2026-08-30 19:24:48 +03:00
|
|
|
FinalizeDatabaseRequest(request);
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-02 07:13:30 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Initialize daily session |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool InitializeDailySession()
|
|
|
|
|
{
|
|
|
|
|
//--- Identify the current trading day
|
|
|
|
|
string tradingDate = GetTradingDate();
|
|
|
|
|
|
|
|
|
|
//--- Restore today's session when it already exists
|
|
|
|
|
if(DailySessionExists(tradingDate))
|
|
|
|
|
{
|
|
|
|
|
if(!LoadDailySession())
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
g_hasDailySession = true;
|
|
|
|
|
|
|
|
|
|
Print("Prop Firm Guard: Daily session restored for ",
|
|
|
|
|
tradingDate,
|
|
|
|
|
".");
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Create a new unconfirmed session for the current trading day
|
|
|
|
|
g_dailySession.accountLogin = g_accountState.login;
|
|
|
|
|
g_dailySession.accountServer = AccountInfoString(ACCOUNT_SERVER);
|
|
|
|
|
g_dailySession.tradingDate = tradingDate;
|
|
|
|
|
g_dailySession.startingBalance = 0.0;
|
|
|
|
|
g_dailySession.startingEquity = 0.0;
|
|
|
|
|
g_dailySession.balanceConfirmed = false;
|
|
|
|
|
g_dailySession.latestBalance = g_accountState.balance;
|
|
|
|
|
g_dailySession.latestEquity = g_accountState.equity;
|
|
|
|
|
g_dailySession.dailyLoss = 0.0;
|
|
|
|
|
g_dailySession.maximumDailyLossReached = 0.0;
|
|
|
|
|
g_dailySession.targetProgress = 0.0;
|
|
|
|
|
g_dailySession.sessionStatus = "AWAITING_CONFIRMATION";
|
|
|
|
|
g_dailySession.createdAt = TimeCurrent();
|
|
|
|
|
g_dailySession.updatedAt = TimeCurrent();
|
|
|
|
|
|
|
|
|
|
//--- Persist the new daily-session record
|
|
|
|
|
if(!SaveDailySession())
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
g_hasDailySession = true;
|
|
|
|
|
|
|
|
|
|
Print("Prop Firm Guard: New daily session created for ",
|
|
|
|
|
tradingDate,
|
|
|
|
|
".");
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-08-31 18:18:41 +03:00
|
|
|
|
2026-08-30 19:24:48 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Initialize account settings |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool InitializeAccountSettings()
|
|
|
|
|
{
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Check whether this account/server pair already has stored settings
|
2026-08-30 19:24:48 +03:00
|
|
|
bool settingsFound = LoadAccountSettings();
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Associate the settings with the current trading account
|
2026-08-30 19:24:48 +03:00
|
|
|
g_accountSettings.accountLogin = g_accountState.login;
|
|
|
|
|
g_accountSettings.accountServer = AccountInfoString(ACCOUNT_SERVER);
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Synchronize persistence with the current Inputs values
|
2026-08-30 19:24:48 +03:00
|
|
|
g_accountSettings.initialBalance = InpInitialAccountBalance;
|
|
|
|
|
g_accountSettings.dailyDrawdownPercent = InpDailyDrawdownPercent;
|
|
|
|
|
g_accountSettings.overallDrawdownPercent = InpOverallDrawdownPercent;
|
|
|
|
|
g_accountSettings.profitTargetPercent = InpProfitTargetPercent;
|
|
|
|
|
g_accountSettings.updatedAt = TimeCurrent();
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Save the synchronized configuration
|
2026-08-30 19:24:48 +03:00
|
|
|
if(!SaveAccountSettings())
|
|
|
|
|
return(false);
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Report whether the record was created or synchronized
|
2026-08-30 19:24:48 +03:00
|
|
|
if(settingsFound)
|
|
|
|
|
Print("Prop Firm Guard: Account settings restored and synchronized.");
|
|
|
|
|
else
|
|
|
|
|
Print("Prop Firm Guard: New account settings saved.");
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-01 08:12:04 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Save daily session |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool SaveDailySession()
|
|
|
|
|
{
|
|
|
|
|
//--- Prepare the complete daily-session record
|
|
|
|
|
string sql =
|
|
|
|
|
"INSERT OR REPLACE INTO daily_sessions ("
|
|
|
|
|
"account_login,"
|
|
|
|
|
"account_server,"
|
|
|
|
|
"trading_date,"
|
|
|
|
|
"starting_balance,"
|
|
|
|
|
"starting_equity,"
|
|
|
|
|
"balance_confirmed,"
|
|
|
|
|
"latest_balance,"
|
|
|
|
|
"latest_equity,"
|
|
|
|
|
"daily_loss,"
|
|
|
|
|
"maximum_daily_loss_reached,"
|
|
|
|
|
"target_progress,"
|
|
|
|
|
"session_status,"
|
|
|
|
|
"created_at,"
|
|
|
|
|
"updated_at"
|
|
|
|
|
") VALUES ("
|
|
|
|
|
"?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14"
|
|
|
|
|
");";
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
int request = DatabasePrepare(g_database,
|
|
|
|
|
sql,
|
|
|
|
|
g_dailySession.accountLogin,
|
|
|
|
|
g_dailySession.accountServer,
|
|
|
|
|
g_dailySession.tradingDate,
|
|
|
|
|
g_dailySession.startingBalance,
|
|
|
|
|
g_dailySession.startingEquity,
|
|
|
|
|
g_dailySession.balanceConfirmed ? 1 : 0,
|
|
|
|
|
g_dailySession.latestBalance,
|
|
|
|
|
g_dailySession.latestEquity,
|
|
|
|
|
g_dailySession.dailyLoss,
|
|
|
|
|
g_dailySession.maximumDailyLossReached,
|
|
|
|
|
g_dailySession.targetProgress,
|
|
|
|
|
g_dailySession.sessionStatus,
|
|
|
|
|
(long)g_dailySession.createdAt,
|
|
|
|
|
(long)g_dailySession.updatedAt);
|
|
|
|
|
|
|
|
|
|
if(request == INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not prepare daily session save request. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Execute and finalize the prepared write request
|
|
|
|
|
return(ExecuteDatabaseRequest(request,
|
|
|
|
|
"saving daily session"));
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-01 08:12:04 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Load daily session |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool LoadDailySession()
|
|
|
|
|
{
|
|
|
|
|
//--- Select the session belonging to the current account and date
|
|
|
|
|
string sql =
|
|
|
|
|
"SELECT "
|
|
|
|
|
"account_login,"
|
|
|
|
|
"account_server,"
|
|
|
|
|
"trading_date,"
|
|
|
|
|
"starting_balance,"
|
|
|
|
|
"starting_equity,"
|
|
|
|
|
"balance_confirmed,"
|
|
|
|
|
"latest_balance,"
|
|
|
|
|
"latest_equity,"
|
|
|
|
|
"daily_loss,"
|
|
|
|
|
"maximum_daily_loss_reached,"
|
|
|
|
|
"target_progress,"
|
|
|
|
|
"session_status,"
|
|
|
|
|
"created_at,"
|
|
|
|
|
"updated_at "
|
|
|
|
|
"FROM daily_sessions "
|
|
|
|
|
"WHERE account_login=?1 "
|
|
|
|
|
"AND account_server=?2 "
|
|
|
|
|
"AND trading_date=?3;";
|
|
|
|
|
|
|
|
|
|
string tradingDate = GetTradingDate();
|
|
|
|
|
string accountServer = AccountInfoString(ACCOUNT_SERVER);
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
int request = DatabasePrepare(g_database,
|
|
|
|
|
sql,
|
|
|
|
|
g_accountState.login,
|
|
|
|
|
accountServer,
|
|
|
|
|
tradingDate);
|
|
|
|
|
|
|
|
|
|
if(request == INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not prepare daily session load request. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Read the matching record
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
if(!DatabaseRead(request))
|
|
|
|
|
{
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
if(errorCode != ERR_DATABASE_NO_MORE_DATA)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not load daily session. Error: ",
|
|
|
|
|
errorCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FinalizeDatabaseRequest(request);
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Read all stored values before updating the in-memory session
|
|
|
|
|
long accountLogin;
|
|
|
|
|
string loadedServer;
|
|
|
|
|
string loadedDate;
|
|
|
|
|
double startingBalance;
|
|
|
|
|
double startingEquity;
|
|
|
|
|
int balanceConfirmed;
|
|
|
|
|
double latestBalance;
|
|
|
|
|
double latestEquity;
|
|
|
|
|
double dailyLoss;
|
|
|
|
|
double maximumDailyLossReached;
|
|
|
|
|
double targetProgress;
|
|
|
|
|
string sessionStatus;
|
|
|
|
|
long createdAt;
|
|
|
|
|
long updatedAt;
|
|
|
|
|
|
|
|
|
|
bool loaded =
|
|
|
|
|
DatabaseColumnLong(request, 0, accountLogin) &&
|
|
|
|
|
DatabaseColumnText(request, 1, loadedServer) &&
|
|
|
|
|
DatabaseColumnText(request, 2, loadedDate) &&
|
|
|
|
|
DatabaseColumnDouble(request, 3, startingBalance) &&
|
|
|
|
|
DatabaseColumnDouble(request, 4, startingEquity) &&
|
|
|
|
|
DatabaseColumnInteger(request, 5, balanceConfirmed) &&
|
|
|
|
|
DatabaseColumnDouble(request, 6, latestBalance) &&
|
|
|
|
|
DatabaseColumnDouble(request, 7, latestEquity) &&
|
|
|
|
|
DatabaseColumnDouble(request, 8, dailyLoss) &&
|
|
|
|
|
DatabaseColumnDouble(request, 9, maximumDailyLossReached) &&
|
|
|
|
|
DatabaseColumnDouble(request, 10, targetProgress) &&
|
|
|
|
|
DatabaseColumnText(request, 11, sessionStatus) &&
|
|
|
|
|
DatabaseColumnLong(request, 12, createdAt) &&
|
|
|
|
|
DatabaseColumnLong(request, 13, updatedAt);
|
|
|
|
|
|
|
|
|
|
if(!loaded)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not read daily session columns. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
FinalizeDatabaseRequest(request);
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Restore the complete daily-session state
|
|
|
|
|
g_dailySession.accountLogin = accountLogin;
|
|
|
|
|
g_dailySession.accountServer = loadedServer;
|
|
|
|
|
g_dailySession.tradingDate = loadedDate;
|
|
|
|
|
g_dailySession.startingBalance = startingBalance;
|
|
|
|
|
g_dailySession.startingEquity = startingEquity;
|
|
|
|
|
g_dailySession.balanceConfirmed = (balanceConfirmed != 0);
|
|
|
|
|
g_dailySession.latestBalance = latestBalance;
|
|
|
|
|
g_dailySession.latestEquity = latestEquity;
|
|
|
|
|
g_dailySession.dailyLoss = dailyLoss;
|
|
|
|
|
g_dailySession.maximumDailyLossReached = maximumDailyLossReached;
|
|
|
|
|
g_dailySession.targetProgress = targetProgress;
|
|
|
|
|
g_dailySession.sessionStatus = sessionStatus;
|
|
|
|
|
g_dailySession.createdAt = (datetime)createdAt;
|
|
|
|
|
g_dailySession.updatedAt = (datetime)updatedAt;
|
|
|
|
|
|
|
|
|
|
//--- Release the completed read request
|
|
|
|
|
FinalizeDatabaseRequest(request);
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-01 08:12:04 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Check daily session existence |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool DailySessionExists(const string tradingDate)
|
|
|
|
|
{
|
|
|
|
|
//--- Build the session identity for the requested trading date
|
|
|
|
|
string accountServer = AccountInfoString(ACCOUNT_SERVER);
|
|
|
|
|
|
|
|
|
|
string sql =
|
|
|
|
|
"SELECT 1 "
|
|
|
|
|
"FROM daily_sessions "
|
|
|
|
|
"WHERE account_login = ?1 "
|
|
|
|
|
"AND account_server = ?2 "
|
|
|
|
|
"AND trading_date = ?3 "
|
|
|
|
|
"LIMIT 1;";
|
|
|
|
|
|
|
|
|
|
int request = DatabasePrepare(g_database, sql);
|
|
|
|
|
|
|
|
|
|
if(request == INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not prepare session existence request. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Bind the current account, server, and requested date
|
|
|
|
|
if(!DatabaseBind(request, 0, g_accountState.login) ||
|
|
|
|
|
!DatabaseBind(request, 1, accountServer) ||
|
|
|
|
|
!DatabaseBind(request, 2, tradingDate))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not bind session existence values. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
FinalizeDatabaseRequest(request);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Check whether one matching row exists
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
bool exists = DatabaseRead(request);
|
|
|
|
|
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
FinalizeDatabaseRequest(request);
|
|
|
|
|
|
|
|
|
|
if(!exists && errorCode != ERR_DATABASE_NO_MORE_DATA)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not check daily session existence. Error: ",
|
|
|
|
|
errorCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return(exists);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-02 08:29:37 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Create starting balance edit |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CreateStartingBalanceEdit(const int x,
|
|
|
|
|
const int y,
|
|
|
|
|
const string suggestedBalance)
|
|
|
|
|
{
|
|
|
|
|
//--- Create the editable starting-balance field
|
|
|
|
|
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 position and appearance
|
|
|
|
|
if(!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_CORNER,
|
|
|
|
|
CORNER_LEFT_UPPER) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_XDISTANCE,
|
|
|
|
|
x) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_YDISTANCE,
|
|
|
|
|
y) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_XSIZE,
|
|
|
|
|
120) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_YSIZE,
|
|
|
|
|
20) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_FONTSIZE,
|
|
|
|
|
9) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_COLOR,
|
|
|
|
|
clrBlack) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_BGCOLOR,
|
|
|
|
|
clrWhite) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_BORDER_COLOR,
|
|
|
|
|
clrSilver) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_ALIGN,
|
|
|
|
|
ALIGN_RIGHT) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_HIDDEN,
|
|
|
|
|
true))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not configure starting balance field. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Apply the suggested balance and text properties
|
|
|
|
|
if(!ObjectSetString(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_FONT,
|
|
|
|
|
"Tahoma") ||
|
|
|
|
|
!ObjectSetString(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_TEXT,
|
|
|
|
|
suggestedBalance))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not initialize starting balance field. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-02 08:29:37 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Create confirm balance button |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CreateConfirmBalanceButton(const int x,
|
|
|
|
|
const int y)
|
|
|
|
|
{
|
|
|
|
|
//--- Create the daily-balance confirmation button
|
|
|
|
|
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 button position and appearance
|
|
|
|
|
if(!ObjectSetInteger(0,
|
|
|
|
|
g_confirmButton,
|
|
|
|
|
OBJPROP_CORNER,
|
|
|
|
|
CORNER_LEFT_UPPER) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_confirmButton,
|
|
|
|
|
OBJPROP_XDISTANCE,
|
|
|
|
|
x) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_confirmButton,
|
|
|
|
|
OBJPROP_YDISTANCE,
|
|
|
|
|
y) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_confirmButton,
|
|
|
|
|
OBJPROP_XSIZE,
|
|
|
|
|
100) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_confirmButton,
|
|
|
|
|
OBJPROP_YSIZE,
|
|
|
|
|
20) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_confirmButton,
|
|
|
|
|
OBJPROP_FONTSIZE,
|
|
|
|
|
9) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_confirmButton,
|
|
|
|
|
OBJPROP_COLOR,
|
|
|
|
|
clrBlack) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_confirmButton,
|
|
|
|
|
OBJPROP_BGCOLOR,
|
|
|
|
|
clrWhite) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_confirmButton,
|
|
|
|
|
OBJPROP_BORDER_COLOR,
|
|
|
|
|
clrSilver) ||
|
|
|
|
|
!ObjectSetInteger(0,
|
|
|
|
|
g_confirmButton,
|
|
|
|
|
OBJPROP_HIDDEN,
|
|
|
|
|
true))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not configure confirmation button. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Apply the button text 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());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-02 08:29:37 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Create daily session controls |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CreateDailySessionControls()
|
|
|
|
|
{
|
|
|
|
|
//--- Skip the controls when today's balance is already confirmed
|
|
|
|
|
if(g_dailySession.balanceConfirmed)
|
|
|
|
|
return(true);
|
|
|
|
|
|
|
|
|
|
int controlX = InpDashboardX + 18;
|
|
|
|
|
int controlY = InpDashboardY + 120;
|
|
|
|
|
|
|
|
|
|
//--- Suggest the current balance without confirming it
|
|
|
|
|
string suggestedBalance =
|
|
|
|
|
DoubleToString(g_accountState.balance, 2);
|
|
|
|
|
|
|
|
|
|
//--- Create the editable starting-balance field
|
|
|
|
|
if(!CreateStartingBalanceEdit(controlX,
|
|
|
|
|
controlY,
|
|
|
|
|
suggestedBalance))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
//--- Create the confirmation button beside the edit field
|
|
|
|
|
if(!CreateConfirmBalanceButton(controlX + 130,
|
|
|
|
|
controlY))
|
|
|
|
|
return(false);
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-02 08:29:37 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Delete daily session controls |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void DeleteDailySessionControls()
|
|
|
|
|
{
|
|
|
|
|
//--- Remove the starting-balance field when present
|
|
|
|
|
if(ObjectFind(0, g_startBalanceEdit) >= 0)
|
|
|
|
|
{
|
|
|
|
|
if(!ObjectDelete(0, g_startBalanceEdit))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not delete starting balance field. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Remove the confirmation button when present
|
|
|
|
|
if(ObjectFind(0, g_confirmButton) >= 0)
|
|
|
|
|
{
|
|
|
|
|
if(!ObjectDelete(0, g_confirmButton))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not delete confirmation button. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChartRedraw();
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-02 10:03:37 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Confirm daily starting balance |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool ConfirmDailyStartingBalance()
|
|
|
|
|
{
|
|
|
|
|
//--- Do not overwrite an already confirmed daily baseline
|
|
|
|
|
if(g_dailySession.balanceConfirmed)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Today's starting balance is already confirmed.");
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string balanceText;
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
//--- Read the value entered in the starting-balance field
|
|
|
|
|
if(!ObjectGetString(0,
|
|
|
|
|
g_startBalanceEdit,
|
|
|
|
|
OBJPROP_TEXT,
|
|
|
|
|
0,
|
|
|
|
|
balanceText))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not read starting balance field. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double startingBalance = StringToDouble(balanceText);
|
|
|
|
|
|
|
|
|
|
//--- Reject an invalid daily starting balance
|
|
|
|
|
if(startingBalance <= 0.0)
|
|
|
|
|
{
|
|
|
|
|
Alert("Prop Firm Guard: Enter a valid starting balance greater than zero.");
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Store the confirmed daily reference
|
|
|
|
|
g_dailySession.startingBalance = startingBalance;
|
|
|
|
|
g_dailySession.startingEquity = g_accountState.equity;
|
|
|
|
|
g_dailySession.balanceConfirmed = true;
|
|
|
|
|
g_dailySession.updatedAt = TimeCurrent();
|
|
|
|
|
|
|
|
|
|
//--- Persist the confirmed session immediately
|
|
|
|
|
if(!SaveDailySession())
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not save confirmed daily session.");
|
|
|
|
|
|
|
|
|
|
//--- Do not report a confirmed state that SQLite failed to preserve
|
|
|
|
|
g_dailySession.balanceConfirmed = false;
|
|
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Print("Prop Firm Guard: Daily starting balance confirmed at ",
|
|
|
|
|
DoubleToString(g_dailySession.startingBalance, 2),
|
|
|
|
|
" for ",
|
|
|
|
|
g_dailySession.tradingDate,
|
|
|
|
|
".");
|
|
|
|
|
|
|
|
|
|
//--- Remove controls after the confirmed value has been stored
|
|
|
|
|
DeleteDailySessionControls();
|
|
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-03 09:55:13 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Convert compliance status to text |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string ComplianceStatusToString(const ENUM_COMPLIANCE_STATUS status)
|
|
|
|
|
{
|
|
|
|
|
switch(status)
|
|
|
|
|
{
|
|
|
|
|
case COMPLIANCE_WARNING:
|
|
|
|
|
return("WARNING");
|
|
|
|
|
|
|
|
|
|
case COMPLIANCE_CRITICAL:
|
|
|
|
|
return("CRITICAL");
|
|
|
|
|
|
|
|
|
|
case COMPLIANCE_BREACHED:
|
|
|
|
|
return("BREACHED");
|
|
|
|
|
|
|
|
|
|
case COMPLIANCE_SAFE:
|
|
|
|
|
default:
|
|
|
|
|
return("SAFE");
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-03 09:55:13 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Convert target status to text |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string TargetStatusToString(const ENUM_TARGET_STATUS status)
|
|
|
|
|
{
|
|
|
|
|
switch(status)
|
|
|
|
|
{
|
|
|
|
|
case TARGET_NEARING:
|
|
|
|
|
return("NEARING TARGET");
|
|
|
|
|
|
|
|
|
|
case TARGET_REACHED:
|
|
|
|
|
return("TARGET REACHED");
|
|
|
|
|
|
|
|
|
|
case TARGET_IN_PROGRESS:
|
|
|
|
|
default:
|
|
|
|
|
return("IN PROGRESS");
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-03 10:16:43 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Get overall guard status |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
ENUM_GUARD_STATUS GetOverallGuardStatus()
|
|
|
|
|
{
|
|
|
|
|
//--- Give the highest priority to any drawdown breach
|
|
|
|
|
if(g_dailyDrawdown.status == COMPLIANCE_BREACHED ||
|
|
|
|
|
g_overallDrawdown.status == COMPLIANCE_BREACHED)
|
|
|
|
|
return(GUARD_BREACHED);
|
|
|
|
|
|
|
|
|
|
//--- Check for a critical drawdown condition
|
|
|
|
|
if(g_dailyDrawdown.status == COMPLIANCE_CRITICAL ||
|
|
|
|
|
g_overallDrawdown.status == COMPLIANCE_CRITICAL)
|
|
|
|
|
return(GUARD_CRITICAL);
|
|
|
|
|
|
|
|
|
|
//--- Check for a warning condition
|
|
|
|
|
if(g_dailyDrawdown.status == COMPLIANCE_WARNING ||
|
|
|
|
|
g_overallDrawdown.status == COMPLIANCE_WARNING)
|
|
|
|
|
return(GUARD_WARNING);
|
|
|
|
|
|
|
|
|
|
//--- Report target completion only when no risk condition has priority
|
|
|
|
|
if(g_profitTarget.status == TARGET_REACHED)
|
|
|
|
|
return(GUARD_TARGET_REACHED);
|
|
|
|
|
|
|
|
|
|
return(GUARD_SAFE);
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-03 10:16:43 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Convert guard status to text |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string GuardStatusToString(const ENUM_GUARD_STATUS status)
|
|
|
|
|
{
|
|
|
|
|
switch(status)
|
|
|
|
|
{
|
|
|
|
|
case GUARD_BREACHED:
|
|
|
|
|
return("BREACHED");
|
|
|
|
|
|
|
|
|
|
case GUARD_CRITICAL:
|
|
|
|
|
return("CRITICAL");
|
|
|
|
|
|
|
|
|
|
case GUARD_WARNING:
|
|
|
|
|
return("WARNING");
|
|
|
|
|
|
|
|
|
|
case GUARD_TARGET_REACHED:
|
|
|
|
|
return("TARGET REACHED");
|
|
|
|
|
|
|
|
|
|
case GUARD_SAFE:
|
|
|
|
|
default:
|
|
|
|
|
return("SAFE");
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-15 02:44:23 -07:00
|
|
|
|
2026-08-13 09:00:54 -07:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Expert initialization function |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
int OnInit()
|
|
|
|
|
{
|
2026-09-05 19:17:39 +03:00
|
|
|
//--- Validate the timer interval
|
2026-08-30 21:30:12 +03:00
|
|
|
if(InpTimerSeconds < 1)
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Timer interval must be at least 1 second.");
|
|
|
|
|
|
|
|
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-05 19:17:39 +03:00
|
|
|
//--- Validate the configured compliance inputs
|
2026-08-30 12:36:36 +03:00
|
|
|
if(!ValidateComplianceInputs())
|
|
|
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
|
|
|
|
2026-09-02 10:34:03 +03:00
|
|
|
//--- Read the current account state
|
2026-08-30 11:53:44 +03:00
|
|
|
UpdateAccountState();
|
|
|
|
|
|
2026-09-05 19:17:39 +03:00
|
|
|
//--- Open the SQLite database
|
2026-08-30 13:06:01 +03:00
|
|
|
if(!OpenDatabase())
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Database initialization failed.");
|
|
|
|
|
|
|
|
|
|
return(INIT_FAILED);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-05 19:17:39 +03:00
|
|
|
//--- Ensure the required database tables exist
|
2026-08-30 14:24:21 +03:00
|
|
|
if(!EnsureDatabaseTables())
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not initialize database tables.");
|
|
|
|
|
|
|
|
|
|
CloseDatabase();
|
|
|
|
|
|
|
|
|
|
return(INIT_FAILED);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-05 19:17:39 +03:00
|
|
|
//--- Restore account-specific settings
|
2026-08-30 19:24:48 +03:00
|
|
|
if(!InitializeAccountSettings())
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not initialize account settings.");
|
|
|
|
|
|
|
|
|
|
CloseDatabase();
|
|
|
|
|
|
|
|
|
|
return(INIT_FAILED);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-05 19:17:39 +03:00
|
|
|
//--- Restore or create the current daily session
|
2026-09-02 07:13:30 +03:00
|
|
|
if(!InitializeDailySession())
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Could not initialize daily session.");
|
|
|
|
|
|
|
|
|
|
CloseDatabase();
|
|
|
|
|
|
|
|
|
|
return(INIT_FAILED);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-05 19:17:39 +03:00
|
|
|
//--- Calculate the initial financial compliance state
|
2026-09-03 10:30:42 +03:00
|
|
|
CalculateDailyDrawdown();
|
|
|
|
|
CalculateOverallDrawdown();
|
|
|
|
|
CalculateProfitTarget();
|
|
|
|
|
|
2026-09-05 19:17:39 +03:00
|
|
|
//--- Calculate the initial economic-news state
|
|
|
|
|
UpdateNewsState();
|
2026-09-02 08:29:37 +03:00
|
|
|
|
2026-09-07 05:34:09 +03:00
|
|
|
//--- Classify positions and evaluate the initial holding state
|
|
|
|
|
UpdateNewsPositionMatches();
|
|
|
|
|
|
2026-09-05 19:17:39 +03:00
|
|
|
//--- Start periodic monitoring
|
2026-09-02 07:54:42 +03:00
|
|
|
if(!EventSetTimer(InpTimerSeconds))
|
|
|
|
|
{
|
|
|
|
|
Print("Prop Firm Guard: Failed to start the timer. Error: ",
|
|
|
|
|
GetLastError());
|
|
|
|
|
|
|
|
|
|
CloseDatabase();
|
|
|
|
|
|
|
|
|
|
return(INIT_FAILED);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-30 21:30:12 +03:00
|
|
|
Print("Prop Firm Guard initialized successfully.");
|
|
|
|
|
|
2026-08-13 09:00:54 -07:00
|
|
|
return(INIT_SUCCEEDED);
|
|
|
|
|
}
|
2026-08-15 02:44:23 -07:00
|
|
|
|
2026-08-13 09:00:54 -07:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Expert deinitialization function |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void OnDeinit(const int reason)
|
|
|
|
|
{
|
2026-09-02 10:34:03 +03:00
|
|
|
//--- Stop periodic events
|
2026-09-02 07:54:42 +03:00
|
|
|
EventKillTimer();
|
|
|
|
|
|
2026-09-02 10:34:03 +03:00
|
|
|
//--- Remove daily-session controls
|
2026-09-02 08:29:37 +03:00
|
|
|
DeleteDailySessionControls();
|
|
|
|
|
|
2026-08-31 19:38:11 +03:00
|
|
|
//--- Close the SQLite connection
|
2026-08-30 13:06:01 +03:00
|
|
|
CloseDatabase();
|
|
|
|
|
|
|
|
|
|
Print("Prop Firm Guard removed. Deinitialization reason: ", reason);
|
2026-08-13 09:00:54 -07:00
|
|
|
}
|
2026-08-15 02:44:23 -07:00
|
|
|
|
2026-08-13 09:00:54 -07:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Expert tick function |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void OnTick()
|
|
|
|
|
{
|
2026-08-31 18:18:41 +03:00
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-02 07:54:42 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Timer function |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void OnTimer()
|
|
|
|
|
{
|
2026-09-03 09:35:13 +03:00
|
|
|
//--- Refresh the current account state
|
2026-09-02 07:54:42 +03:00
|
|
|
UpdateAccountState();
|
|
|
|
|
|
2026-09-03 10:30:42 +03:00
|
|
|
//--- Maintain the current trading-day session
|
2026-09-02 07:54:42 +03:00
|
|
|
CheckForNewTradingDay();
|
2026-09-03 10:30:42 +03:00
|
|
|
|
|
|
|
|
//--- Recalculate the financial compliance state
|
|
|
|
|
CalculateDailyDrawdown();
|
|
|
|
|
CalculateOverallDrawdown();
|
|
|
|
|
CalculateProfitTarget();
|
2026-09-05 19:17:39 +03:00
|
|
|
|
2026-09-07 05:34:09 +03:00
|
|
|
//--- Refresh the account-level economic-news state
|
2026-09-05 19:17:39 +03:00
|
|
|
UpdateNewsState();
|
2026-09-07 05:34:09 +03:00
|
|
|
|
|
|
|
|
//--- Refresh position matching and news-holding evaluation
|
|
|
|
|
UpdateNewsPositionMatches();
|
2026-09-02 07:54:42 +03:00
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
|
2026-09-02 10:03:37 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Chart event function |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void OnChartEvent(const int id,
|
|
|
|
|
const long &lparam,
|
|
|
|
|
const double &dparam,
|
|
|
|
|
const string &sparam)
|
|
|
|
|
{
|
|
|
|
|
//--- Process chart-object clicks only
|
|
|
|
|
if(id != CHARTEVENT_OBJECT_CLICK)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
//--- Confirm the entered balance when the confirmation button is clicked
|
|
|
|
|
if(sparam == g_confirmButton)
|
|
|
|
|
{
|
|
|
|
|
if(!ConfirmDailyStartingBalance())
|
|
|
|
|
Print("Prop Firm Guard: Starting balance confirmation failed.");
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-07 04:17:47 +03:00
|
|
|
//+------------------------------------------------------------------+
|