2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Utilities.mqh |
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Utility Functions Module 2.3 |
|
|
|
|
//| Fixed: Remove Duplicates & Add Missing Members |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
#ifndef UTILITIES_MQH
|
|
|
|
#define UTILITIES_MQH
|
|
|
|
|
2025-08-27 15:53:29 +01:00
|
|
|
#include <Object.mqh>
|
2025-07-20 22:38:24 +01:00
|
|
|
#include <Files/FileTxt.mqh>
|
2025-08-27 15:53:29 +01:00
|
|
|
#include <Indicators/Indicators.mqh>
|
|
|
|
#include "DataTypes.mqh"
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Log Levels |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
enum ENUM_LOG_LEVEL
|
|
|
|
{
|
|
|
|
LOG_DEBUG = 0,
|
|
|
|
LOG_INFO = 1,
|
|
|
|
LOG_WARNING = 2,
|
|
|
|
LOG_ERROR = 3,
|
|
|
|
LOG_CRITICAL = 4
|
|
|
|
};
|
2025-07-20 22:38:24 +01:00
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Utilities Class |
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
class CUtilities : public CObject
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
|
|
|
private:
|
2025-08-27 15:53:29 +01:00
|
|
|
// Logging
|
|
|
|
ENUM_LOG_LEVEL m_log_level;
|
|
|
|
string m_log_prefix;
|
|
|
|
int m_log_handle;
|
2025-08-27 16:22:33 +01:00
|
|
|
bool m_save_log;
|
2025-08-27 15:53:29 +01:00
|
|
|
string m_log_filename;
|
|
|
|
|
|
|
|
// Performance tracking
|
|
|
|
ulong m_last_tick;
|
|
|
|
double m_tick_duration;
|
|
|
|
|
|
|
|
// ATR Indicator
|
|
|
|
int m_atr_handle;
|
|
|
|
int m_atr_period;
|
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
public:
|
2025-08-27 15:53:29 +01:00
|
|
|
// Constructor/Destructor
|
2025-07-20 22:38:24 +01:00
|
|
|
CUtilities();
|
|
|
|
~CUtilities();
|
2025-08-27 15:53:29 +01:00
|
|
|
|
|
|
|
// Initialization
|
|
|
|
bool Initialize(ENUM_LOG_LEVEL level = LOG_INFO);
|
|
|
|
void SetLogLevel(ENUM_LOG_LEVEL level) { m_log_level = level; }
|
|
|
|
void EnableFileLogging(bool enable) { m_save_log = enable; }
|
|
|
|
|
2025-08-27 16:22:33 +01:00
|
|
|
// Logging methods
|
2025-08-27 15:53:29 +01:00
|
|
|
void Log(string message, ENUM_LOG_LEVEL level = LOG_INFO);
|
|
|
|
void LogTrade(const TradeInfo &trade);
|
|
|
|
void LogExternalTrade(ulong ticket, string action);
|
|
|
|
void LogRiskEvent(ENUM_RISK_EVENT event, string details);
|
|
|
|
void LogPerformance(const PerformanceMetrics &metrics);
|
|
|
|
|
2025-08-27 16:22:33 +01:00
|
|
|
// Report generation
|
2025-08-27 15:53:29 +01:00
|
|
|
string GenerateReport(const PerformanceMetrics &metrics);
|
|
|
|
string GenerateReportEnhanced(const PerformanceMetrics &metrics,
|
|
|
|
int external_count, double external_profit);
|
|
|
|
void SaveTradeHistory(const TradeInfo &trades[], int count);
|
|
|
|
|
|
|
|
// Technical indicators
|
|
|
|
double GetATR(int period);
|
|
|
|
double GetRSI(string symbol, ENUM_TIMEFRAMES timeframe, int period);
|
|
|
|
double GetMA(string symbol, ENUM_TIMEFRAMES timeframe, int period,
|
|
|
|
ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price);
|
|
|
|
double GetBollingerBand(string symbol, ENUM_TIMEFRAMES timeframe,
|
|
|
|
int period, double deviation, int band_type);
|
|
|
|
|
|
|
|
// Price calculations
|
|
|
|
double NormalizePrice(double price, string symbol);
|
|
|
|
double NormalizeLot(double lot, string symbol);
|
|
|
|
double CalculatePipValue(string symbol, double lot_size);
|
|
|
|
double CalculateSpread(string symbol);
|
|
|
|
int GetDigits(string symbol) { return (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); }
|
|
|
|
|
|
|
|
// Risk calculations
|
|
|
|
double CalculatePositionSize(double risk_amount, double stop_distance, string symbol);
|
|
|
|
double CalculateRiskAmount(double balance, double risk_percent);
|
|
|
|
double CalculateRiskReward(double entry, double sl, double tp, ENUM_ORDER_TYPE type);
|
|
|
|
|
|
|
|
// Time functions
|
|
|
|
bool IsMarketOpen();
|
|
|
|
bool IsSessionActive(string session_name);
|
|
|
|
datetime GetBarTime(string symbol, ENUM_TIMEFRAMES timeframe, int shift);
|
|
|
|
int GetDayOfWeek() { MqlDateTime dt; TimeCurrent(dt); return dt.day_of_week; }
|
|
|
|
|
|
|
|
// String utilities
|
|
|
|
string FormatMoney(double amount);
|
|
|
|
string FormatPercent(double percent);
|
|
|
|
string OrderTypeToString(ENUM_ORDER_TYPE type);
|
|
|
|
string TimeframeToString(ENUM_TIMEFRAMES tf);
|
|
|
|
|
|
|
|
// File operations
|
|
|
|
bool SaveToFile(string filename, string content);
|
|
|
|
string LoadFromFile(string filename);
|
2025-08-27 16:22:33 +01:00
|
|
|
void CloseExternalLog();
|
2025-08-27 15:53:29 +01:00
|
|
|
|
|
|
|
// Performance monitoring
|
|
|
|
void StartTimer() { m_last_tick = GetMicrosecondCount(); }
|
|
|
|
double GetElapsedTime() { return (GetMicrosecondCount() - m_last_tick) / 1000.0; }
|
2025-07-20 22:38:24 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Constructor |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
CUtilities::CUtilities()
|
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
m_log_level = LOG_INFO;
|
|
|
|
m_log_prefix = "EA";
|
|
|
|
m_log_handle = INVALID_HANDLE;
|
|
|
|
m_save_log = false;
|
|
|
|
m_log_filename = "";
|
|
|
|
m_last_tick = 0;
|
|
|
|
m_tick_duration = 0;
|
|
|
|
m_atr_handle = INVALID_HANDLE;
|
|
|
|
m_atr_period = 14;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Destructor |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
CUtilities::~CUtilities()
|
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
if(m_log_handle != INVALID_HANDLE)
|
|
|
|
FileClose(m_log_handle);
|
|
|
|
|
|
|
|
if(m_atr_handle != INVALID_HANDLE)
|
|
|
|
IndicatorRelease(m_atr_handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Initialize |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CUtilities::Initialize(ENUM_LOG_LEVEL level)
|
|
|
|
{
|
|
|
|
m_log_level = level;
|
|
|
|
|
|
|
|
// Create log file if enabled
|
|
|
|
if(m_save_log)
|
|
|
|
{
|
|
|
|
m_log_filename = StringFormat("EA_Log_%s.txt", TimeToString(TimeCurrent(), TIME_DATE));
|
|
|
|
m_log_handle = FileOpen(m_log_filename, FILE_WRITE|FILE_TXT|FILE_ANSI);
|
|
|
|
if(m_log_handle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
m_save_log = false;
|
|
|
|
Print("Failed to create log file");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize ATR indicator
|
|
|
|
m_atr_handle = iATR(_Symbol, PERIOD_CURRENT, m_atr_period);
|
|
|
|
if(m_atr_handle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Log("Failed to initialize ATR indicator", LOG_ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Log("Utilities initialized successfully", LOG_INFO);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Main Logging Function |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::Log(string message, ENUM_LOG_LEVEL level)
|
|
|
|
{
|
|
|
|
if(level < m_log_level) return;
|
|
|
|
|
|
|
|
string level_str = "";
|
|
|
|
switch(level)
|
|
|
|
{
|
|
|
|
case LOG_DEBUG: level_str = "DEBUG"; break;
|
|
|
|
case LOG_INFO: level_str = "INFO"; break;
|
|
|
|
case LOG_WARNING: level_str = "WARN"; break;
|
|
|
|
case LOG_ERROR: level_str = "ERROR"; break;
|
|
|
|
case LOG_CRITICAL: level_str = "CRITICAL"; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
string full_message = StringFormat("[%s] [%s] %s: %s",
|
|
|
|
TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
|
|
|
|
level_str, m_log_prefix, message);
|
|
|
|
|
|
|
|
// Print to terminal
|
|
|
|
Print(full_message);
|
|
|
|
|
|
|
|
// Save to file if enabled
|
|
|
|
if(m_save_log && m_log_handle != INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
FileWriteString(m_log_handle, full_message + "\n");
|
|
|
|
FileFlush(m_log_handle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Log Trade Information |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::LogTrade(const TradeInfo &trade)
|
|
|
|
{
|
|
|
|
string message = StringFormat("Trade %d: %s %s %.2f @ %.5f, SL=%.5f, TP=%.5f, P/L=%.2f",
|
2025-08-27 14:21:02 +01:00
|
|
|
trade.ticket,
|
2025-08-27 15:53:29 +01:00
|
|
|
OrderTypeToString(trade.type),
|
2025-08-27 14:21:02 +01:00
|
|
|
trade.symbol,
|
|
|
|
trade.volume,
|
2025-08-27 15:53:29 +01:00
|
|
|
trade.entry_price,
|
|
|
|
trade.sl,
|
|
|
|
trade.tp,
|
|
|
|
trade.profit);
|
|
|
|
Log(message, LOG_INFO);
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Log External Trade |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
void CUtilities::LogExternalTrade(ulong ticket, string action)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
string message = StringFormat("External Trade %d: %s", ticket, action);
|
|
|
|
Log(message, LOG_INFO);
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Log Risk Event |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
void CUtilities::LogRiskEvent(ENUM_RISK_EVENT event, string details)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
string event_str = "";
|
|
|
|
switch(event)
|
|
|
|
{
|
|
|
|
case RISK_OVERLEVERAGED: event_str = "Overleveraged"; break;
|
|
|
|
case RISK_CORRELATION_HIGH: event_str = "High Correlation"; break;
|
|
|
|
case RISK_DRAWDOWN_LIMIT: event_str = "Drawdown Limit"; break;
|
|
|
|
case RISK_DAILY_LOSS_LIMIT: event_str = "Daily Loss Limit"; break;
|
|
|
|
case RISK_MARGIN_CALL: event_str = "Margin Call Risk"; break;
|
|
|
|
case RISK_VOLATILITY_SPIKE: event_str = "Volatility Spike"; break;
|
|
|
|
case RISK_LIQUIDITY_ISSUE: event_str = "Liquidity Issue"; break;
|
|
|
|
default: event_str = "Unknown Risk"; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
string message = StringFormat("Risk Event: %s - %s", event_str, details);
|
|
|
|
Log(message, LOG_WARNING);
|
|
|
|
}
|
2025-08-27 14:21:02 +01:00
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Log Performance Metrics |
|
2025-07-30 14:26:35 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
void CUtilities::LogPerformance(const PerformanceMetrics &metrics)
|
2025-07-30 14:26:35 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
string message = StringFormat(
|
|
|
|
"Performance: Trades=%d, Win Rate=%.1f%%, PF=%.2f, DD=%.2f%%",
|
|
|
|
metrics.total_trades,
|
|
|
|
metrics.win_rate,
|
|
|
|
metrics.profit_factor,
|
|
|
|
metrics.max_drawdown
|
|
|
|
);
|
|
|
|
Log(message, LOG_INFO);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Generate Performance Report |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CUtilities::GenerateReport(const PerformanceMetrics &metrics)
|
|
|
|
{
|
|
|
|
string report = "\n========== PERFORMANCE REPORT ==========\n";
|
|
|
|
report += StringFormat("Total Profit/Loss: %s\n", FormatMoney(metrics.total_profit));
|
|
|
|
report += StringFormat("Total Trades: %d\n", metrics.total_trades);
|
|
|
|
report += StringFormat("Winning Trades: %d (%.1f%%)\n",
|
|
|
|
metrics.winning_trades, metrics.win_rate);
|
|
|
|
report += StringFormat("Losing Trades: %d\n", metrics.losing_trades);
|
|
|
|
report += StringFormat("Average Win: %s\n", FormatMoney(metrics.average_win));
|
|
|
|
report += StringFormat("Average Loss: %s\n", FormatMoney(metrics.average_loss));
|
|
|
|
report += StringFormat("Profit Factor: %.2f\n", metrics.profit_factor);
|
|
|
|
report += StringFormat("Max Drawdown: %.2f%%\n", metrics.max_drawdown);
|
|
|
|
report += StringFormat("Current Drawdown: %.2f%%\n", metrics.current_drawdown);
|
|
|
|
report += StringFormat("Sharpe Ratio: %.2f\n", metrics.sharpe_ratio);
|
|
|
|
report += StringFormat("Expected Value: %s\n", FormatMoney(metrics.expected_value));
|
|
|
|
report += "==========================================\n";
|
|
|
|
|
|
|
|
return report;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Generate Enhanced Report with External Trades |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CUtilities::GenerateReportEnhanced(const PerformanceMetrics &metrics,
|
|
|
|
int external_count, double external_profit)
|
2025-07-30 14:26:35 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
string report = GenerateReport(metrics);
|
|
|
|
|
|
|
|
// Add external trade information
|
|
|
|
report += "\n========== EXTERNAL TRADES ==========\n";
|
|
|
|
report += StringFormat("External Positions: %d\n", external_count);
|
|
|
|
report += StringFormat("External P/L: %s\n", FormatMoney(external_profit));
|
|
|
|
report += StringFormat("Total Combined P/L: %s\n",
|
|
|
|
FormatMoney(metrics.total_profit + external_profit));
|
|
|
|
report += "======================================\n";
|
|
|
|
|
|
|
|
return report;
|
|
|
|
}
|
2025-07-30 14:26:35 +01:00
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Save Trade History to File |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::SaveTradeHistory(const TradeInfo &trades[], int count)
|
2025-07-30 14:26:35 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
string filename = StringFormat("TradeHistory_%s.csv",
|
|
|
|
TimeToString(TimeCurrent(), TIME_DATE));
|
|
|
|
int handle = FileOpen(filename, FILE_WRITE|FILE_CSV);
|
|
|
|
|
|
|
|
if(handle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Log("Failed to create trade history file", LOG_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write header
|
|
|
|
FileWrite(handle, "Ticket", "Symbol", "Type", "Volume", "Entry", "Exit",
|
|
|
|
"SL", "TP", "Profit", "Open Time", "Close Time");
|
|
|
|
|
|
|
|
// Write trade data
|
|
|
|
for(int i = 0; i < count; i++)
|
|
|
|
{
|
|
|
|
FileWrite(handle,
|
|
|
|
trades[i].ticket,
|
|
|
|
trades[i].symbol,
|
|
|
|
OrderTypeToString(trades[i].type),
|
|
|
|
trades[i].volume,
|
|
|
|
trades[i].entry_price,
|
|
|
|
trades[i].current_price,
|
|
|
|
trades[i].sl,
|
|
|
|
trades[i].tp,
|
|
|
|
trades[i].profit,
|
|
|
|
TimeToString(trades[i].open_time),
|
|
|
|
TimeToString(trades[i].last_update));
|
|
|
|
}
|
|
|
|
|
|
|
|
FileClose(handle);
|
|
|
|
Log(StringFormat("Trade history saved to %s", filename), LOG_INFO);
|
2025-07-30 14:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Get ATR Value |
|
2025-07-30 14:26:35 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
double CUtilities::GetATR(int period)
|
2025-07-30 14:26:35 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
if(period != m_atr_period)
|
|
|
|
{
|
|
|
|
// Reinitialize with new period
|
|
|
|
if(m_atr_handle != INVALID_HANDLE)
|
|
|
|
IndicatorRelease(m_atr_handle);
|
|
|
|
|
|
|
|
m_atr_period = period;
|
|
|
|
m_atr_handle = iATR(_Symbol, PERIOD_CURRENT, m_atr_period);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(m_atr_handle == INVALID_HANDLE) return 0;
|
|
|
|
|
|
|
|
double atr_buffer[1];
|
|
|
|
if(CopyBuffer(m_atr_handle, 0, 0, 1, atr_buffer) <= 0) return 0;
|
|
|
|
|
|
|
|
return atr_buffer[0];
|
2025-07-30 14:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Get RSI Value |
|
2025-07-30 14:26:35 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
double CUtilities::GetRSI(string symbol, ENUM_TIMEFRAMES timeframe, int period)
|
2025-07-30 14:26:35 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
int handle = iRSI(symbol, timeframe, period, PRICE_CLOSE);
|
|
|
|
if(handle == INVALID_HANDLE) return 50; // Return neutral value
|
|
|
|
|
|
|
|
double rsi_buffer[1];
|
|
|
|
if(CopyBuffer(handle, 0, 0, 1, rsi_buffer) <= 0)
|
|
|
|
{
|
|
|
|
IndicatorRelease(handle);
|
|
|
|
return 50;
|
|
|
|
}
|
|
|
|
|
|
|
|
IndicatorRelease(handle);
|
|
|
|
return rsi_buffer[0];
|
2025-07-30 14:26:35 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 16:22:33 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get Moving Average Value |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CUtilities::GetMA(string symbol, ENUM_TIMEFRAMES timeframe, int period,
|
|
|
|
ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price)
|
|
|
|
{
|
|
|
|
int handle = iMA(symbol, timeframe, period, 0, method, price);
|
|
|
|
if(handle == INVALID_HANDLE) return 0;
|
|
|
|
|
|
|
|
double ma_buffer[1];
|
|
|
|
if(CopyBuffer(handle, 0, 0, 1, ma_buffer) <= 0)
|
|
|
|
{
|
|
|
|
IndicatorRelease(handle);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
IndicatorRelease(handle);
|
|
|
|
return ma_buffer[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get Bollinger Band Value |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CUtilities::GetBollingerBand(string symbol, ENUM_TIMEFRAMES timeframe,
|
|
|
|
int period, double deviation, int band_type)
|
|
|
|
{
|
|
|
|
int handle = iBands(symbol, timeframe, period, 0, deviation, PRICE_CLOSE);
|
|
|
|
if(handle == INVALID_HANDLE) return 0;
|
|
|
|
|
|
|
|
double band_buffer[1];
|
|
|
|
// band_type: 0=middle, 1=upper, 2=lower
|
|
|
|
if(CopyBuffer(handle, band_type, 0, 1, band_buffer) <= 0)
|
|
|
|
{
|
|
|
|
IndicatorRelease(handle);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
IndicatorRelease(handle);
|
|
|
|
return band_buffer[0];
|
|
|
|
}
|
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Calculate Position Size |
|
2025-07-30 14:26:35 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
double CUtilities::CalculatePositionSize(double risk_amount, double stop_distance, string symbol)
|
2025-07-30 14:26:35 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
if(stop_distance <= 0) return 0;
|
|
|
|
|
|
|
|
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
|
|
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
|
|
|
|
|
|
if(tick_value == 0 || tick_size == 0) return 0;
|
|
|
|
|
|
|
|
double lot_size = (risk_amount * tick_size) / (stop_distance * tick_value);
|
|
|
|
|
|
|
|
return NormalizeLot(lot_size, symbol);
|
2025-07-30 14:26:35 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 16:22:33 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate Risk Amount |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CUtilities::CalculateRiskAmount(double balance, double risk_percent)
|
|
|
|
{
|
|
|
|
return balance * risk_percent / 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate Risk Reward Ratio |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CUtilities::CalculateRiskReward(double entry, double sl, double tp, ENUM_ORDER_TYPE type)
|
|
|
|
{
|
|
|
|
if(sl == 0 || tp == 0) return 0;
|
|
|
|
|
|
|
|
double risk = MathAbs(entry - sl);
|
|
|
|
double reward = MathAbs(tp - entry);
|
|
|
|
|
|
|
|
if(risk == 0) return 0;
|
|
|
|
|
|
|
|
return reward / risk;
|
|
|
|
}
|
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Normalize Price |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
double CUtilities::NormalizePrice(double price, string symbol)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
|
|
|
return NormalizeDouble(price, digits);
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Normalize Lot Size |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
double CUtilities::NormalizeLot(double lot, string symbol)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
|
|
double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
|
|
|
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
|
|
|
|
if(lot < min_lot) lot = min_lot;
|
|
|
|
if(lot > max_lot) lot = max_lot;
|
|
|
|
|
|
|
|
int steps = (int)MathRound(lot / lot_step);
|
|
|
|
return steps * lot_step;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 16:22:33 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate Pip Value |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CUtilities::CalculatePipValue(string symbol, double lot_size)
|
|
|
|
{
|
|
|
|
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
|
|
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
|
|
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
|
|
|
|
|
|
|
double pip_size = (digits == 3 || digits == 5) ? tick_size * 10 : tick_size;
|
|
|
|
double pip_value = (tick_value * pip_size) / tick_size * lot_size;
|
|
|
|
|
|
|
|
return pip_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate Spread |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CUtilities::CalculateSpread(string symbol)
|
|
|
|
{
|
|
|
|
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
|
|
|
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
|
|
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
|
|
|
|
if(point == 0) return 0;
|
|
|
|
|
|
|
|
return (ask - bid) / point;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if Market is Open |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CUtilities::IsMarketOpen()
|
|
|
|
{
|
|
|
|
datetime current = TimeCurrent();
|
|
|
|
MqlDateTime dt;
|
|
|
|
TimeToStruct(current, dt);
|
|
|
|
|
|
|
|
// Skip weekends
|
|
|
|
if(dt.day_of_week == 0 || dt.day_of_week == 6) return false;
|
|
|
|
|
|
|
|
// Check session times
|
|
|
|
long session_deals = SymbolInfoInteger(_Symbol, SYMBOL_SESSION_DEALS);
|
|
|
|
if(session_deals == 0) return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if Session is Active |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CUtilities::IsSessionActive(string session_name)
|
|
|
|
{
|
|
|
|
MqlDateTime current;
|
|
|
|
TimeToStruct(TimeCurrent(), current);
|
|
|
|
|
|
|
|
int hour = current.hour;
|
|
|
|
|
|
|
|
// Define session times (simplified)
|
|
|
|
if(session_name == "ASIAN")
|
|
|
|
return (hour >= 0 && hour < 9);
|
|
|
|
else if(session_name == "EUROPEAN")
|
|
|
|
return (hour >= 8 && hour < 17);
|
|
|
|
else if(session_name == "AMERICAN")
|
|
|
|
return (hour >= 13 && hour < 22);
|
|
|
|
else if(session_name == "PACIFIC")
|
|
|
|
return (hour >= 21 || hour < 6);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get Bar Time |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
datetime CUtilities::GetBarTime(string symbol, ENUM_TIMEFRAMES timeframe, int shift)
|
|
|
|
{
|
|
|
|
datetime time_array[1];
|
|
|
|
if(CopyTime(symbol, timeframe, shift, 1, time_array) > 0)
|
|
|
|
return time_array[0];
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Format Money |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
string CUtilities::FormatMoney(double amount)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
string sign = amount < 0 ? "-" : "";
|
|
|
|
amount = MathAbs(amount);
|
|
|
|
return StringFormat("%s$%.2f", sign, amount);
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
2025-07-30 14:26:35 +01:00
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Format Percent |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
string CUtilities::FormatPercent(double percent)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +01:00
|
|
|
return StringFormat("%.2f%%", percent);
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
//| Order Type to String |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 15:53:29 +01:00
|
|
|
string CUtilities::OrderTypeToString(ENUM_ORDER_TYPE type)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 15:53:29 +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";
|
|
|
|
default: return "UNKNOWN";
|
|
|
|
}
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 16:22:33 +01:00
|
|
|
//| Timeframe to String |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 16:22:33 +01:00
|
|
|
string CUtilities::TimeframeToString(ENUM_TIMEFRAMES tf)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 16:22:33 +01:00
|
|
|
switch(tf)
|
2025-08-27 15:53:29 +01:00
|
|
|
{
|
2025-08-27 16:22:33 +01:00
|
|
|
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 "Current";
|
2025-08-27 15:53:29 +01:00
|
|
|
}
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 16:22:33 +01:00
|
|
|
//| Save Content to File |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 16:22:33 +01:00
|
|
|
bool CUtilities::SaveToFile(string filename, string content)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-08-27 16:22:33 +01:00
|
|
|
int handle = FileOpen(filename, FILE_WRITE|FILE_TXT|FILE_ANSI);
|
|
|
|
if(handle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Log(StringFormat("Failed to open file %s for writing", filename), LOG_ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
|
2025-08-27 16:22:33 +01:00
|
|
|
FileWriteString(handle, content);
|
|
|
|
FileClose(handle);
|
2025-08-27 15:53:29 +01:00
|
|
|
|
2025-08-27 16:22:33 +01:00
|
|
|
Log(StringFormat("Content saved to %s", filename), LOG_INFO);
|
|
|
|
return true;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 16:22:33 +01:00
|
|
|
//| Load Content from File |
|
2025-08-27 14:21:02 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-08-27 16:22:33 +01:00
|
|
|
string CUtilities::LoadFromFile(string filename)
|
2025-08-27 14:21:02 +01:00
|
|
|
{
|
2025-08-27 16:22:33 +01:00
|
|
|
int handle = FileOpen(filename, FILE_READ|FILE_TXT|FILE_ANSI);
|
|
|
|
if(handle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Log(StringFormat("Failed to open file %s for reading", filename), LOG_ERROR);
|
|
|
|
return "";
|
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
|
2025-08-27 16:22:33 +01:00
|
|
|
string content = "";
|
|
|
|
while(!FileIsEnding(handle))
|
|
|
|
{
|
|
|
|
content += FileReadString(handle) + "\n";
|
|
|
|
}
|
2025-08-27 15:53:29 +01:00
|
|
|
|
2025-08-27 16:22:33 +01:00
|
|
|
FileClose(handle);
|
|
|
|
return content;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Close External Log File |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::CloseExternalLog()
|
|
|
|
{
|
|
|
|
if(m_log_handle != INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
FileClose(m_log_handle);
|
|
|
|
m_log_handle = INVALID_HANDLE;
|
|
|
|
Log("External log file closed", LOG_INFO);
|
|
|
|
}
|
2025-08-27 14:21:02 +01:00
|
|
|
}
|
|
|
|
|
2025-08-27 15:53:29 +01:00
|
|
|
#endif // UTILITIES_MQH
|