2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Utilities.mqh |
|
2025-09-01 16:55:52 +01:00
|
|
|
//| Utility Functions Module 2.4 |
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Fixed: Remove Duplicates & Add Missing Members |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-09-01 16:55:52 +01:00
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
#ifndef UTILITIES_MQH
|
|
|
|
#define UTILITIES_MQH
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
#include "DataTypes.mqh" // Include DataTypes first for all type definitions
|
2025-07-20 22:38:24 +01:00
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-09-01 16:55:52 +01:00
|
|
|
//| Logger Class |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-09-01 16:55:52 +01:00
|
|
|
class CLogger
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
|
|
|
private:
|
2025-09-01 16:55:52 +01:00
|
|
|
static ENUM_LOG_LEVEL m_LogLevel;
|
|
|
|
static string m_LogFile;
|
|
|
|
static bool m_FileLogging;
|
|
|
|
static bool m_TerminalLogging;
|
|
|
|
static int m_FileHandle;
|
2025-08-27 15:53:29 +01:00
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
public:
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Initialize logger |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static void Init(ENUM_LOG_LEVEL level = LOG_INFO, bool fileLogging = true, bool terminalLogging = true)
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
m_LogLevel = level;
|
|
|
|
m_FileLogging = fileLogging;
|
|
|
|
m_TerminalLogging = terminalLogging;
|
|
|
|
|
|
|
|
if(m_FileLogging)
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
string filename = "ERMT_" + TimeToString(TimeCurrent(), TIME_DATE) + ".log";
|
|
|
|
m_LogFile = filename;
|
|
|
|
m_FileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI);
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Deinitialize logger |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static void Deinit()
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
if(m_FileHandle != INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
FileClose(m_FileHandle);
|
|
|
|
m_FileHandle = INVALID_HANDLE;
|
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Log message |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static void Log(ENUM_LOG_LEVEL level, string message)
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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);
|
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
private:
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Convert log level to string |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static string LogLevelToString(ENUM_LOG_LEVEL level)
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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";
|
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
2025-09-01 16:55:52 +01:00
|
|
|
};
|
2025-08-27 14:21:02 +01:00
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
// 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;
|
2025-07-20 22:38:24 +01:00
|
|
|
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-09-01 16:55:52 +01:00
|
|
|
//| Utilities Class |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-09-01 16:55:52 +01:00
|
|
|
class CUtilities
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
public:
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Convert order type to string |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static string OrderTypeToString(ENUM_ORDER_TYPE type)
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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";
|
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Format currency value |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static string FormatCurrency(double value, string currency = "")
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
if(currency == "")
|
|
|
|
currency = AccountInfoString(ACCOUNT_CURRENCY);
|
|
|
|
return StringFormat("%s %.2f", currency, value);
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Format percentage |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static string FormatPercent(double value)
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
return StringFormat("%.2f%%", value);
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get time frame string |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static string TimeFrameToString(ENUM_TIMEFRAMES tf)
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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";
|
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate lot size based on risk |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static double CalculateLotSize(double riskAmount, double stopLossPoints, string symbol = NULL)
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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);
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Log trade info |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static void LogTradeInfo(const TradeInfo &trade)
|
2025-08-27 16:22:33 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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);
|
2025-08-27 16:22:33 +01:00
|
|
|
}
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Log risk event |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static void LogRiskEvent(ENUM_RISK_EVENT event)
|
2025-08-27 16:22:33 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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);
|
2025-08-27 16:22:33 +01:00
|
|
|
}
|
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate performance metrics |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static void CalculatePerformanceMetrics(
|
|
|
|
int totalTrades,
|
|
|
|
int winningTrades,
|
|
|
|
double totalProfit,
|
|
|
|
double totalLoss,
|
|
|
|
double &winRate,
|
|
|
|
double &profitFactor,
|
|
|
|
double &expectedValue
|
|
|
|
)
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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));
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
2025-09-01 16:55:52 +01:00
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Format trade array for logging |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static string FormatTradeArray(const TradeInfo &trades[], int count)
|
2025-08-27 16:22:33 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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;
|
2025-08-27 16:22:33 +01:00
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if market is open |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static bool IsMarketOpen(string symbol = NULL)
|
2025-08-27 16:22:33 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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
|
2025-08-27 16:22:33 +01:00
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate correlation coefficient |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static double CalculateCorrelation(const double &x[], const double &y[])
|
2025-08-27 16:22:33 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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;
|
2025-08-27 16:22:33 +01:00
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
|
2025-09-01 16:55:52 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get volatility (ATR-based) |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
static double GetVolatility(string symbol, ENUM_TIMEFRAMES timeframe, int period = 14)
|
2025-08-27 16:22:33 +01:00
|
|
|
{
|
2025-09-01 16:55:52 +01:00
|
|
|
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];
|
2025-08-27 16:22:33 +01:00
|
|
|
}
|
2025-09-01 16:55:52 +01:00
|
|
|
};
|
2025-08-27 14:21:02 +01:00
|
|
|
|
2025-08-27 15:53:29 +01:00
|
|
|
#endif // UTILITIES_MQH
|