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

579 lines
No EOL
43 KiB
MQL5

//+------------------------------------------------------------------+
//| MultiTradeReporter.mqh |
//| Multi-Source Trade Reporting Module |
//| Fixed all compilation errors |
//+------------------------------------------------------------------+
#ifndef MULTI_TRADE_REPORTER_MQH
#define MULTI_TRADE_REPORTER_MQH
#include "DataTypes.mqh"
#include <Files/FileTxt.mqh>
//+------------------------------------------------------------------+
//| Structure for correlation matrix |
//+------------------------------------------------------------------+
struct CorrelationPair
{
int index1;
int index2;
double correlation;
};
//+------------------------------------------------------------------+
//| Multi-Trade Reporter Class |
//+------------------------------------------------------------------+
class CMultiTradeReporter
{
private:
//--- Configuration
string m_report_path;
bool m_csv_export;
bool m_html_export;
int m_report_frequency;
//--- Tracking
datetime m_last_report_time;
int m_report_count;
//--- Helper methods
void GenerateHTMLReport(const ManagedTrade &trades[], const PerformanceMetrics &performance);
void GenerateCSVReport(const ManagedTrade &trades[], const PerformanceMetrics &performance);
void AnalyzeTradesBySource(const ManagedTrade &trades[], string &output);
void AnalyzeCorrelations(const ManagedTrade &trades[], string &output);
double CalculateCorrelation(string symbol1, string symbol2, int period);
string GetSourceName(long magic); // Added declaration
public:
CMultiTradeReporter();
~CMultiTradeReporter();
//--- Initialization
bool Initialize(string report_path);
//--- Main reporting
void GenerateFullReport(const ManagedTrade &trades[], const PerformanceMetrics &performance);
void GenerateQuickSummary(const ManagedTrade &trades[]);
void ExportTradeHistory(const ManagedTrade &trades[]);
//--- Configuration
void SetExportFormats(bool csv, bool html) { m_csv_export = csv; m_html_export = html; }
void SetReportFrequency(int minutes) { m_report_frequency = minutes; }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CMultiTradeReporter::CMultiTradeReporter()
{
m_report_path = "Reports\\";
m_csv_export = true;
m_html_export = true;
m_report_frequency = 60; // Hourly by default
m_last_report_time = 0;
m_report_count = 0;
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMultiTradeReporter::~CMultiTradeReporter()
{
}
//+------------------------------------------------------------------+
//| Initialize reporter |
//+------------------------------------------------------------------+
bool CMultiTradeReporter::Initialize(string report_path)
{
m_report_path = report_path;
//--- Create directory if needed
if(!FileIsExist(m_report_path))
{
// Note: MQL5 cannot create directories, user must create manually
Print("MultiTradeReporter: Please create directory: ", m_report_path);
}
Print("MultiTradeReporter initialized: Path=", m_report_path);
return true;
}
//+------------------------------------------------------------------+
//| Generate full report |
//+------------------------------------------------------------------+
void CMultiTradeReporter::GenerateFullReport(const ManagedTrade &trades[], const PerformanceMetrics &performance)
{
//--- Check if it's time for report
if(TimeCurrent() - m_last_report_time < m_report_frequency * 60)
return;
m_report_count++;
string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES);
StringReplace(timestamp, ":", "_");
StringReplace(timestamp, " ", "_");
//--- Text report
string report = "=== MULTI-SOURCE TRADING REPORT ===\n";
report += "Generated: " + TimeToString(TimeCurrent()) + "\n";
report += "Report #" + IntegerToString(m_report_count) + "\n\n";
//--- Account overview
report += "ACCOUNT OVERVIEW\n";
report += "================\n";
report += "Balance: $" + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + "\n";
report += "Equity: $" + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "\n";
report += "Free Margin: $" + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2) + "\n";
report += "Margin Level: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL), 2) + "%\n\n";
//--- Performance summary
report += "PERFORMANCE SUMMARY\n";
report += "==================\n";
report += "Total Trades: " + IntegerToString(performance.total_trades) + "\n";
report += "Win Rate: " + DoubleToString(performance.win_rate, 2) + "%\n";
report += "Profit Factor: " + DoubleToString(performance.profit_factor, 2) + "\n";
report += "Net Profit: $" + DoubleToString(performance.net_profit, 2) + "\n";
report += "Max Drawdown: " + DoubleToString(performance.max_drawdown_percent, 2) + "%\n";
report += "Sharpe Ratio: " + DoubleToString(performance.sharpe_ratio, 2) + "\n\n";
//--- Active trades by source
string source_analysis;
AnalyzeTradesBySource(trades, source_analysis);
report += source_analysis;
//--- Correlation analysis
string correlation_analysis;
AnalyzeCorrelations(trades, correlation_analysis);
report += correlation_analysis;
//--- Risk analysis
report += "RISK ANALYSIS\n";
report += "=============\n";
double total_risk = 0;
double max_risk_trade = 0;
int high_risk_count = 0;
int trade_count = ArraySize(trades);
for(int i = 0; i < trade_count; i++)
{
total_risk += trades[i].risk_percent;
if(trades[i].risk_percent > max_risk_trade)
max_risk_trade = trades[i].risk_percent;
if(trades[i].risk_percent > 2.0)
high_risk_count++;
}
report += "Total Portfolio Risk: " + DoubleToString(total_risk, 2) + "%\n";
report += "Maximum Single Trade Risk: " + DoubleToString(max_risk_trade, 2) + "%\n";
report += "High Risk Trades (>2%): " + IntegerToString(high_risk_count) + "\n";
report += "Average Risk per Trade: " + DoubleToString(total_risk / MathMax(trade_count, 1), 2) + "%\n\n";
//--- Save reports
string filename = "MultiSource_Report_" + timestamp;
//--- Text file
int handle = FileOpen(m_report_path + filename + ".txt", FILE_WRITE|FILE_TXT|FILE_ANSI);
if(handle != INVALID_HANDLE)
{
FileWrite(handle, report);
FileClose(handle);
}
//--- Additional formats
if(m_csv_export)
GenerateCSVReport(trades, performance);
if(m_html_export)
GenerateHTMLReport(trades, performance);
m_last_report_time = TimeCurrent();
Print("MultiTradeReporter: Generated report #", m_report_count);
}
//+------------------------------------------------------------------+
//| Analyze trades by source |
//+------------------------------------------------------------------+
void CMultiTradeReporter::AnalyzeTradesBySource(const ManagedTrade &trades[], string &output)
{
output = "TRADES BY SOURCE\n";
output += "===============\n";
//--- Group trades by magic number
struct SourceGroup
{
long magic;
string name;
int count;
double volume;
double profit;
double risk;
};
SourceGroup groups[];
int group_count = 0;
int trade_count = ArraySize(trades);
for(int i = 0; i < trade_count; i++)
{
long magic = (long)trades[i].magic;
bool found = false;
for(int j = 0; j < group_count; j++)
{
if(groups[j].magic == magic)
{
groups[j].count++;
groups[j].volume += trades[i].volume;
groups[j].profit += trades[i].profit;
groups[j].risk += trades[i].risk_percent;
found = true;
break;
}
}
if(!found)
{
ArrayResize(groups, group_count + 1);
groups[group_count].magic = magic;
groups[group_count].name = GetSourceName(magic);
groups[group_count].count = 1;
groups[group_count].volume = trades[i].volume;
groups[group_count].profit = trades[i].profit;
groups[group_count].risk = trades[i].risk_percent;
group_count++;
}
}
//--- Output grouped data
for(int i = 0; i < group_count; i++)
{
output += "\n" + groups[i].name + " (Magic: " + IntegerToString(groups[i].magic) + ")\n";
output += " Trades: " + IntegerToString(groups[i].count) + "\n";
output += " Volume: " + DoubleToString(groups[i].volume, 2) + " lots\n";
output += " P/L: $" + DoubleToString(groups[i].profit, 2) + "\n";
output += " Risk: " + DoubleToString(groups[i].risk, 2) + "%\n";
}
output += "\n";
}
//+------------------------------------------------------------------+
//| Get source name from magic number |
//+------------------------------------------------------------------+
string CMultiTradeReporter::GetSourceName(long magic)
{
switch((int)magic)
{
case 0: return "Manual Trading";
case 12345: return "Risk Manager EA";
default:
{
if(magic >= 10000 && magic < 20000)
return "External EA";
else if(magic >= 80000 && magic < 90000)
return "Copy Service";
else
return "Unknown Source";
}
}
}
//+------------------------------------------------------------------+
//| Analyze position correlations |
//+------------------------------------------------------------------+
void CMultiTradeReporter::AnalyzeCorrelations(const ManagedTrade &trades[], string &output)
{
output = "POSITION CORRELATIONS\n";
output += "===================\n";
//--- Get unique symbols
string symbols[];
int symbol_count = 0;
int trade_count = ArraySize(trades);
for(int i = 0; i < trade_count; i++)
{
bool found = false;
for(int j = 0; j < symbol_count; j++)
{
if(symbols[j] == trades[i].symbol)
{
found = true;
break;
}
}
if(!found && symbol_count < 20) // Limit to 20 symbols
{
ArrayResize(symbols, symbol_count + 1);
symbols[symbol_count] = trades[i].symbol;
symbol_count++;
}
}
//--- Calculate correlations between pairs
if(symbol_count >= 2)
{
output += "\nHigh Correlations (>0.7):\n";
// Use a 1D array to simulate 2D array
double correlations[];
int corr_size = symbol_count * symbol_count;
ArrayResize(correlations, corr_size);
ArrayInitialize(correlations, 0.0);
// Helper function to access 2D array as 1D
#define CORR_INDEX(i,j) ((i)*symbol_count+(j))
for(int i = 0; i < symbol_count; i++)
{
for(int j = i + 1; j < symbol_count; j++)
{
double corr = CalculateCorrelation(symbols[i], symbols[j], 100);
correlations[CORR_INDEX(i,j)] = corr;
correlations[CORR_INDEX(j,i)] = corr; // Symmetric
if(MathAbs(corr) > 0.7)
{
output += symbols[i] + " vs " + symbols[j] + ": " +
DoubleToString(corr, 3) + "\n";
}
}
}
//--- Correlation risk warning
int high_corr_positions = 0;
for(int i = 0; i < trade_count; i++)
{
for(int j = i + 1; j < trade_count; j++)
{
// Find symbol indices
int idx1 = -1, idx2 = -1;
for(int k = 0; k < symbol_count; k++)
{
if(symbols[k] == trades[i].symbol) idx1 = k;
if(symbols[k] == trades[j].symbol) idx2 = k;
}
if(idx1 >= 0 && idx2 >= 0 && idx1 != idx2)
{
double corr = correlations[CORR_INDEX(idx1,idx2)];
if(MathAbs(corr) > 0.8 && trades[i].type == trades[j].type)
{
high_corr_positions++;
}
}
}
}
#undef CORR_INDEX
if(high_corr_positions > 0)
{
output += "\n⚠️ WARNING: " + IntegerToString(high_corr_positions) +
" position pairs with high correlation!\n";
}
}
output += "\n";
}
//+------------------------------------------------------------------+
//| Calculate correlation between two symbols |
//+------------------------------------------------------------------+
double CMultiTradeReporter::CalculateCorrelation(string symbol1, string symbol2, int period)
{
//--- Simplified correlation calculation
double prices1[], prices2[];
ArrayResize(prices1, period);
ArrayResize(prices2, period);
//--- Get price data
for(int i = 0; i < period; i++)
{
prices1[i] = iClose(symbol1, PERIOD_H1, i);
prices2[i] = iClose(symbol2, PERIOD_H1, i);
}
//--- Calculate returns
double returns1[], returns2[];
ArrayResize(returns1, period - 1);
ArrayResize(returns2, period - 1);
for(int i = 0; i < period - 1; i++)
{
returns1[i] = (prices1[i] - prices1[i + 1]) / prices1[i + 1];
returns2[i] = (prices2[i] - prices2[i + 1]) / prices2[i + 1];
}
//--- Calculate correlation
double sum1 = 0, sum2 = 0, sum12 = 0, sum1sq = 0, sum2sq = 0;
int n = period - 1;
for(int i = 0; i < n; i++)
{
sum1 += returns1[i];
sum2 += returns2[i];
sum12 += returns1[i] * returns2[i];
sum1sq += returns1[i] * returns1[i];
sum2sq += returns2[i] * returns2[i];
}
double numerator = n * sum12 - sum1 * sum2;
double denominator = MathSqrt((n * sum1sq - sum1 * sum1) * (n * sum2sq - sum2 * sum2));
if(denominator == 0) return 0;
return numerator / denominator;
}
//+------------------------------------------------------------------+
//| Generate CSV report |
//+------------------------------------------------------------------+
void CMultiTradeReporter::GenerateCSVReport(const ManagedTrade &trades[], const PerformanceMetrics &performance)
{
string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES);
StringReplace(timestamp, ":", "_");
StringReplace(timestamp, " ", "_");
string filename = m_report_path + "Trades_" + timestamp + ".csv";
int handle = FileOpen(filename, FILE_WRITE|FILE_CSV|FILE_ANSI, ',');
if(handle == INVALID_HANDLE) return;
//--- Header
FileWrite(handle, "Ticket", "Symbol", "Type", "Volume", "Entry", "Current",
"SL", "TP", "Profit", "Risk%", "RMultiple", "Magic", "Source", "EntryReason");
//--- Trade data
int trade_count = ArraySize(trades);
for(int i = 0; i < trade_count; i++)
{
FileWrite(handle,
trades[i].ticket,
trades[i].symbol,
(trades[i].type == POSITION_TYPE_BUY) ? "BUY" : "SELL",
trades[i].volume,
trades[i].open_price,
SymbolInfoDouble(trades[i].symbol, SYMBOL_BID),
trades[i].sl,
trades[i].tp,
trades[i].profit,
trades[i].risk_percent,
trades[i].r_multiple,
trades[i].magic,
GetSourceName((long)trades[i].magic),
trades[i].entry_reason); // Changed from 'comment' to 'entry_reason'
}
FileClose(handle);
}
//+------------------------------------------------------------------+
//| Generate HTML report |
//+------------------------------------------------------------------+
void CMultiTradeReporter::GenerateHTMLReport(const ManagedTrade &trades[], const PerformanceMetrics &performance)
{
string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES);
StringReplace(timestamp, ":", "_");
StringReplace(timestamp, " ", "_");
string filename = m_report_path + "Report_" + timestamp + ".html";
int handle = FileOpen(filename, FILE_WRITE|FILE_TXT|FILE_ANSI);
if(handle == INVALID_HANDLE) return;
//--- HTML header
string html = "<!DOCTYPE html>\n<html>\n<head>\n";
html += "<title>Multi-Source Trading Report</title>\n";
html += "<style>\n";
html += "body { font-family: Arial, sans-serif; margin: 20px; }\n";
html += "table { border-collapse: collapse; width: 100%; margin: 20px 0; }\n";
html += "th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n";
html += "th { background-color: #4CAF50; color: white; }\n";
html += "tr:nth-child(even) { background-color: #f2f2f2; }\n";
html += ".profit { color: green; font-weight: bold; }\n";
html += ".loss { color: red; font-weight: bold; }\n";
html += ".warning { background-color: #fff3cd; padding: 10px; margin: 10px 0; }\n";
html += "</style>\n</head>\n<body>\n";
//--- Report header
html += "<h1>Multi-Source Trading Report</h1>\n";
html += "<p>Generated: " + TimeToString(TimeCurrent()) + "</p>\n";
//--- Account summary
html += "<h2>Account Summary</h2>\n";
html += "<table>\n";
html += "<tr><td>Balance</td><td>$" + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + "</td></tr>\n";
html += "<tr><td>Equity</td><td>$" + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "</td></tr>\n";
html += "<tr><td>Free Margin</td><td>$" + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2) + "</td></tr>\n";
html += "</table>\n";
//--- Active trades table
html += "<h2>Active Trades</h2>\n";
html += "<table>\n";
html += "<tr><th>Ticket</th><th>Symbol</th><th>Type</th><th>Volume</th>";
html += "<th>Entry</th><th>Current</th><th>P/L</th><th>Risk</th><th>Source</th></tr>\n";
int trade_count = ArraySize(trades);
for(int i = 0; i < trade_count; i++)
{
string pl_class = trades[i].profit >= 0 ? "profit" : "loss";
html += "<tr>";
html += "<td>" + IntegerToString(trades[i].ticket) + "</td>";
html += "<td>" + trades[i].symbol + "</td>";
html += "<td>" + ((trades[i].type == POSITION_TYPE_BUY) ? "BUY" : "SELL") + "</td>";
html += "<td>" + DoubleToString(trades[i].volume, 2) + "</td>";
html += "<td>" + DoubleToString(trades[i].open_price, 5) + "</td>";
html += "<td>" + DoubleToString(SymbolInfoDouble(trades[i].symbol, SYMBOL_BID), 5) + "</td>";
html += "<td class='" + pl_class + "'>$" + DoubleToString(trades[i].profit, 2) + "</td>";
html += "<td>" + DoubleToString(trades[i].risk_percent, 2) + "%</td>";
html += "<td>" + GetSourceName((long)trades[i].magic) + "</td>";
html += "</tr>\n";
}
html += "</table>\n";
//--- Footer
html += "</body>\n</html>";
FileWrite(handle, html);
FileClose(handle);
}
//+------------------------------------------------------------------+
//| Generate quick summary |
//+------------------------------------------------------------------+
void CMultiTradeReporter::GenerateQuickSummary(const ManagedTrade &trades[])
{
int trade_count = ArraySize(trades);
double total_profit = 0;
double total_risk = 0;
for(int i = 0; i < trade_count; i++)
{
total_profit += trades[i].profit;
total_risk += trades[i].risk_percent;
}
string summary = StringFormat("\n=== QUICK SUMMARY ===\n" +
"Active Trades: %d\n" +
"Total P/L: $%.2f\n" +
"Total Risk: %.2f%%\n" +
"==================\n",
trade_count, total_profit, total_risk);
Print(summary);
}
//+------------------------------------------------------------------+
//| Export trade history - additional method |
//+------------------------------------------------------------------+
void CMultiTradeReporter::ExportTradeHistory(const ManagedTrade &trades[])
{
// This method can be used for exporting closed trade history
// Implementation depends on your specific needs
GenerateCSVReport(trades, PerformanceMetrics());
}
#endif // MULTI_TRADE_REPORTER_MQH
//+------------------------------------------------------------------+