mql5/Experts/Advisors/ERMT_6.903-DualDash.mq5
2025-09-01 16:55:52 +01:00

770 lines
No EOL
30 KiB
MQL5

//+------------------------------------------------------------------+
//| ERMT_6.9-DualDash.mq5|
//| Enterprise Risk Management Terminal v6.9 |
//| Complete Working Implementation |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Institutional Trading Systems"
#property link "https://example.com"
#property version "6.903"
#property description "Enterprise Risk Management Terminal - Dual Dashboard Edition"
//+------------------------------------------------------------------+
//| Include Files |
//+------------------------------------------------------------------+
//--- Include base MQL5 classes
#include <Object.mqh>
#include <Trade/Trade.mqh>
//--- Include system modules
#include "Modules/DataTypes.mqh"
#include "Modules/Utilities.mqh"
#include "Modules/RiskManager.mqh"
#include "Modules/TradeManager.mqh"
#include "Modules/TechnicalAnalysis.mqh"
#include "Modules/EntrySystem.mqh"
#include "Modules/Dashboard.mqh" // Original Dashboard
#include "Modules/Dashboard_SelfContained.mqh" // New Self-Contained Dashboard
#include "Modules/ExternalTradeManager.mqh"
//+------------------------------------------------------------------+
//| Input Parameters - Complete Set |
//+------------------------------------------------------------------+
// Risk Management Parameters
input double InpMaxRiskPerTrade = 1.0; // Maximum Risk Per Trade (%)
input double InpMaxDailyLoss = 5.0; // Maximum Daily Loss (%)
input double InpMaxDrawdown = 20.0; // Maximum Drawdown (%)
input double InpMaxLeverage = 10.0; // Maximum Leverage
input int InpMaxPositions = 5; // Maximum Concurrent Positions
input double InpMaxCorrelation = 0.7; // Maximum Correlation Threshold
// Trade Management Parameters
input ENUM_TRADE_MANAGEMENT_MODE InpManagementMode = MANAGEMENT_BASIC; // Management Mode
input ENUM_SL_MODE InpSLMode = SL_ATR; // Stop Loss Mode
input ENUM_TP_MODE InpTPMode = TP_RISK_REWARD; // Take Profit Mode
input double InpRiskRewardRatio = 2.0; // Risk/Reward Ratio
input bool InpUseTrailingStop = true; // Use Trailing Stop
input double InpTrailingDistance = 50; // Trailing Distance (points)
input bool InpMoveToBreakeven = true; // Move to Breakeven
input double InpBreakevenTrigger = 30; // Breakeven Trigger (points)
// Position Sizing Parameters
input ENUM_POSITION_SIZING InpSizingMethod = POSITION_SIZE_RISK; // Position Sizing Method
input double InpFixedLotSize = 0.01; // Fixed Lot Size
input double InpRiskPercent = 1.0; // Risk Percentage for Sizing
// Entry System Parameters
input ENUM_ENTRY_MODE InpEntryMode = ENTRY_MANUAL; // Entry Mode
input ENUM_ENTRY_STRATEGY InpEntryStrategy = ENTRY_MA_CROSS; // Entry Strategy
input int InpMAPeriodFast = 10; // Fast MA Period
input int InpMAPeriodSlow = 20; // Slow MA Period
input int InpMomentumPeriod = 14; // Momentum Period
input double InpBreakoutThreshold = 1.5; // Breakout Threshold (ATR)
input int InpMinSignalStrength = 5; // Minimum Signal Strength (1-10)
// Dashboard Parameters
input int InpOriginalDashboardX = 10; // Original Dashboard X Position
input int InpOriginalDashboardY = 30; // Original Dashboard Y Position
input int InpDualDashboardX = 350; // Dual Dashboard X Position
input int InpDualDashboardY = 30; // Dual Dashboard Y Position
input int InpDashboardWidth = 320; // Dashboard Width
input int InpDashboardHeight = 200; // Dashboard Height
input bool InpShowOriginalDashboard = true; // Show Original Dashboard
input bool InpShowDualDashboard = true; // Show Dual Dashboard
input ENUM_DASH_THEME InpDashboardTheme = DASH_THEME_DARK; // Dashboard Theme
// System Parameters
input int InpMagicNumber = 123456; // Magic Number
input string InpCommentPrefix = "ERMT_"; // Trade Comment Prefix
input int InpUpdateFrequency = 1; // Update Frequency (seconds)
input bool InpEnableAlerts = true; // Enable Alerts
input bool InpEnableNotifications = false; // Enable Push Notifications
input bool InpEnableEmails = false; // Enable Email Alerts
input ENUM_LOG_LEVEL InpLogLevel = LOG_INFO; // Logging Level
// Time Filter Parameters
input bool InpUseTimeFilter = false; // Use Time Filter
input int InpStartHour = 8; // Trading Start Hour
input int InpEndHour = 20; // Trading End Hour
input bool InpTradeOnMonday = true; // Trade on Monday
input bool InpTradeOnFriday = true; // Trade on Friday
input bool InpCloseOnFriday = false; // Close All on Friday
//+------------------------------------------------------------------+
//| Global Variables and Objects |
//+------------------------------------------------------------------+
// Core System Objects
CRiskManager g_RiskManager; // Risk manager instance
CDashboardSelfContained g_Dashboard; // Original dashboard
CDashboardSelfContained g_DualDashboard; // Dual dashboard
CUtilities* g_pUtils; // Utilities pointer
// Data Storage
ManagedTrade g_ManagedTrades[]; // Array of managed trades
RiskMetrics g_RiskMetrics; // Current risk metrics
PerformanceMetrics g_Performance; // Performance metrics
MarketConditions g_MarketConditions; // Current market conditions
// System State Variables
bool g_IsInitialized = false; // System initialized flag
bool g_IsTrading = false; // Trading enabled flag
bool g_InPosition = false; // Currently in position flag
datetime g_LastUpdateTime = 0; // Last update timestamp
datetime g_LastTradeTime = 0; // Last trade timestamp
int g_ConsecutiveLosses = 0; // Consecutive losses counter
double g_DailyProfit = 0; // Today's profit/loss
double g_SessionHigh = 0; // Session high balance
double g_SessionLow = 0; // Session low balance
// Performance Tracking
double g_StartingBalance = 0; // Starting balance
double g_PeakBalance = 0; // Peak balance for DD calc
int g_TotalTrades = 0; // Total trades counter
int g_WinningTrades = 0; // Winning trades counter
int g_LosingTrades = 0; // Losing trades counter
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Initialize logging
CLogger::Init(InpLogLevel, true, true);
CLogger::Log(LOG_INFO, "===========================================");
CLogger::Log(LOG_INFO, "ERMT 6.9 Dual Dashboard - Initializing");
CLogger::Log(LOG_INFO, "===========================================");
// Create utilities instance
g_pUtils = new CUtilities();
if(g_pUtils == NULL)
{
CLogger::Log(LOG_ERROR, "Failed to create Utilities instance");
return INIT_FAILED;
}
// Initialize Risk Parameters
RiskParameters riskParams;
riskParams.maxRiskPerTrade = InpMaxRiskPerTrade;
riskParams.maxDailyRisk = InpMaxDailyLoss;
riskParams.maxDrawdown = InpMaxDrawdown;
riskParams.maxLeverage = InpMaxLeverage;
riskParams.maxPositions = InpMaxPositions;
riskParams.maxCorrelation = InpMaxCorrelation;
riskParams.marginCallLevel = 50.0;
riskParams.stopOutLevel = 30.0;
riskParams.useVolatilityAdjustment = true;
riskParams.volatilityMultiplier = 1.5;
riskParams.useTimeFilter = InpUseTimeFilter;
riskParams.startHour = InpStartHour;
riskParams.endHour = InpEndHour;
riskParams.closeOnFriday = InpCloseOnFriday;
riskParams.tradeOnMonday = InpTradeOnMonday;
// Initialize Risk Manager
if(!g_RiskManager.Initialize(g_pUtils, riskParams))
{
CLogger::Log(LOG_ERROR, "Failed to initialize Risk Manager");
delete g_pUtils;
return INIT_FAILED;
}
// Initialize Dashboards
if(InpShowOriginalDashboard)
{
if(!g_Dashboard.Create("Original", InpOriginalDashboardX, InpOriginalDashboardY,
InpDashboardWidth, InpDashboardHeight))
{
CLogger::Log(LOG_WARNING, "Failed to create original dashboard");
}
else
{
g_Dashboard.SetTheme(InpDashboardTheme);
CLogger::Log(LOG_INFO, "Original dashboard created successfully");
}
}
if(InpShowDualDashboard)
{
if(!g_DualDashboard.Create("Dual", InpDualDashboardX, InpDualDashboardY,
InpDashboardWidth, InpDashboardHeight))
{
CLogger::Log(LOG_WARNING, "Failed to create dual dashboard");
}
else
{
g_DualDashboard.SetTheme(InpDashboardTheme);
g_DualDashboard.SetCompact(true); // Make dual dashboard compact
CLogger::Log(LOG_INFO, "Dual dashboard created successfully");
}
}
// Initialize tracking variables
g_StartingBalance = AccountInfoDouble(ACCOUNT_BALANCE);
g_PeakBalance = g_StartingBalance;
g_SessionHigh = g_StartingBalance;
g_SessionLow = g_StartingBalance;
g_LastUpdateTime = TimeCurrent();
// Initialize arrays
ArrayResize(g_ManagedTrades, 0);
// Set timer for updates
EventSetTimer(InpUpdateFrequency);
// Set initialization flag
g_IsInitialized = true;
g_IsTrading = true;
CLogger::Log(LOG_INFO, StringFormat("Starting Balance: %.2f", g_StartingBalance));
CLogger::Log(LOG_INFO, "ERMT 6.9 Initialized Successfully");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Kill timer
EventKillTimer();
// Log final statistics
LogFinalStatistics();
// Destroy dashboards
if(InpShowOriginalDashboard)
{
g_Dashboard.Destroy();
}
if(InpShowDualDashboard)
{
g_DualDashboard.Destroy();
}
// Clean up Risk Manager
g_RiskManager.Deinitialize();
// Delete utilities
if(g_pUtils != NULL)
{
delete g_pUtils;
g_pUtils = NULL;
}
// Log shutdown reason
string reasonStr = GetDeInitReasonText(reason);
CLogger::Log(LOG_INFO, "ERMT 6.9 Shutdown: " + reasonStr);
// Deinitialize logger
CLogger::Deinit();
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if(!g_IsInitialized || !g_IsTrading) return;
// Check if market is open
if(!IsMarketOpen()) return;
// Update market conditions
UpdateMarketConditions();
// Update managed trades
UpdateManagedTrades();
// Check for risk events
CheckRiskEvents();
// Process existing positions
ProcessExistingPositions();
// Check for new entry signals (if enabled)
if(InpEntryMode != ENTRY_MANUAL)
{
CheckEntrySignals();
}
}
//+------------------------------------------------------------------+
//| Timer function |
//+------------------------------------------------------------------+
void OnTimer()
{
if(!g_IsInitialized) return;
// Update performance metrics
UpdatePerformanceMetrics();
// Update risk metrics
g_RiskMetrics = g_RiskManager.GetCurrentRiskMetrics();
// Update dashboards
UpdateDashboards();
// Check for daily reset
CheckDailyReset();
g_LastUpdateTime = TimeCurrent();
}
//+------------------------------------------------------------------+
//| Update Market Conditions |
//+------------------------------------------------------------------+
void UpdateMarketConditions()
{
g_MarketConditions.volatility = g_RiskManager.GetVolatility(_Symbol);
g_MarketConditions.atr = g_RiskManager.GetATR(_Symbol, PERIOD_CURRENT, 14);
g_MarketConditions.spread = (SymbolInfoDouble(_Symbol, SYMBOL_ASK) -
SymbolInfoDouble(_Symbol, SYMBOL_BID)) /
SymbolInfoDouble(_Symbol, SYMBOL_POINT);
// Determine market condition
if(g_MarketConditions.volatility > g_MarketConditions.atr * 1.5)
{
g_MarketConditions.condition = MARKET_VOLATILE;
}
else if(g_MarketConditions.volatility < g_MarketConditions.atr * 0.5)
{
g_MarketConditions.condition = MARKET_QUIET;
}
else
{
g_MarketConditions.condition = MARKET_RANGING;
}
g_MarketConditions.lastUpdate = TimeCurrent();
}
//+------------------------------------------------------------------+
//| Update Managed Trades |
//+------------------------------------------------------------------+
void UpdateManagedTrades()
{
ArrayResize(g_ManagedTrades, 0);
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionSelectByTicket(PositionGetTicket(i)))
{
if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
ManagedTrade trade;
trade.ticket = PositionGetInteger(POSITION_TICKET);
trade.symbol = PositionGetString(POSITION_SYMBOL);
trade.type = (int)PositionGetInteger(POSITION_TYPE);
trade.volume = PositionGetDouble(POSITION_VOLUME);
trade.openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
trade.stopLoss = PositionGetDouble(POSITION_SL);
trade.takeProfit = PositionGetDouble(POSITION_TP);
trade.profit = PositionGetDouble(POSITION_PROFIT);
trade.commission = PositionGetDouble(POSITION_COMMISSION);
trade.swap = PositionGetDouble(POSITION_SWAP);
trade.openTime = (datetime)PositionGetInteger(POSITION_TIME);
trade.magic = (int)PositionGetInteger(POSITION_MAGIC);
trade.comment = PositionGetString(POSITION_COMMENT);
trade.isManaged = true;
trade.slMode = InpSLMode;
trade.tpMode = InpTPMode;
ArrayResize(g_ManagedTrades, count + 1);
g_ManagedTrades[count] = trade;
count++;
}
}
}
g_InPosition = (count > 0);
}
//+------------------------------------------------------------------+
//| Check Risk Events |
//+------------------------------------------------------------------+
void CheckRiskEvents()
{
if(g_RiskManager.IsRiskLimitExceeded())
{
ENUM_RISK_ACTION action = g_RiskManager.EvaluateRiskAction();
if(action != ACTION_NONE)
{
g_RiskManager.ExecuteRiskAction(action);
// Stop trading if critical
if(action == ACTION_CLOSE_ALL || action == ACTION_STOP_TRADING)
{
g_IsTrading = false;
CloseAllPositions();
}
}
}
}
//+------------------------------------------------------------------+
//| Process Existing Positions |
//+------------------------------------------------------------------+
void ProcessExistingPositions()
{
for(int i = 0; i < ArraySize(g_ManagedTrades); i++)
{
// Process trailing stop
if(InpUseTrailingStop)
{
ProcessTrailingStop(g_ManagedTrades[i]);
}
// Process breakeven
if(InpMoveToBreakeven && !g_ManagedTrades[i].isBreakeven)
{
ProcessBreakeven(g_ManagedTrades[i]);
}
}
}
//+------------------------------------------------------------------+
//| Process Trailing Stop |
//+------------------------------------------------------------------+
void ProcessTrailingStop(ManagedTrade &trade)
{
if(!PositionSelectByTicket(trade.ticket)) return;
double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
double currentSL = PositionGetDouble(POSITION_SL);
double point = SymbolInfoDouble(trade.symbol, SYMBOL_POINT);
double trailDistance = InpTrailingDistance * point;
bool isBuy = (trade.type == POSITION_TYPE_BUY);
double newSL = 0;
if(isBuy)
{
newSL = currentPrice - trailDistance;
if(newSL > currentSL && newSL < currentPrice)
{
ModifyPosition(trade.ticket, newSL, trade.takeProfit);
}
}
else
{
newSL = currentPrice + trailDistance;
if((currentSL == 0 || newSL < currentSL) && newSL > currentPrice)
{
ModifyPosition(trade.ticket, newSL, trade.takeProfit);
}
}
}
//+------------------------------------------------------------------+
//| Process Breakeven |
//+------------------------------------------------------------------+
void ProcessBreakeven(ManagedTrade &trade)
{
if(!PositionSelectByTicket(trade.ticket)) return;
double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
double openPrice = trade.openPrice;
double point = SymbolInfoDouble(trade.symbol, SYMBOL_POINT);
double triggerDistance = InpBreakevenTrigger * point;
bool isBuy = (trade.type == POSITION_TYPE_BUY);
if(isBuy)
{
if(currentPrice >= openPrice + triggerDistance)
{
double newSL = openPrice + point; // Small profit to cover costs
if(trade.stopLoss < newSL)
{
if(ModifyPosition(trade.ticket, newSL, trade.takeProfit))
{
trade.isBreakeven = true;
CLogger::Log(LOG_INFO, StringFormat("Position %d moved to breakeven", trade.ticket));
}
}
}
}
else
{
if(currentPrice <= openPrice - triggerDistance)
{
double newSL = openPrice - point;
if(trade.stopLoss == 0 || trade.stopLoss > newSL)
{
if(ModifyPosition(trade.ticket, newSL, trade.takeProfit))
{
trade.isBreakeven = true;
CLogger::Log(LOG_INFO, StringFormat("Position %d moved to breakeven", trade.ticket));
}
}
}
}
}
//+------------------------------------------------------------------+
//| Modify Position |
//+------------------------------------------------------------------+
bool ModifyPosition(ulong ticket, double sl, double tp)
{
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_SLTP;
request.position = ticket;
request.sl = NormalizeDouble(sl, _Digits);
request.tp = NormalizeDouble(tp, _Digits);
if(!OrderSend(request, result))
{
CLogger::Log(LOG_ERROR, StringFormat("Failed to modify position %d: %s",
ticket, GetLastErrorString()));
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Check Entry Signals |
//+------------------------------------------------------------------+
void CheckEntrySignals()
{
// This is a placeholder for entry signal logic
// Implement based on selected strategy
if(!g_RiskManager.CanOpenNewPosition(_Symbol, 0.01)) return;
// Entry logic would go here based on InpEntryStrategy
}
//+------------------------------------------------------------------+
//| Update Performance Metrics |
//+------------------------------------------------------------------+
void UpdatePerformanceMetrics()
{
double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE);
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
// Update account values
g_Performance.currentBalance = currentBalance;
g_Performance.currentEquity = currentEquity;
g_Performance.startingBalance = g_StartingBalance;
g_Performance.netProfit = currentBalance - g_StartingBalance;
// Update peak balance
if(currentBalance > g_PeakBalance)
{
g_PeakBalance = currentBalance;
}
g_Performance.peakBalance = g_PeakBalance;
// Calculate drawdown
if(g_PeakBalance > 0)
{
g_Performance.currentDrawdown = g_PeakBalance - currentBalance;
g_Performance.currentDrawdownPercent = (g_Performance.currentDrawdown / g_PeakBalance) * 100;
}
// Update trade statistics
g_Performance.totalTrades = g_TotalTrades;
g_Performance.winningTrades = g_WinningTrades;
g_Performance.losingTrades = g_LosingTrades;
// Calculate win rate
if(g_TotalTrades > 0)
{
g_Performance.winRate = ((double)g_WinningTrades / g_TotalTrades) * 100;
}
g_Performance.lastUpdateTime = TimeCurrent();
}
//+------------------------------------------------------------------+
//| Update Dashboards |
//+------------------------------------------------------------------+
void UpdateDashboards()
{
if(InpShowOriginalDashboard)
{
g_Dashboard.Update(g_RiskMetrics, g_Performance);
}
if(InpShowDualDashboard)
{
g_DualDashboard.Update(g_RiskMetrics, g_Performance);
}
}
//+------------------------------------------------------------------+
//| Check Daily Reset |
//+------------------------------------------------------------------+
void CheckDailyReset()
{
static datetime lastResetDate = 0;
MqlDateTime current;
TimeToStruct(TimeCurrent(), current);
datetime currentDate = StringToTime(StringFormat("%04d.%02d.%02d",
current.year, current.mon, current.day));
if(currentDate != lastResetDate)
{
g_DailyProfit = 0;
lastResetDate = currentDate;
CLogger::Log(LOG_INFO, "Daily metrics reset");
}
}
//+------------------------------------------------------------------+
//| Check if Market is Open |
//+------------------------------------------------------------------+
bool IsMarketOpen()
{
if(!InpUseTimeFilter) return true;
MqlDateTime current;
TimeToStruct(TimeCurrent(), current);
// Check day of week
if(current.day_of_week == 0 || current.day_of_week == 6) return false;
if(current.day_of_week == 1 && !InpTradeOnMonday) return false;
if(current.day_of_week == 5 && !InpTradeOnFriday) return false;
// Check time
if(current.hour < InpStartHour || current.hour >= InpEndHour) return false;
return true;
}
//+------------------------------------------------------------------+
//| Close All Positions |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
CLogger::Log(LOG_WARNING, "Closing all positions");
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionSelectByTicket(PositionGetTicket(i)))
{
if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;
request.position = PositionGetInteger(POSITION_TICKET);
request.symbol = PositionGetString(POSITION_SYMBOL);
request.volume = PositionGetDouble(POSITION_VOLUME);
request.type = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ?
ORDER_TYPE_SELL : ORDER_TYPE_BUY;
request.price = (request.type == ORDER_TYPE_SELL) ?
SymbolInfoDouble(request.symbol, SYMBOL_BID) :
SymbolInfoDouble(request.symbol, SYMBOL_ASK);
request.deviation = 10;
request.magic = InpMagicNumber;
OrderSend(request, result);
}
}
}
}
//+------------------------------------------------------------------+
//| Get Deinit Reason Text |
//+------------------------------------------------------------------+
string GetDeInitReasonText(int reason)
{
switch(reason)
{
case REASON_PROGRAM: return "Program terminated";
case REASON_REMOVE: return "Program removed from chart";
case REASON_RECOMPILE: return "Program recompiled";
case REASON_CHARTCHANGE: return "Chart symbol or period changed";
case REASON_CHARTCLOSE: return "Chart closed";
case REASON_PARAMETERS: return "Parameters changed";
case REASON_ACCOUNT: return "Account changed";
default: return "Unknown reason";
}
}
//+------------------------------------------------------------------+
//| Get Last Error String |
//+------------------------------------------------------------------+
string GetLastErrorString()
{
int error = GetLastError();
return StringFormat("Error %d: %s", error, ErrorDescription(error));
}
//+------------------------------------------------------------------+
//| Error Description |
//+------------------------------------------------------------------+
string ErrorDescription(int error_code)
{
// Add common error descriptions
switch(error_code)
{
case 0: return "No error";
case 4756: return "Trade request sending failed";
case 4757: return "Trade request canceled by timeout";
case 10004: return "Requote";
case 10006: return "Request rejected";
case 10007: return "Request canceled by trader";
case 10010: return "Only part of the request was completed";
case 10011: return "Request processing error";
case 10012: return "Request canceled by timeout";
case 10013: return "Invalid request";
case 10014: return "Invalid volume";
case 10015: return "Invalid price";
case 10016: return "Invalid stops";
case 10017: return "Trade disabled";
case 10018: return "Market closed";
case 10019: return "Not enough money";
case 10020: return "Prices changed";
case 10021: return "No quotes to process request";
case 10022: return "Invalid expiration date";
case 10023: return "Order state changed";
case 10024: return "Too frequent requests";
case 10025: return "No changes in request";
case 10026: return "Autotrading disabled by server";
case 10027: return "Autotrading disabled by client terminal";
default: return "Unknown error";
}
}
//+------------------------------------------------------------------+
//| Log Final Statistics |
//+------------------------------------------------------------------+
void LogFinalStatistics()
{
double finalBalance = AccountInfoDouble(ACCOUNT_BALANCE);
double totalProfit = finalBalance - g_StartingBalance;
double profitPercent = (g_StartingBalance > 0) ?
(totalProfit / g_StartingBalance) * 100 : 0;
CLogger::Log(LOG_INFO, "===========================================");
CLogger::Log(LOG_INFO, "FINAL STATISTICS");
CLogger::Log(LOG_INFO, "===========================================");
CLogger::Log(LOG_INFO, StringFormat("Starting Balance: %.2f", g_StartingBalance));
CLogger::Log(LOG_INFO, StringFormat("Final Balance: %.2f", finalBalance));
CLogger::Log(LOG_INFO, StringFormat("Total Profit/Loss: %.2f (%.2f%%)",
totalProfit, profitPercent));
CLogger::Log(LOG_INFO, StringFormat("Total Trades: %d", g_TotalTrades));
CLogger::Log(LOG_INFO, StringFormat("Winning Trades: %d", g_WinningTrades));
CLogger::Log(LOG_INFO, StringFormat("Losing Trades: %d", g_LosingTrades));
CLogger::Log(LOG_INFO, StringFormat("Win Rate: %.2f%%", g_Performance.winRate));
CLogger::Log(LOG_INFO, StringFormat("Max Drawdown: %.2f%%",
g_Performance.maxDrawdownPercent));
CLogger::Log(LOG_INFO, "===========================================");
}