mql5/Experts/Advisors/Modules/Utilities.mqh
2025-09-01 16:55:52 +01:00

378 lines
No EOL
15 KiB
MQL5

//+------------------------------------------------------------------+
//| Utilities.mqh |
//| Utility Functions Module 2.4 |
//| Fixed: Remove Duplicates & Add Missing Members |
//+------------------------------------------------------------------+
#ifndef UTILITIES_MQH
#define UTILITIES_MQH
#include "DataTypes.mqh" // Include DataTypes first for all type definitions
//+------------------------------------------------------------------+
//| Logger Class |
//+------------------------------------------------------------------+
class CLogger
{
private:
static ENUM_LOG_LEVEL m_LogLevel;
static string m_LogFile;
static bool m_FileLogging;
static bool m_TerminalLogging;
static int m_FileHandle;
public:
//+------------------------------------------------------------------+
//| Initialize logger |
//+------------------------------------------------------------------+
static void Init(ENUM_LOG_LEVEL level = LOG_INFO, bool fileLogging = true, bool terminalLogging = true)
{
m_LogLevel = level;
m_FileLogging = fileLogging;
m_TerminalLogging = terminalLogging;
if(m_FileLogging)
{
string filename = "ERMT_" + TimeToString(TimeCurrent(), TIME_DATE) + ".log";
m_LogFile = filename;
m_FileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI);
}
}
//+------------------------------------------------------------------+
//| Deinitialize logger |
//+------------------------------------------------------------------+
static void Deinit()
{
if(m_FileHandle != INVALID_HANDLE)
{
FileClose(m_FileHandle);
m_FileHandle = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+
//| Log message |
//+------------------------------------------------------------------+
static void Log(ENUM_LOG_LEVEL level, string message)
{
if(level < m_LogLevel) return;
string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS);
string levelStr = LogLevelToString(level);
string logMessage = StringFormat("[%s] [%s] %s", timestamp, levelStr, message);
if(m_TerminalLogging)
Print(logMessage);
if(m_FileLogging && m_FileHandle != INVALID_HANDLE)
{
FileWriteString(m_FileHandle, logMessage + "\n");
FileFlush(m_FileHandle);
}
}
private:
//+------------------------------------------------------------------+
//| Convert log level to string |
//+------------------------------------------------------------------+
static string LogLevelToString(ENUM_LOG_LEVEL level)
{
switch(level)
{
case LOG_ERROR: return "ERROR";
case LOG_WARNING: return "WARNING";
case LOG_INFO: return "INFO";
case LOG_DEBUG: return "DEBUG";
default: return "UNKNOWN";
}
}
};
// Static member initialization
ENUM_LOG_LEVEL CLogger::m_LogLevel = LOG_INFO;
string CLogger::m_LogFile = "";
bool CLogger::m_FileLogging = false;
bool CLogger::m_TerminalLogging = true;
int CLogger::m_FileHandle = INVALID_HANDLE;
//+------------------------------------------------------------------+
//| Utilities Class |
//+------------------------------------------------------------------+
class CUtilities
{
public:
//+------------------------------------------------------------------+
//| Convert order type to string |
//+------------------------------------------------------------------+
static string OrderTypeToString(ENUM_ORDER_TYPE type)
{
switch(type)
{
case ORDER_TYPE_BUY: return "BUY";
case ORDER_TYPE_SELL: return "SELL";
case ORDER_TYPE_BUY_LIMIT: return "BUY_LIMIT";
case ORDER_TYPE_SELL_LIMIT: return "SELL_LIMIT";
case ORDER_TYPE_BUY_STOP: return "BUY_STOP";
case ORDER_TYPE_SELL_STOP: return "SELL_STOP";
case ORDER_TYPE_BUY_STOP_LIMIT: return "BUY_STOP_LIMIT";
case ORDER_TYPE_SELL_STOP_LIMIT: return "SELL_STOP_LIMIT";
default: return "UNKNOWN";
}
}
//+------------------------------------------------------------------+
//| Format currency value |
//+------------------------------------------------------------------+
static string FormatCurrency(double value, string currency = "")
{
if(currency == "")
currency = AccountInfoString(ACCOUNT_CURRENCY);
return StringFormat("%s %.2f", currency, value);
}
//+------------------------------------------------------------------+
//| Format percentage |
//+------------------------------------------------------------------+
static string FormatPercent(double value)
{
return StringFormat("%.2f%%", value);
}
//+------------------------------------------------------------------+
//| Get time frame string |
//+------------------------------------------------------------------+
static string TimeFrameToString(ENUM_TIMEFRAMES tf)
{
switch(tf)
{
case PERIOD_M1: return "M1";
case PERIOD_M5: return "M5";
case PERIOD_M15: return "M15";
case PERIOD_M30: return "M30";
case PERIOD_H1: return "H1";
case PERIOD_H4: return "H4";
case PERIOD_D1: return "D1";
case PERIOD_W1: return "W1";
case PERIOD_MN1: return "MN1";
default: return "UNKNOWN";
}
}
//+------------------------------------------------------------------+
//| Calculate lot size based on risk |
//+------------------------------------------------------------------+
static double CalculateLotSize(double riskAmount, double stopLossPoints, string symbol = NULL)
{
if(symbol == NULL) symbol = _Symbol;
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if(stopLossPoints <= 0 || tickSize <= 0) return minLot;
double lotSize = (riskAmount * tickSize) / (stopLossPoints * tickValue);
// Round to lot step
lotSize = MathFloor(lotSize / lotStep) * lotStep;
// Apply limits
if(lotSize < minLot) lotSize = minLot;
if(lotSize > maxLot) lotSize = maxLot;
return NormalizeDouble(lotSize, 2);
}
//+------------------------------------------------------------------+
//| Log trade info |
//+------------------------------------------------------------------+
static void LogTradeInfo(const TradeInfo &trade)
{
string message = StringFormat(
"Trade: Ticket=%d, Symbol=%s, Type=%s, Volume=%.2f, OpenPrice=%.5f, SL=%.5f, TP=%.5f, Profit=%.2f",
trade.ticket,
trade.symbol,
OrderTypeToString((ENUM_ORDER_TYPE)trade.type),
trade.volume,
trade.openPrice,
trade.stopLoss,
trade.takeProfit,
trade.profit
);
CLogger::Log(LOG_INFO, message);
}
//+------------------------------------------------------------------+
//| Log risk event |
//+------------------------------------------------------------------+
static void LogRiskEvent(ENUM_RISK_EVENT event)
{
string eventName = "";
switch(event)
{
case RISK_OVERLEVERAGED: eventName = "OVERLEVERAGED"; break;
case RISK_CORRELATION_HIGH: eventName = "HIGH_CORRELATION"; break;
case RISK_DRAWDOWN_LIMIT: eventName = "DRAWDOWN_LIMIT"; break;
case RISK_DAILY_LOSS_LIMIT: eventName = "DAILY_LOSS_LIMIT"; break;
case RISK_MARGIN_CALL: eventName = "MARGIN_CALL"; break;
case RISK_VOLATILITY_SPIKE: eventName = "VOLATILITY_SPIKE"; break;
case RISK_LIQUIDITY_ISSUE: eventName = "LIQUIDITY_ISSUE"; break;
default: eventName = "UNKNOWN"; break;
}
CLogger::Log(LOG_WARNING, "Risk Event: " + eventName);
}
//+------------------------------------------------------------------+
//| Calculate performance metrics |
//+------------------------------------------------------------------+
static void CalculatePerformanceMetrics(
int totalTrades,
int winningTrades,
double totalProfit,
double totalLoss,
double &winRate,
double &profitFactor,
double &expectedValue
)
{
if(totalTrades == 0)
{
winRate = 0;
profitFactor = 0;
expectedValue = 0;
return;
}
winRate = (double)winningTrades / totalTrades * 100;
if(totalLoss != 0)
profitFactor = MathAbs(totalProfit / totalLoss);
else
profitFactor = (totalProfit > 0) ? 999.99 : 0;
double avgWin = (winningTrades > 0) ? totalProfit / winningTrades : 0;
double avgLoss = ((totalTrades - winningTrades) > 0) ? totalLoss / (totalTrades - winningTrades) : 0;
expectedValue = (winRate / 100 * avgWin) - ((100 - winRate) / 100 * MathAbs(avgLoss));
}
//+------------------------------------------------------------------+
//| Format trade array for logging |
//+------------------------------------------------------------------+
static string FormatTradeArray(const TradeInfo &trades[], int count)
{
if(count == 0) return "No trades";
string result = StringFormat("\n=== Trade List (%d trades) ===\n", count);
for(int i = 0; i < count && i < ArraySize(trades); i++)
{
result += StringFormat(
"#%d: Ticket=%d, %s %s, Vol=%.2f, Price=%.5f, SL=%.5f, TP=%.5f, P/L=%.2f\n",
i + 1,
trades[i].ticket,
trades[i].symbol,
OrderTypeToString((ENUM_ORDER_TYPE)trades[i].type),
trades[i].volume,
trades[i].openPrice,
trades[i].stopLoss,
trades[i].takeProfit,
trades[i].profit
);
}
result += "=========================\n";
return result;
}
//+------------------------------------------------------------------+
//| Check if market is open |
//+------------------------------------------------------------------+
static bool IsMarketOpen(string symbol = NULL)
{
if(symbol == NULL) symbol = _Symbol;
datetime serverTime = TimeCurrent();
MqlDateTime dt;
TimeToStruct(serverTime, dt);
// Basic check - skip weekends
if(dt.day_of_week == 0 || dt.day_of_week == 6)
return false;
// Check session times
datetime from, to;
if(SymbolInfoSessionTrade(symbol, (ENUM_DAY_OF_WEEK)dt.day_of_week, 0, from, to))
{
datetime currentTime = serverTime % 86400; // Time of day in seconds
datetime sessionStart = from % 86400;
datetime sessionEnd = to % 86400;
if(sessionEnd < sessionStart) // Session crosses midnight
{
return (currentTime >= sessionStart || currentTime <= sessionEnd);
}
else
{
return (currentTime >= sessionStart && currentTime <= sessionEnd);
}
}
return true; // Default to open if can't determine
}
//+------------------------------------------------------------------+
//| Calculate correlation coefficient |
//+------------------------------------------------------------------+
static double CalculateCorrelation(const double &x[], const double &y[])
{
int n = MathMin(ArraySize(x), ArraySize(y));
if(n < 2) return 0;
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;
for(int i = 0; i < n; i++)
{
sumX += x[i];
sumY += y[i];
sumXY += x[i] * y[i];
sumX2 += x[i] * x[i];
sumY2 += y[i] * y[i];
}
double numerator = n * sumXY - sumX * sumY;
double denominator = MathSqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
if(denominator == 0) return 0;
return numerator / denominator;
}
//+------------------------------------------------------------------+
//| Get volatility (ATR-based) |
//+------------------------------------------------------------------+
static double GetVolatility(string symbol, ENUM_TIMEFRAMES timeframe, int period = 14)
{
int atrHandle = iATR(symbol, timeframe, period);
if(atrHandle == INVALID_HANDLE) return 0;
double atr[];
ArraySetAsSeries(atr, true);
if(CopyBuffer(atrHandle, 0, 0, 1, atr) <= 0)
{
IndicatorRelease(atrHandle);
return 0;
}
IndicatorRelease(atrHandle);
return atr[0];
}
};
#endif // UTILITIES_MQH