mql5/Experts/Advisors/Modules/MultiTradeReporter.mqh
darashikoh 9afaf5efaf Summary
I've provided you with a comprehensive solution for managing your commercial EA trades using the Risk Management Overlay EA, despite MT5's one-EA-per-chart limitation:
1. Complete Management Guide

4 different methods to run both EAs
Detailed setup instructions for each method
Configuration examples for different scenarios

2. Quick Setup Card

5-minute setup for the most common scenario
Essential configuration templates
Visual dashboard examples

3. EA Detection Utility

Automatically detects all running EAs
Shows magic numbers and trade counts
Provides configuration suggestions

4. Method Selection Flowchart

Simple decision tree to choose the best method
Comparison of difficulty and resources
Specific recommendations based on your needs

Recommended Approach
For your commercial EA on forex pairs, I recommend:

Start with Method 1 (Different Timeframes):

Put your commercial EA on H1 chart
Put Risk Manager on M15 chart of same pair
This is the simplest and most reli
2025-08-01 10:56:06 +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
//+------------------------------------------------------------------+