Overview To fully integrate the enhanced external trade management system, updates are required to 5 out of 7 existing modules. The updates maintain backward compatibility while adding new functionality for external trade handling. Module Update Requirements 🟢 No Updates Required (2 modules) TechnicalAnalysis.mqh - Already provides necessary calculations EntrySystem.mqh - Only handles EA's own entry signals 🟡 Minor Updates (2 modules) DataTypes.mqh - Add external trade structures and fields Utilities.mqh - Enhanced logging for external trades 🟠 Moderate Updates (3 modules) RiskManager.mqh - Enhanced risk enforcement methods TradeManager.mqh - Improved stop management for externals Dashboard.mqh - Display external trade information Integration Steps Phase 1: Data Structures (DataTypes.mqh) Add ENUM_EXTERNAL_STATUS enumeration Extend ManagedTrade structure with external-specific fields Add ExternalTradeStats structure for metrics Update DashboardConfig with show_external flag Key additions: external_status - Track state of external trade source_name - Identify where trade came from stops_modified - Track if we modified the trade original_sl/tp - Store original values for comparison Phase 2: Risk Management (RiskManager.mqh) Add EnforceRiskRulesEnhanced() method Implement GetExternalExposure() for risk aggregation Add UpdateExternalStats() for tracking Enhance ValidateAndAdjustRiskExternal() method Key features: Separate risk calculation for external trades Cache mechanism for performance Statistical tracking of external positions Smart risk adjustment without closing trades Phase 3: Trade Management (TradeManager.mqh) Add ApplyDefaultStopsEnhanced() with better logic Implement OverrideExternalStops() with smart override Create ManageExternalTrade() with different rules Add ApplyBreakevenExternal() with wider triggers Key features: Smart stop override (only improve, never worsen) Different management rules for external trades Respect minimum broker distances Track modification success/failure rates Phase 4: User Interface (Dashboard.mqh) Add CreateExternalSection() for display area Implement UpdateExternalSection() for real-time updates Add SetCustomText() for flexible display Create ShowExternalTrades() toggle method Key features: Real-time external trade count and risk Color-coded risk warnings List of active external positions Modification statistics display Phase 5: Logging (Utilities.mqh) Add LogExternalTrade() for detailed event logging Create separate CSV log for external trades Enhance GenerateReportEnhanced() with external section Add IdentifyTradeSource() for magic number interpretation Key features: Separate CSV log for external trade events Detailed tracking of all modifications Source identification from magic numbers Enhanced reporting with external statistics
863 lines
No EOL
31 KiB
MQL5
863 lines
No EOL
31 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Utilities_v71.mqh |
|
|
//| Enhanced Utilities Module v7.1 |
|
|
//| Optimized Logging, Performance Tools, Helpers |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef UTILITIES_V71_MQH
|
|
#define UTILITIES_V71_MQH
|
|
|
|
#include "DataTypes_v71.mqh"
|
|
#include <Files/FileTxt.mqh>
|
|
#include <Files/FileBin.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Performance Timer |
|
|
//+------------------------------------------------------------------+
|
|
struct PerformanceTimer
|
|
{
|
|
string name;
|
|
ulong start_time;
|
|
ulong total_time;
|
|
int call_count;
|
|
double avg_time_ms;
|
|
double max_time_ms;
|
|
double min_time_ms;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Log Entry |
|
|
//+------------------------------------------------------------------+
|
|
struct LogEntry
|
|
{
|
|
datetime timestamp;
|
|
ENUM_LOG_LEVEL level;
|
|
string source;
|
|
string message;
|
|
int error_code;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Utilities Class - Enhanced Version |
|
|
//+------------------------------------------------------------------+
|
|
class CUtilitiesV71
|
|
{
|
|
private:
|
|
//--- Configuration
|
|
ENUM_LOG_LEVEL m_log_level;
|
|
bool m_is_testing;
|
|
bool m_ultra_low_latency;
|
|
string m_log_filename;
|
|
string m_report_filename;
|
|
|
|
//--- Performance monitoring
|
|
PerformanceTimer m_timers[];
|
|
int m_timer_count;
|
|
|
|
//--- Log buffer for low latency mode
|
|
LogEntry m_log_buffer[];
|
|
int m_log_buffer_size;
|
|
int m_log_write_index;
|
|
bool m_buffered_logging;
|
|
|
|
//--- File handles
|
|
int m_log_handle;
|
|
int m_report_handle;
|
|
|
|
//--- Memory pool for string operations
|
|
string m_string_pool[];
|
|
int m_string_pool_size;
|
|
|
|
//--- Helper methods
|
|
string GetLogPrefix(ENUM_LOG_LEVEL level);
|
|
void WriteLogEntry(const LogEntry &entry);
|
|
void FlushLogBuffer();
|
|
|
|
public:
|
|
CUtilitiesV71();
|
|
~CUtilitiesV71();
|
|
|
|
//--- Initialization
|
|
bool Initialize(ENUM_LOG_LEVEL level);
|
|
bool InitializeOptimized(ENUM_LOG_LEVEL level, bool ultra_low_latency);
|
|
|
|
//--- Logging with minimal overhead
|
|
void Log(ENUM_LOG_LEVEL level, string message);
|
|
void LogOptimized(ENUM_LOG_LEVEL level, string message);
|
|
void LogError(string function, int error_code);
|
|
void LogTrade(const ManagedTradeV71 &trade, string action);
|
|
void LogPerformance(string operation, double time_ms);
|
|
|
|
//--- Performance monitoring
|
|
void StartTimer(string name);
|
|
void StopTimer(string name);
|
|
double GetAverageTime(string name);
|
|
void ReportTimings();
|
|
|
|
//--- Terminal and connection checks
|
|
bool CheckTerminalConnection();
|
|
bool CheckAutoTradingEnabled();
|
|
bool IsTradingAllowed();
|
|
bool IsMarketClosed(string symbol);
|
|
|
|
//--- Time utilities
|
|
bool IsAsianSession();
|
|
bool IsEuropeanSession();
|
|
bool IsAmericanSession();
|
|
bool IsNewsTime(string symbol);
|
|
int GetMarketSession();
|
|
|
|
//--- Mathematical utilities
|
|
double NormalizeVolume(string symbol, double volume);
|
|
double NormalizePricepredictable(string symbol, double price);
|
|
double CalculateCommission(ulong ticket);
|
|
double CalculateCommissionOptimized(ulong ticket);
|
|
double GetSpreadCost(string symbol, double volume);
|
|
|
|
//--- String utilities optimized
|
|
string FormatCurrency(double value);
|
|
string FormatPercentage(double value, int digits = 2);
|
|
string FormatDuration(int seconds);
|
|
string GetUninitReasonText(int reason);
|
|
|
|
//--- File operations
|
|
void GenerateReport(PerformanceMetricsV71 &performance,
|
|
ManagedTradeV71 &trades[]);
|
|
void SaveTradeHistory(const ManagedTradeV71 &trade);
|
|
void SaveTradeHistoryOptimized(const ManagedTradeV71 &trade);
|
|
bool LoadConfiguration(string filename);
|
|
bool SaveConfiguration(string filename);
|
|
|
|
//--- Data export
|
|
void ExportToCSV(string filename, ManagedTradeV71 &trades[]);
|
|
void ExportPerformanceMetrics(string filename, PerformanceMetricsV71 &metrics);
|
|
void ExportRiskMetrics(string filename, SystemStateV71 &state);
|
|
|
|
//--- System utilities
|
|
void PrintSystemInfo();
|
|
int GetCPUCores();
|
|
long GetAvailableMemory();
|
|
double GetCPUUsage();
|
|
|
|
//--- Market data utilities
|
|
double GetSymbolPointValue(string symbol);
|
|
double GetSymbolTickValue(string symbol, double volume = 1.0);
|
|
int GetSymbolDigits(string symbol);
|
|
double ConvertToAccountCurrency(string symbol, double amount);
|
|
|
|
//--- Array utilities (optimized)
|
|
template<typename T>
|
|
void ArrayShuffle(T &array[]);
|
|
template<typename T>
|
|
int ArrayFindMax(const T &array[]);
|
|
template<typename T>
|
|
int ArrayFindMin(const T &array[]);
|
|
template<typename T>
|
|
double ArrayMean(const T &array[]);
|
|
template<typename T>
|
|
double ArrayStdDev(const T &array[]);
|
|
|
|
//--- Notification utilities
|
|
void SendNotification(string title, string message);
|
|
void SendEmail(string subject, string body);
|
|
void PlayAlert(string sound_file);
|
|
|
|
//--- Cleanup
|
|
void FlushLogs();
|
|
void CloseAllFiles();
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CUtilitiesV71::CUtilitiesV71()
|
|
{
|
|
m_log_level = LOG_INFO;
|
|
m_is_testing = MQLInfoInteger(MQL_TESTER);
|
|
m_ultra_low_latency = false;
|
|
m_log_filename = "ERMT71_Log.txt";
|
|
m_report_filename = "ERMT71_Report.txt";
|
|
m_timer_count = 0;
|
|
m_log_buffer_size = 1000;
|
|
m_log_write_index = 0;
|
|
m_buffered_logging = false;
|
|
m_log_handle = INVALID_HANDLE;
|
|
m_report_handle = INVALID_HANDLE;
|
|
m_string_pool_size = 100;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
CUtilitiesV71::~CUtilitiesV71()
|
|
{
|
|
FlushLogs();
|
|
CloseAllFiles();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize with optimization |
|
|
//+------------------------------------------------------------------+
|
|
bool CUtilitiesV71::InitializeOptimized(ENUM_LOG_LEVEL level, bool ultra_low_latency)
|
|
{
|
|
m_log_level = level;
|
|
m_ultra_low_latency = ultra_low_latency;
|
|
|
|
//--- Set up buffered logging for low latency
|
|
if(m_ultra_low_latency)
|
|
{
|
|
m_buffered_logging = true;
|
|
ArrayResize(m_log_buffer, m_log_buffer_size);
|
|
ArrayResize(m_string_pool, m_string_pool_size);
|
|
}
|
|
|
|
//--- Initialize performance timers
|
|
ArrayResize(m_timers, 20);
|
|
|
|
//--- Open log file if not testing
|
|
if(!m_is_testing && !m_ultra_low_latency)
|
|
{
|
|
m_log_handle = FileOpen(m_log_filename, FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_SHARE_READ);
|
|
}
|
|
|
|
Log(LOG_INFO, "UtilitiesV71 initialized: UltraLowLatency=" + (string)m_ultra_low_latency);
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Optimized logging for low latency |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::LogOptimized(ENUM_LOG_LEVEL level, string message)
|
|
{
|
|
if(level < m_log_level) return;
|
|
|
|
if(m_buffered_logging)
|
|
{
|
|
//--- Add to buffer
|
|
LogEntry entry;
|
|
entry.timestamp = TimeCurrent();
|
|
entry.level = level;
|
|
entry.source = "ERMT71";
|
|
entry.message = message;
|
|
entry.error_code = 0;
|
|
|
|
m_log_buffer[m_log_write_index] = entry;
|
|
m_log_write_index = (m_log_write_index + 1) % m_log_buffer_size;
|
|
|
|
//--- Flush buffer if critical
|
|
if(level >= LOG_ERROR)
|
|
FlushLogBuffer();
|
|
}
|
|
else
|
|
{
|
|
Log(level, message);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Standard logging |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::Log(ENUM_LOG_LEVEL level, string message)
|
|
{
|
|
if(level < m_log_level) return;
|
|
|
|
string log_entry = TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + " " +
|
|
GetLogPrefix(level) + " " + message;
|
|
|
|
//--- Print to terminal
|
|
if(level >= LOG_WARNING)
|
|
Print(log_entry);
|
|
|
|
//--- Write to file if not testing
|
|
if(!m_is_testing && m_log_handle != INVALID_HANDLE)
|
|
{
|
|
FileWrite(m_log_handle, log_entry);
|
|
FileFlush(m_log_handle);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get log level prefix |
|
|
//+------------------------------------------------------------------+
|
|
string CUtilitiesV71::GetLogPrefix(ENUM_LOG_LEVEL level)
|
|
{
|
|
switch(level)
|
|
{
|
|
case LOG_DEBUG: return "[DEBUG]";
|
|
case LOG_INFO: return "[INFO ]";
|
|
case LOG_WARNING: return "[WARN ]";
|
|
case LOG_ERROR: return "[ERROR]";
|
|
case LOG_CRITICAL: return "[CRIT ]";
|
|
default: return "[?????]";
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Flush log buffer to file |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::FlushLogBuffer()
|
|
{
|
|
if(!m_buffered_logging || m_is_testing) return;
|
|
|
|
//--- Open file if needed
|
|
if(m_log_handle == INVALID_HANDLE)
|
|
{
|
|
m_log_handle = FileOpen(m_log_filename, FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_SHARE_READ);
|
|
if(m_log_handle == INVALID_HANDLE) return;
|
|
}
|
|
|
|
//--- Write all buffered entries
|
|
for(int i = 0; i < m_log_buffer_size; i++)
|
|
{
|
|
if(m_log_buffer[i].timestamp > 0)
|
|
{
|
|
WriteLogEntry(m_log_buffer[i]);
|
|
m_log_buffer[i].timestamp = 0; // Clear entry
|
|
}
|
|
}
|
|
|
|
FileFlush(m_log_handle);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Write single log entry |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::WriteLogEntry(const LogEntry &entry)
|
|
{
|
|
if(m_log_handle == INVALID_HANDLE) return;
|
|
|
|
string log_line = TimeToString(entry.timestamp, TIME_DATE|TIME_SECONDS) + " " +
|
|
GetLogPrefix(entry.level) + " [" + entry.source + "] " +
|
|
entry.message;
|
|
|
|
if(entry.error_code != 0)
|
|
log_line += " (Error: " + IntegerToString(entry.error_code) + ")";
|
|
|
|
FileWrite(m_log_handle, log_line);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Start performance timer |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::StartTimer(string name)
|
|
{
|
|
//--- Find existing timer
|
|
int index = -1;
|
|
for(int i = 0; i < m_timer_count; i++)
|
|
{
|
|
if(m_timers[i].name == name)
|
|
{
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//--- Create new timer if not found
|
|
if(index < 0)
|
|
{
|
|
if(m_timer_count >= ArraySize(m_timers))
|
|
ArrayResize(m_timers, m_timer_count + 10);
|
|
|
|
index = m_timer_count;
|
|
m_timers[index].name = name;
|
|
m_timers[index].total_time = 0;
|
|
m_timers[index].call_count = 0;
|
|
m_timers[index].avg_time_ms = 0;
|
|
m_timers[index].max_time_ms = 0;
|
|
m_timers[index].min_time_ms = DBL_MAX;
|
|
m_timer_count++;
|
|
}
|
|
|
|
//--- Start timing
|
|
m_timers[index].start_time = GetMicrosecondCount();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Stop performance timer |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::StopTimer(string name)
|
|
{
|
|
//--- Find timer
|
|
for(int i = 0; i < m_timer_count; i++)
|
|
{
|
|
if(m_timers[i].name == name)
|
|
{
|
|
//--- Calculate elapsed time
|
|
ulong elapsed = GetMicrosecondCount() - m_timers[i].start_time;
|
|
double elapsed_ms = elapsed / 1000.0;
|
|
|
|
//--- Update statistics
|
|
m_timers[i].total_time += elapsed;
|
|
m_timers[i].call_count++;
|
|
m_timers[i].avg_time_ms = (m_timers[i].total_time / 1000.0) / m_timers[i].call_count;
|
|
|
|
if(elapsed_ms > m_timers[i].max_time_ms)
|
|
m_timers[i].max_time_ms = elapsed_ms;
|
|
|
|
if(elapsed_ms < m_timers[i].min_time_ms)
|
|
m_timers[i].min_time_ms = elapsed_ms;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get average execution time |
|
|
//+------------------------------------------------------------------+
|
|
double CUtilitiesV71::GetAverageTime(string name)
|
|
{
|
|
for(int i = 0; i < m_timer_count; i++)
|
|
{
|
|
if(m_timers[i].name == name)
|
|
return m_timers[i].avg_time_ms;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Report all timings |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::ReportTimings()
|
|
{
|
|
Log(LOG_INFO, "=== Performance Report ===");
|
|
|
|
for(int i = 0; i < m_timer_count; i++)
|
|
{
|
|
string report = StringFormat("%s: Calls=%d, Avg=%.3fms, Min=%.3fms, Max=%.3fms",
|
|
m_timers[i].name,
|
|
m_timers[i].call_count,
|
|
m_timers[i].avg_time_ms,
|
|
m_timers[i].min_time_ms,
|
|
m_timers[i].max_time_ms);
|
|
Log(LOG_INFO, report);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check terminal connection |
|
|
//+------------------------------------------------------------------+
|
|
bool CUtilitiesV71::CheckTerminalConnection()
|
|
{
|
|
if(!TerminalInfoInteger(TERMINAL_CONNECTED))
|
|
{
|
|
LogOptimized(LOG_WARNING, "Terminal not connected");
|
|
return false;
|
|
}
|
|
|
|
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
|
|
{
|
|
LogOptimized(LOG_WARNING, "Trading not allowed in terminal");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if auto trading is enabled |
|
|
//+------------------------------------------------------------------+
|
|
bool CUtilitiesV71::CheckAutoTradingEnabled()
|
|
{
|
|
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
|
|
return false;
|
|
|
|
if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
|
|
return false;
|
|
|
|
if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
|
|
return false;
|
|
|
|
if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate position commission optimized |
|
|
//+------------------------------------------------------------------+
|
|
double CUtilitiesV71::CalculateCommissionOptimized(ulong ticket)
|
|
{
|
|
static ulong last_ticket = 0;
|
|
static double last_commission = 0;
|
|
|
|
//--- Cache hit
|
|
if(ticket == last_ticket)
|
|
return last_commission;
|
|
|
|
double commission = 0;
|
|
|
|
//--- Get deals for position
|
|
if(HistorySelectByPosition(ticket))
|
|
{
|
|
int deals = HistoryDealsTotal();
|
|
|
|
for(int i = 0; i < deals; i++)
|
|
{
|
|
ulong deal_ticket = HistoryDealGetTicket(i);
|
|
if(deal_ticket > 0)
|
|
{
|
|
commission += HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION);
|
|
}
|
|
}
|
|
}
|
|
|
|
//--- Update cache
|
|
last_ticket = ticket;
|
|
last_commission = commission;
|
|
|
|
return commission;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Format currency with optimization |
|
|
//+------------------------------------------------------------------+
|
|
string CUtilitiesV71::FormatCurrency(double value)
|
|
{
|
|
//--- Use string pool for common values
|
|
if(value == 0) return "0.00";
|
|
|
|
string sign = (value < 0) ? "-" : "";
|
|
value = MathAbs(value);
|
|
|
|
if(value >= 1000000)
|
|
return sign + DoubleToString(value/1000000, 2) + "M";
|
|
else if(value >= 1000)
|
|
return sign + DoubleToString(value/1000, 2) + "K";
|
|
else
|
|
return sign + DoubleToString(value, 2);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get market session |
|
|
//+------------------------------------------------------------------+
|
|
int CUtilitiesV71::GetMarketSession()
|
|
{
|
|
MqlDateTime time;
|
|
TimeToStruct(TimeCurrent(), time);
|
|
int hour = time.hour;
|
|
|
|
//--- Asian session (Tokyo: 00:00 - 09:00 GMT)
|
|
if(hour >= 0 && hour < 9)
|
|
return 1;
|
|
|
|
//--- European session (London: 08:00 - 17:00 GMT)
|
|
if(hour >= 8 && hour < 17)
|
|
return 2;
|
|
|
|
//--- American session (New York: 13:00 - 22:00 GMT)
|
|
if(hour >= 13 && hour < 22)
|
|
return 3;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Generate performance report |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::GenerateReport(PerformanceMetricsV71 &performance,
|
|
ManagedTradeV71 &trades[])
|
|
{
|
|
string report = "\n";
|
|
report += "════════════════════════════════════════════════════════════════\n";
|
|
report += " ERMT 7.1 PERFORMANCE REPORT \n";
|
|
report += "════════════════════════════════════════════════════════════════\n\n";
|
|
|
|
//--- Account Summary
|
|
report += "ACCOUNT SUMMARY\n";
|
|
report += "───────────────\n";
|
|
report += "Starting Balance: " + FormatCurrency(AccountInfoDouble(ACCOUNT_BALANCE) - performance.net_profit) + "\n";
|
|
report += "Current Balance: " + FormatCurrency(AccountInfoDouble(ACCOUNT_BALANCE)) + "\n";
|
|
report += "Net Profit: " + FormatCurrency(performance.net_profit) + "\n";
|
|
report += "Return: " + FormatPercentage((performance.net_profit / (AccountInfoDouble(ACCOUNT_BALANCE) - performance.net_profit)) * 100) + "\n\n";
|
|
|
|
//--- Performance Metrics
|
|
report += "PERFORMANCE METRICS\n";
|
|
report += "──────────────────\n";
|
|
report += "Total Trades: " + IntegerToString(performance.total_trades) + "\n";
|
|
report += "Win Rate: " + FormatPercentage(performance.win_rate) + "\n";
|
|
report += "Profit Factor: " + DoubleToString(performance.profit_factor, 2) + "\n";
|
|
report += "Sharpe Ratio: " + DoubleToString(performance.sharpe_ratio, 2) + "\n";
|
|
report += "Sortino Ratio: " + DoubleToString(performance.sortino_ratio, 2) + "\n";
|
|
report += "Information Ratio: " + DoubleToString(performance.information_ratio, 2) + "\n\n";
|
|
|
|
//--- Risk Metrics
|
|
report += "RISK METRICS\n";
|
|
report += "────────────\n";
|
|
report += "Max Drawdown: " + FormatPercentage(performance.max_drawdown_percent) + "\n";
|
|
report += "VaR (95%): " + FormatPercentage(performance.var_95) + "\n";
|
|
report += "CVaR (95%): " + FormatPercentage(performance.cvar_95) + "\n";
|
|
report += "Recovery Factor: " + DoubleToString(performance.recovery_factor, 2) + "\n";
|
|
report += "Calmar Ratio: " + DoubleToString(performance.calmar_ratio, 2) + "\n\n";
|
|
|
|
//--- Advanced Metrics
|
|
report += "ADVANCED METRICS\n";
|
|
report += "───────────────\n";
|
|
report += "Alpha: " + DoubleToString(performance.alpha, 4) + "\n";
|
|
report += "Beta: " + DoubleToString(performance.beta, 2) + "\n";
|
|
report += "Treynor Ratio: " + DoubleToString(performance.treynor_ratio, 2) + "\n";
|
|
report += "Omega Ratio: " + DoubleToString(performance.omega_ratio, 2) + "\n";
|
|
report += "Hit Rate: " + FormatPercentage(performance.hit_rate) + "\n";
|
|
report += "Avg Holding Time: " + FormatDuration((int)performance.avg_holding_time) + "\n\n";
|
|
|
|
//--- Trade Analysis
|
|
report += "TRADE ANALYSIS\n";
|
|
report += "─────────────\n";
|
|
report += "Winning Trades: " + IntegerToString(performance.winning_trades) + "\n";
|
|
report += "Losing Trades: " + IntegerToString(performance.losing_trades) + "\n";
|
|
report += "Average Win: " + FormatCurrency(performance.avg_win) + "\n";
|
|
report += "Average Loss: " + FormatCurrency(performance.avg_loss) + "\n";
|
|
report += "Largest Win: " + FormatCurrency(performance.largest_win) + "\n";
|
|
report += "Largest Loss: " + FormatCurrency(performance.largest_loss) + "\n";
|
|
report += "Max Consec Wins: " + IntegerToString(performance.max_consecutive_wins) + "\n";
|
|
report += "Max Consec Losses: " + IntegerToString(performance.max_consecutive_losses) + "\n\n";
|
|
|
|
//--- Current Positions
|
|
int trade_count = ArraySize(trades);
|
|
if(trade_count > 0)
|
|
{
|
|
report += "CURRENT POSITIONS\n";
|
|
report += "────────────────\n";
|
|
report += "Symbol Type Volume Entry Current P/L Risk% R-Mult\n";
|
|
report += "─────────────────────────────────────────────────────────────────\n";
|
|
|
|
for(int i = 0; i < trade_count && i < 10; i++)
|
|
{
|
|
double current_price = (trades[i].type == POSITION_TYPE_BUY) ?
|
|
SymbolInfoDouble(trades[i].symbol, SYMBOL_BID) :
|
|
SymbolInfoDouble(trades[i].symbol, SYMBOL_ASK);
|
|
|
|
report += StringFormat("%-8s %-5s %6.2f %7.5f %7.5f %-8s %5.1f %6.2f\n",
|
|
trades[i].symbol,
|
|
(trades[i].type == POSITION_TYPE_BUY) ? "BUY" : "SELL",
|
|
trades[i].volume,
|
|
trades[i].open_price,
|
|
current_price,
|
|
FormatCurrency(trades[i].profit),
|
|
trades[i].risk_percent,
|
|
trades[i].r_multiple);
|
|
}
|
|
report += "\n";
|
|
}
|
|
|
|
//--- Performance timings
|
|
if(m_timer_count > 0)
|
|
{
|
|
report += "PERFORMANCE TIMINGS\n";
|
|
report += "──────────────────\n";
|
|
for(int i = 0; i < m_timer_count && i < 5; i++)
|
|
{
|
|
report += StringFormat("%-20s: %6.3f ms (avg), %d calls\n",
|
|
m_timers[i].name,
|
|
m_timers[i].avg_time_ms,
|
|
m_timers[i].call_count);
|
|
}
|
|
report += "\n";
|
|
}
|
|
|
|
report += "════════════════════════════════════════════════════════════════\n";
|
|
report += "Report generated: " + TimeToString(TimeCurrent()) + "\n";
|
|
report += "════════════════════════════════════════════════════════════════\n";
|
|
|
|
//--- Output report
|
|
if(m_is_testing)
|
|
{
|
|
Print(report);
|
|
}
|
|
else
|
|
{
|
|
//--- Save to file
|
|
int handle = FileOpen(m_report_filename, FILE_WRITE|FILE_TXT|FILE_ANSI);
|
|
if(handle != INVALID_HANDLE)
|
|
{
|
|
FileWrite(handle, report);
|
|
FileClose(handle);
|
|
Log(LOG_INFO, "Performance report saved to " + m_report_filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Save trade history optimized |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::SaveTradeHistoryOptimized(const ManagedTradeV71 &trade)
|
|
{
|
|
if(m_is_testing) return;
|
|
|
|
string filename = "TradeHistory_" + trade.symbol + ".csv";
|
|
|
|
//--- Check if file exists
|
|
bool file_exists = FileIsExist(filename);
|
|
|
|
int handle = FileOpen(filename, FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_SHARE_READ, ',');
|
|
if(handle == INVALID_HANDLE) return;
|
|
|
|
//--- Write header if new file
|
|
if(!file_exists)
|
|
{
|
|
FileWrite(handle, "Ticket", "Symbol", "Type", "OpenTime", "CloseTime",
|
|
"OpenPrice", "ClosePrice", "Volume", "Profit", "Commission",
|
|
"Swap", "RMultiple", "MAE", "MFE", "VaR", "MLConfidence",
|
|
"ExecutionQuality", "EntryAlgo", "Comment");
|
|
}
|
|
|
|
//--- Move to end
|
|
FileSeek(handle, 0, SEEK_END);
|
|
|
|
//--- Write trade data
|
|
FileWrite(handle,
|
|
trade.ticket,
|
|
trade.symbol,
|
|
(trade.type == POSITION_TYPE_BUY) ? "BUY" : "SELL",
|
|
TimeToString(trade.open_time),
|
|
TimeToString(TimeCurrent()),
|
|
trade.open_price,
|
|
(trade.type == POSITION_TYPE_BUY) ?
|
|
SymbolInfoDouble(trade.symbol, SYMBOL_BID) :
|
|
SymbolInfoDouble(trade.symbol, SYMBOL_ASK),
|
|
trade.volume,
|
|
trade.profit,
|
|
trade.commission,
|
|
trade.swap,
|
|
trade.r_multiple,
|
|
trade.mae,
|
|
trade.mfe,
|
|
trade.var_contribution,
|
|
trade.ml_confidence,
|
|
trade.execution_quality,
|
|
trade.entry_algo,
|
|
trade.comment);
|
|
|
|
FileClose(handle);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Format percentage |
|
|
//+------------------------------------------------------------------+
|
|
string CUtilitiesV71::FormatPercentage(double value, int digits)
|
|
{
|
|
return DoubleToString(value, digits) + "%";
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Format duration |
|
|
//+------------------------------------------------------------------+
|
|
string CUtilitiesV71::FormatDuration(int seconds)
|
|
{
|
|
if(seconds < 60)
|
|
return IntegerToString(seconds) + "s";
|
|
else if(seconds < 3600)
|
|
return IntegerToString(seconds/60) + "m " + IntegerToString(seconds%60) + "s";
|
|
else if(seconds < 86400)
|
|
return IntegerToString(seconds/3600) + "h " + IntegerToString((seconds%3600)/60) + "m";
|
|
else
|
|
return IntegerToString(seconds/86400) + "d " + IntegerToString((seconds%86400)/3600) + "h";
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Print system information |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::PrintSystemInfo()
|
|
{
|
|
Print("=== SYSTEM INFORMATION ===");
|
|
Print("Terminal Build: ", TerminalInfoInteger(TERMINAL_BUILD));
|
|
Print("CPU Cores: ", GetCPUCores());
|
|
Print("Available Memory: ", GetAvailableMemory() / (1024*1024), " MB");
|
|
Print("Screen DPI: ", TerminalInfoInteger(TERMINAL_SCREEN_DPI));
|
|
Print("Max Bars: ", TerminalInfoInteger(TERMINAL_MAXBARS));
|
|
Print("Code Page: ", TerminalInfoInteger(TERMINAL_CODEPAGE));
|
|
Print("Community Account: ", TerminalInfoInteger(TERMINAL_COMMUNITY_ACCOUNT) ? "Yes" : "No");
|
|
Print("Connected: ", TerminalInfoInteger(TERMINAL_CONNECTED) ? "Yes" : "No");
|
|
Print("Trade Allowed: ", TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) ? "Yes" : "No");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get CPU cores |
|
|
//+------------------------------------------------------------------+
|
|
int CUtilitiesV71::GetCPUCores()
|
|
{
|
|
return (int)TerminalInfoInteger(TERMINAL_CPU_CORES);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get available memory |
|
|
//+------------------------------------------------------------------+
|
|
long CUtilitiesV71::GetAvailableMemory()
|
|
{
|
|
return TerminalInfoInteger(TERMINAL_MEMORY_AVAILABLE);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Array mean calculation |
|
|
//+------------------------------------------------------------------+
|
|
template<typename T>
|
|
double CUtilitiesV71::ArrayMean(const T &array[])
|
|
{
|
|
int size = ArraySize(array);
|
|
if(size == 0) return 0;
|
|
|
|
double sum = 0;
|
|
for(int i = 0; i < size; i++)
|
|
sum += array[i];
|
|
|
|
return sum / size;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Array standard deviation |
|
|
//+------------------------------------------------------------------+
|
|
template<typename T>
|
|
double CUtilitiesV71::ArrayStdDev(const T &array[])
|
|
{
|
|
int size = ArraySize(array);
|
|
if(size <= 1) return 0;
|
|
|
|
double mean = ArrayMean(array);
|
|
double sum_sq = 0;
|
|
|
|
for(int i = 0; i < size; i++)
|
|
sum_sq += MathPow(array[i] - mean, 2);
|
|
|
|
return MathSqrt(sum_sq / (size - 1));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close all files |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::CloseAllFiles()
|
|
{
|
|
if(m_log_handle != INVALID_HANDLE)
|
|
{
|
|
FileClose(m_log_handle);
|
|
m_log_handle = INVALID_HANDLE;
|
|
}
|
|
|
|
if(m_report_handle != INVALID_HANDLE)
|
|
{
|
|
FileClose(m_report_handle);
|
|
m_report_handle = INVALID_HANDLE;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Flush all logs |
|
|
//+------------------------------------------------------------------+
|
|
void CUtilitiesV71::FlushLogs()
|
|
{
|
|
if(m_buffered_logging)
|
|
FlushLogBuffer();
|
|
|
|
if(m_log_handle != INVALID_HANDLE)
|
|
FileFlush(m_log_handle);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Standard initialization (compatibility) |
|
|
//+------------------------------------------------------------------+
|
|
bool CUtilitiesV71::Initialize(ENUM_LOG_LEVEL level)
|
|
{
|
|
return InitializeOptimized(level, false);
|
|
}
|
|
|
|
#endif // UTILITIES_V71_MQH |