2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Utilities.mqh |
|
2025-07-30 14:26:35 +01:00
|
|
|
//| Utility Functions Module 2.2 |
|
|
|
|
//| Fixed: Backtest compatibility |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
#ifndef UTILITIES_MQH
|
|
|
|
#define UTILITIES_MQH
|
|
|
|
|
|
|
|
#include "DataTypes.mqh"
|
|
|
|
#include <Files/FileTxt.mqh>
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Utilities Class |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
class CUtilities
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
//--- Configuration
|
|
|
|
ENUM_LOG_LEVEL m_log_level;
|
|
|
|
string m_log_filename;
|
|
|
|
string m_report_filename;
|
2025-07-30 14:26:35 +01:00
|
|
|
bool m_is_testing; // Add flag for testing mode
|
2025-07-20 22:38:24 +01:00
|
|
|
|
|
|
|
//--- File handles
|
|
|
|
int m_log_handle;
|
|
|
|
|
|
|
|
//--- News calendar (simplified)
|
|
|
|
datetime m_news_times[];
|
|
|
|
string m_news_impacts[];
|
|
|
|
int m_news_count;
|
|
|
|
|
|
|
|
//--- Helper methods
|
|
|
|
string GetTimeString(datetime time);
|
|
|
|
string GetLogPrefix(ENUM_LOG_LEVEL level);
|
|
|
|
void WriteToFile(string filename, string text);
|
|
|
|
|
|
|
|
public:
|
|
|
|
CUtilities();
|
|
|
|
~CUtilities();
|
|
|
|
|
|
|
|
//--- Initialization
|
|
|
|
bool Initialize(ENUM_LOG_LEVEL log_level);
|
|
|
|
|
|
|
|
//--- Logging
|
|
|
|
void Log(ENUM_LOG_LEVEL level, string message);
|
|
|
|
void LogTrade(const ManagedTrade &trade, string action);
|
|
|
|
void LogError(string function, int error_code);
|
|
|
|
|
|
|
|
//--- Reporting
|
|
|
|
void GenerateReport(const PerformanceMetrics &performance,
|
|
|
|
const ManagedTrade &trades[]);
|
|
|
|
void SaveTradeHistory(const ManagedTrade &trade);
|
|
|
|
void ExportPerformanceCSV(const PerformanceMetrics &performance);
|
|
|
|
|
|
|
|
//--- Time functions
|
|
|
|
bool IsNewsTime(int buffer_minutes = 30);
|
|
|
|
bool IsTradingHours();
|
|
|
|
bool IsMarketClosed();
|
|
|
|
string GetDayOfWeek();
|
|
|
|
|
|
|
|
//--- Formatting
|
|
|
|
string FormatCurrency(double value, string currency = "USD");
|
|
|
|
string FormatPercent(double value, int digits = 2);
|
|
|
|
string FormatPips(double points);
|
|
|
|
string FormatDuration(int seconds);
|
|
|
|
|
|
|
|
//--- Calculations
|
|
|
|
double CalculatePips(double price_difference);
|
2025-07-22 16:37:29 +01:00
|
|
|
double CalculatePositionCommission(double volume);
|
2025-07-20 22:38:24 +01:00
|
|
|
double CalculateSwap(const ManagedTrade &trade);
|
|
|
|
double CalculatePositionCommission(ulong position_ticket);
|
|
|
|
|
|
|
|
//--- System functions
|
|
|
|
string GetUninitReasonText(int reason);
|
|
|
|
bool CheckTerminalConnection();
|
|
|
|
bool CheckAutoTrading();
|
|
|
|
void SendNotification(string message, bool email = true,
|
|
|
|
bool push = true, bool alert = true);
|
|
|
|
|
|
|
|
//--- File operations
|
|
|
|
bool SaveSettings(string filename);
|
|
|
|
bool LoadSettings(string filename);
|
|
|
|
void BackupData();
|
|
|
|
|
|
|
|
//--- Market helpers
|
|
|
|
double NormalizeLots(double lots);
|
|
|
|
double NormalizePrice(double price);
|
|
|
|
bool IsNewBar(ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT);
|
|
|
|
double GetSpreadInPips();
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Constructor |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
CUtilities::CUtilities()
|
|
|
|
{
|
|
|
|
m_log_level = LOG_INFO;
|
|
|
|
m_log_filename = "RiskManager_Log.txt";
|
|
|
|
m_report_filename = "RiskManager_Report.txt";
|
|
|
|
m_log_handle = INVALID_HANDLE;
|
|
|
|
m_news_count = 0;
|
2025-07-30 14:26:35 +01:00
|
|
|
m_is_testing = false;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Destructor |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
CUtilities::~CUtilities()
|
|
|
|
{
|
|
|
|
if(m_log_handle != INVALID_HANDLE)
|
|
|
|
FileClose(m_log_handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Initialize utilities |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CUtilities::Initialize(ENUM_LOG_LEVEL log_level)
|
|
|
|
{
|
|
|
|
m_log_level = log_level;
|
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
//--- Check if we're in testing mode
|
|
|
|
m_is_testing = MQLInfoInteger(MQL_TESTER);
|
|
|
|
|
|
|
|
//--- Only create log file if NOT in testing mode
|
|
|
|
if(!m_is_testing)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-07-30 14:26:35 +01:00
|
|
|
m_log_handle = FileOpen(m_log_filename, FILE_WRITE|FILE_TXT|FILE_ANSI);
|
|
|
|
if(m_log_handle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("Utilities: Failed to create log file");
|
|
|
|
// Don't fail initialization, just disable file logging
|
|
|
|
m_log_handle = INVALID_HANDLE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//--- Write header
|
|
|
|
string header = "=== Risk Management Overlay v6.6 Log ===\n";
|
|
|
|
header += "Started: " + TimeToString(TimeCurrent()) + "\n";
|
|
|
|
header += "Symbol: " + _Symbol + "\n";
|
|
|
|
header += "Period: " + EnumToString((ENUM_TIMEFRAMES)_Period) + "\n";
|
|
|
|
header += "=========================================\n";
|
|
|
|
|
|
|
|
FileWrite(m_log_handle, header);
|
|
|
|
FileFlush(m_log_handle);
|
|
|
|
}
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//--- Load news calendar (would normally load from file/API)
|
|
|
|
// Simplified example - add some dummy news times
|
|
|
|
ArrayResize(m_news_times, 5);
|
|
|
|
ArrayResize(m_news_impacts, 5);
|
|
|
|
m_news_count = 0;
|
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
Print("Utilities initialized: Testing=", m_is_testing, ", LogLevel=", EnumToString(log_level));
|
2025-07-20 22:38:24 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Log message |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::Log(ENUM_LOG_LEVEL level, string message)
|
|
|
|
{
|
|
|
|
if(level > m_log_level) return;
|
|
|
|
|
|
|
|
string log_entry = GetTimeString(TimeCurrent()) + " " +
|
|
|
|
GetLogPrefix(level) + " " + message;
|
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
//--- Always print to terminal in testing mode or for important messages
|
|
|
|
if(m_is_testing || level <= LOG_WARNING)
|
2025-07-20 22:38:24 +01:00
|
|
|
Print(log_entry);
|
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
//--- Write to file only if not testing and file is open
|
|
|
|
if(!m_is_testing && m_log_handle != INVALID_HANDLE)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
|
|
|
FileWrite(m_log_handle, log_entry);
|
|
|
|
FileFlush(m_log_handle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Generate performance report |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::GenerateReport(const PerformanceMetrics &performance,
|
|
|
|
const ManagedTrade &trades[])
|
|
|
|
{
|
|
|
|
string report = "\n=== PERFORMANCE REPORT ===\n";
|
|
|
|
report += "Generated: " + TimeToString(TimeCurrent()) + "\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) + " (" +
|
|
|
|
FormatPercent(performance.net_profit / (AccountInfoDouble(ACCOUNT_BALANCE) - performance.net_profit) * 100) + ")\n";
|
|
|
|
report += "Current Equity: " + FormatCurrency(AccountInfoDouble(ACCOUNT_EQUITY)) + "\n\n";
|
|
|
|
|
|
|
|
//--- Trading Statistics
|
|
|
|
report += "TRADING STATISTICS\n";
|
|
|
|
report += "-----------------\n";
|
|
|
|
report += "Total Trades: " + IntegerToString(performance.total_trades) + "\n";
|
|
|
|
report += "Winning Trades: " + IntegerToString(performance.winning_trades) + " (" +
|
|
|
|
FormatPercent(performance.win_rate) + ")\n";
|
|
|
|
report += "Losing Trades: " + IntegerToString(performance.losing_trades) + "\n";
|
|
|
|
report += "Gross Profit: " + FormatCurrency(performance.gross_profit) + "\n";
|
|
|
|
report += "Gross Loss: " + FormatCurrency(performance.gross_loss) + "\n";
|
|
|
|
report += "Profit Factor: " + DoubleToString(performance.profit_factor, 2) + "\n";
|
|
|
|
report += "Expectancy: " + FormatCurrency(performance.expectancy) + "\n\n";
|
|
|
|
|
|
|
|
//--- Risk Metrics
|
|
|
|
report += "RISK METRICS\n";
|
|
|
|
report += "------------\n";
|
|
|
|
report += "Max Drawdown: " + FormatCurrency(performance.max_drawdown_amount) +
|
|
|
|
" (" + FormatPercent(performance.max_drawdown_percent) + ")\n";
|
|
|
|
report += "Sharpe Ratio: " + DoubleToString(performance.sharpe_ratio, 2) + "\n";
|
|
|
|
report += "Sortino Ratio: " + DoubleToString(performance.sortino_ratio, 2) + "\n";
|
|
|
|
report += "Calmar Ratio: " + DoubleToString(performance.calmar_ratio, 2) + "\n";
|
|
|
|
report += "Recovery Factor: " + DoubleToString(performance.recovery_factor, 2) + "\n\n";
|
|
|
|
|
|
|
|
//--- Consecutive Stats
|
|
|
|
report += "CONSECUTIVE STATISTICS\n";
|
|
|
|
report += "--------------------\n";
|
|
|
|
report += "Current Consecutive Wins: " + IntegerToString(performance.consecutive_wins) + "\n";
|
|
|
|
report += "Current Consecutive Losses: " + IntegerToString(performance.consecutive_losses) + "\n";
|
|
|
|
report += "Max Consecutive Wins: " + IntegerToString(performance.max_consecutive_wins) + "\n";
|
|
|
|
report += "Max Consecutive Losses: " + IntegerToString(performance.max_consecutive_losses) + "\n\n";
|
|
|
|
|
|
|
|
//--- Active Trades
|
|
|
|
if(ArraySize(trades) > 0)
|
|
|
|
{
|
|
|
|
report += "ACTIVE TRADES\n";
|
|
|
|
report += "-------------\n";
|
|
|
|
for(int i = 0; i < ArraySize(trades); i++)
|
|
|
|
{
|
|
|
|
report += StringFormat("#%d %s %.2f @ %.5f | P/L: %s (%.2fR) | %s\n",
|
|
|
|
trades[i].ticket,
|
|
|
|
(trades[i].type == POSITION_TYPE_BUY) ? "BUY" : "SELL",
|
|
|
|
trades[i].volume,
|
|
|
|
trades[i].open_price,
|
|
|
|
FormatCurrency(trades[i].profit),
|
|
|
|
trades[i].r_multiple,
|
|
|
|
trades[i].entry_reason);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
report += "\n========================\n";
|
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
//--- In testing mode, just print the report
|
|
|
|
if(m_is_testing)
|
|
|
|
{
|
|
|
|
Print(report);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//--- Save to file only if not testing
|
|
|
|
WriteToFile(m_report_filename, report);
|
|
|
|
}
|
2025-07-20 22:38:24 +01:00
|
|
|
|
|
|
|
//--- Also log summary
|
|
|
|
Log(LOG_INFO, "Performance report generated");
|
|
|
|
}
|
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Write to file |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::WriteToFile(string filename, string text)
|
|
|
|
{
|
|
|
|
//--- Skip file operations in testing mode
|
|
|
|
if(m_is_testing)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int handle = FileOpen(filename, FILE_WRITE|FILE_TXT|FILE_ANSI);
|
|
|
|
if(handle != INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
FileWrite(handle, text);
|
|
|
|
FileClose(handle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Save trade history |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::SaveTradeHistory(const ManagedTrade &trade)
|
|
|
|
{
|
|
|
|
//--- Skip file operations in testing mode
|
|
|
|
if(m_is_testing)
|
|
|
|
{
|
|
|
|
//--- Just log the trade details instead
|
|
|
|
string trade_info = StringFormat("Trade Closed: #%d %s %.2f lots, P/L: %.2f, R: %.2f",
|
|
|
|
trade.ticket,
|
|
|
|
(trade.type == POSITION_TYPE_BUY) ? "BUY" : "SELL",
|
|
|
|
trade.volume,
|
|
|
|
trade.profit,
|
|
|
|
trade.r_multiple);
|
|
|
|
Log(LOG_INFO, trade_info);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
string filename = "TradeHistory_" + _Symbol + ".csv";
|
|
|
|
|
|
|
|
//--- Check if file exists to write header
|
|
|
|
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", "EntryReason");
|
|
|
|
}
|
|
|
|
|
|
|
|
//--- Move to end of file
|
|
|
|
FileSeek(handle, 0, SEEK_END);
|
|
|
|
|
|
|
|
//--- Write trade data
|
|
|
|
FileWrite(handle,
|
|
|
|
trade.ticket,
|
|
|
|
trade.symbol,
|
|
|
|
(trade.type == POSITION_TYPE_BUY) ? "BUY" : "SELL",
|
|
|
|
trade.open_time,
|
|
|
|
TimeCurrent(),
|
|
|
|
trade.open_price,
|
|
|
|
SymbolInfoDouble(_Symbol, SYMBOL_BID),
|
|
|
|
trade.volume,
|
|
|
|
trade.profit,
|
|
|
|
trade.commission,
|
|
|
|
trade.swap,
|
|
|
|
trade.r_multiple,
|
|
|
|
trade.mae,
|
|
|
|
trade.mfe,
|
|
|
|
trade.entry_reason);
|
|
|
|
|
|
|
|
FileClose(handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Export performance to CSV |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::ExportPerformanceCSV(const PerformanceMetrics &performance)
|
|
|
|
{
|
|
|
|
//--- Skip file operations in testing mode
|
|
|
|
if(m_is_testing)
|
|
|
|
return;
|
|
|
|
|
|
|
|
string filename = "Performance_" + _Symbol + "_" +
|
|
|
|
TimeToString(TimeCurrent(), TIME_DATE) + ".csv";
|
|
|
|
StringReplace(filename, ".", "_");
|
|
|
|
|
|
|
|
int handle = FileOpen(filename, FILE_WRITE|FILE_CSV|FILE_ANSI, ',');
|
|
|
|
if(handle == INVALID_HANDLE) return;
|
|
|
|
|
|
|
|
//--- Write header
|
|
|
|
FileWrite(handle, "Metric", "Value");
|
|
|
|
|
|
|
|
//--- Write metrics
|
|
|
|
FileWrite(handle, "Total Trades", performance.total_trades);
|
|
|
|
FileWrite(handle, "Winning Trades", performance.winning_trades);
|
|
|
|
FileWrite(handle, "Losing Trades", performance.losing_trades);
|
|
|
|
FileWrite(handle, "Win Rate %", performance.win_rate);
|
|
|
|
FileWrite(handle, "Gross Profit", performance.gross_profit);
|
|
|
|
FileWrite(handle, "Gross Loss", performance.gross_loss);
|
|
|
|
FileWrite(handle, "Net Profit", performance.net_profit);
|
|
|
|
FileWrite(handle, "Profit Factor", performance.profit_factor);
|
|
|
|
FileWrite(handle, "Expectancy", performance.expectancy);
|
|
|
|
FileWrite(handle, "Average Win", performance.avg_win);
|
|
|
|
FileWrite(handle, "Average Loss", performance.avg_loss);
|
|
|
|
FileWrite(handle, "Max Drawdown %", performance.max_drawdown_percent);
|
|
|
|
FileWrite(handle, "Max Drawdown Amount", performance.max_drawdown_amount);
|
|
|
|
FileWrite(handle, "Sharpe Ratio", performance.sharpe_ratio);
|
|
|
|
FileWrite(handle, "Sortino Ratio", performance.sortino_ratio);
|
|
|
|
FileWrite(handle, "Calmar Ratio", performance.calmar_ratio);
|
|
|
|
FileWrite(handle, "Max Consecutive Wins", performance.max_consecutive_wins);
|
|
|
|
FileWrite(handle, "Max Consecutive Losses", performance.max_consecutive_losses);
|
|
|
|
|
|
|
|
FileClose(handle);
|
|
|
|
|
|
|
|
Log(LOG_INFO, "Performance exported to " + filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Backup important data |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::BackupData()
|
|
|
|
{
|
|
|
|
//--- Skip file operations in testing mode
|
|
|
|
if(m_is_testing)
|
|
|
|
return;
|
|
|
|
|
|
|
|
//--- Create backup filename with timestamp
|
|
|
|
string timestamp = TimeToString(TimeCurrent(), TIME_DATE);
|
|
|
|
StringReplace(timestamp, ".", "_");
|
|
|
|
|
|
|
|
string backup_dir = "Backups\\" + timestamp + "\\";
|
|
|
|
|
|
|
|
//--- Copy important files
|
|
|
|
if(FileIsExist(m_log_filename))
|
|
|
|
FileCopy(m_log_filename, 0, backup_dir + m_log_filename, FILE_REWRITE);
|
|
|
|
|
|
|
|
if(FileIsExist(m_report_filename))
|
|
|
|
FileCopy(m_report_filename, 0, backup_dir + m_report_filename, FILE_REWRITE);
|
|
|
|
|
|
|
|
Log(LOG_INFO, "Data backed up to " + backup_dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ... [Rest of the functions remain the same]
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Log trade action |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::LogTrade(const ManagedTrade &trade, string action)
|
|
|
|
{
|
|
|
|
string message = StringFormat("Trade #%d %s: %s %.2f lots at %.5f, P/L: %s",
|
|
|
|
trade.ticket, action,
|
|
|
|
(trade.type == POSITION_TYPE_BUY) ? "BUY" : "SELL",
|
|
|
|
trade.volume, trade.open_price,
|
|
|
|
FormatCurrency(trade.profit));
|
|
|
|
|
|
|
|
Log(LOG_INFO, message);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Log error |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::LogError(string function, int error_code)
|
|
|
|
{
|
|
|
|
string message = StringFormat("Error in %s: %d - %s",
|
|
|
|
function, error_code, GetLastError());
|
|
|
|
Log(LOG_ERROR, message);
|
|
|
|
}
|
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if news time |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CUtilities::IsNewsTime(int buffer_minutes)
|
|
|
|
{
|
|
|
|
datetime current_time = TimeCurrent();
|
|
|
|
|
|
|
|
for(int i = 0; i < m_news_count; i++)
|
|
|
|
{
|
|
|
|
if(MathAbs((double)(current_time - m_news_times[i])) < buffer_minutes * 60)
|
|
|
|
{
|
|
|
|
if(m_news_impacts[i] == "HIGH")
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if within trading hours |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CUtilities::IsTradingHours()
|
|
|
|
{
|
|
|
|
MqlDateTime time;
|
|
|
|
TimeCurrent(time);
|
|
|
|
|
|
|
|
//--- Skip weekends
|
|
|
|
if(time.day_of_week == 0 || time.day_of_week == 6)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
//--- Skip Friday after 20:00
|
|
|
|
if(time.day_of_week == 5 && time.hour >= 20)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
//--- Skip Sunday before 22:00
|
|
|
|
if(time.day_of_week == 0 && time.hour < 22)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if market is closed |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CUtilities::IsMarketClosed()
|
|
|
|
{
|
|
|
|
//--- Check if we're getting price updates
|
|
|
|
static datetime last_tick_time = 0;
|
|
|
|
datetime current_tick_time = (datetime)SymbolInfoInteger(_Symbol, SYMBOL_TIME);
|
|
|
|
|
|
|
|
if(current_tick_time == last_tick_time &&
|
|
|
|
TimeCurrent() - current_tick_time > 120) // No tick for 2 minutes
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
last_tick_time = current_tick_time;
|
|
|
|
return false;
|
|
|
|
}
|
2025-07-30 14:26:35 +01:00
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Format currency value |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CUtilities::FormatCurrency(double value, string currency)
|
|
|
|
{
|
|
|
|
string sign = value >= 0 ? "" : "-";
|
|
|
|
string formatted = sign + currency + " " + DoubleToString(MathAbs(value), 2);
|
|
|
|
return formatted;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Format percentage |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CUtilities::FormatPercent(double value, int digits)
|
|
|
|
{
|
|
|
|
return DoubleToString(value, digits) + "%";
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Format pips |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CUtilities::FormatPips(double points)
|
|
|
|
{
|
|
|
|
double pips = CalculatePips(points);
|
|
|
|
return DoubleToString(pips, 1) + " pips";
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate pips from points |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CUtilities::CalculatePips(double price_difference)
|
|
|
|
{
|
|
|
|
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
|
|
|
|
|
|
|
double pip_value = (digits == 3 || digits == 5) ? point * 10 : point;
|
|
|
|
|
|
|
|
return price_difference / pip_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get spread in pips |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CUtilities::GetSpreadInPips()
|
|
|
|
{
|
|
|
|
double spread_points = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) *
|
|
|
|
SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
return CalculatePips(spread_points);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Send notification |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CUtilities::SendNotification(string message, bool email, bool push, bool alert)
|
|
|
|
{
|
|
|
|
if(alert)
|
|
|
|
Alert(message);
|
|
|
|
|
|
|
|
if(email && TerminalInfoInteger(TERMINAL_EMAIL_ENABLED))
|
|
|
|
SendMail("Risk Management Alert", message);
|
|
|
|
|
|
|
|
if(push && TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))
|
|
|
|
SendNotification(message);
|
|
|
|
|
|
|
|
Log(LOG_INFO, "Notification sent: " + message);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get time string |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CUtilities::GetTimeString(datetime time)
|
|
|
|
{
|
|
|
|
return TimeToString(time, TIME_DATE|TIME_MINUTES|TIME_SECONDS);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get log prefix |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CUtilities::GetLogPrefix(ENUM_LOG_LEVEL level)
|
|
|
|
{
|
|
|
|
switch(level)
|
|
|
|
{
|
|
|
|
case LOG_ERROR: return "[ERROR]";
|
|
|
|
case LOG_WARNING: return "[WARN ]";
|
|
|
|
case LOG_INFO: return "[INFO ]";
|
|
|
|
case LOG_DEBUG: return "[DEBUG]";
|
|
|
|
default: return "[?????]";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get uninit reason text |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CUtilities::GetUninitReasonText(int reason)
|
|
|
|
{
|
|
|
|
switch(reason)
|
|
|
|
{
|
|
|
|
case REASON_PROGRAM: return "Program terminated";
|
|
|
|
case REASON_REMOVE: return "Program removed from chart";
|
|
|
|
case REASON_RECOMPILE: return "Program recompiled";
|
|
|
|
case REASON_CHARTCHANGE: return "Symbol or timeframe changed";
|
|
|
|
case REASON_CHARTCLOSE: return "Chart closed";
|
|
|
|
case REASON_PARAMETERS: return "Input parameters changed";
|
|
|
|
case REASON_ACCOUNT: return "Account changed";
|
|
|
|
case REASON_TEMPLATE: return "New template applied";
|
|
|
|
case REASON_INITFAILED: return "OnInit() failed";
|
|
|
|
case REASON_CLOSE: return "Terminal closed";
|
|
|
|
default: return "Unknown reason";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Normalize lot size |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CUtilities::NormalizeLots(double lots)
|
|
|
|
{
|
|
|
|
double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
|
|
double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
|
|
double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
|
|
|
|
//--- Normalize to lot step
|
|
|
|
lots = MathRound(lots / lot_step) * lot_step;
|
|
|
|
|
|
|
|
//--- Apply limits
|
|
|
|
lots = MathMax(lots, min_lot);
|
|
|
|
lots = MathMin(lots, max_lot);
|
|
|
|
|
|
|
|
return NormalizeDouble(lots, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Normalize price |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CUtilities::NormalizePrice(double price)
|
|
|
|
{
|
|
|
|
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
|
|
|
return NormalizeDouble(price, digits);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check for new bar |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CUtilities::IsNewBar(ENUM_TIMEFRAMES timeframe)
|
|
|
|
{
|
|
|
|
static datetime last_bar_time = 0;
|
|
|
|
datetime current_bar_time = iTime(_Symbol, timeframe, 0);
|
|
|
|
|
|
|
|
if(current_bar_time != last_bar_time)
|
|
|
|
{
|
|
|
|
last_bar_time = current_bar_time;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check terminal connection |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CUtilities::CheckTerminalConnection()
|
|
|
|
{
|
|
|
|
if(!TerminalInfoInteger(TERMINAL_CONNECTED))
|
|
|
|
{
|
|
|
|
Log(LOG_ERROR, "Terminal not connected to broker");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(IsMarketClosed())
|
|
|
|
{
|
|
|
|
Log(LOG_WARNING, "Market appears to be closed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check auto trading |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CUtilities::CheckAutoTrading()
|
|
|
|
{
|
|
|
|
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
|
|
|
|
{
|
|
|
|
Log(LOG_ERROR, "Auto trading not allowed in terminal");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
|
|
|
|
{
|
|
|
|
Log(LOG_ERROR, "Auto trading not allowed for this EA");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
|
|
|
|
{
|
|
|
|
Log(LOG_ERROR, "Auto trading not allowed for this account");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-07-30 14:26:35 +01:00
|
|
|
//| Calculate commission for a position from deal history |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-07-30 14:26:35 +01:00
|
|
|
double CUtilities::CalculatePositionCommission(ulong position_ticket)
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-07-30 14:26:35 +01:00
|
|
|
double total_commission = 0;
|
2025-07-20 22:38:24 +01:00
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
// Select position to get its ID
|
|
|
|
if(!PositionSelectByTicket(position_ticket))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
long position_id = PositionGetInteger(POSITION_IDENTIFIER);
|
2025-07-20 22:38:24 +01:00
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
// Iterate through history deals
|
|
|
|
if(HistorySelectByPosition(position_id))
|
2025-07-20 22:38:24 +01:00
|
|
|
{
|
2025-07-30 14:26:35 +01:00
|
|
|
int deals = HistoryDealsTotal();
|
|
|
|
for(int i = 0; i < deals; i++)
|
|
|
|
{
|
|
|
|
ulong deal_ticket = HistoryDealGetTicket(i);
|
|
|
|
if(deal_ticket > 0)
|
|
|
|
{
|
|
|
|
total_commission += HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION);
|
|
|
|
}
|
|
|
|
}
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
2025-07-30 14:26:35 +01:00
|
|
|
return total_commission;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Format duration |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CUtilities::FormatDuration(int seconds)
|
|
|
|
{
|
|
|
|
int days = seconds / 86400;
|
|
|
|
int hours = (seconds % 86400) / 3600;
|
|
|
|
int minutes = (seconds % 3600) / 60;
|
|
|
|
|
|
|
|
string result = "";
|
|
|
|
if(days > 0) result += IntegerToString(days) + "d ";
|
|
|
|
if(hours > 0) result += IntegerToString(hours) + "h ";
|
|
|
|
if(minutes > 0) result += IntegerToString(minutes) + "m";
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get day of week string |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CUtilities::GetDayOfWeek()
|
|
|
|
{
|
|
|
|
MqlDateTime time;
|
|
|
|
TimeCurrent(time);
|
|
|
|
|
|
|
|
string days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
|
|
|
|
"Thursday", "Friday", "Saturday"};
|
|
|
|
|
|
|
|
return days[time.day_of_week];
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // UTILITIES_MQH
|
|
|
|
//+------------------------------------------------------------------+
|