863 lines
31 KiB
MQL5
863 lines
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
|