//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+ //| 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 = "\n\n\n"; html += "Multi-Source Trading Report\n"; html += "\n\n\n"; //--- Report header html += "

Multi-Source Trading Report

\n"; html += "

Generated: " + TimeToString(TimeCurrent()) + "

\n"; //--- Account summary html += "

Account Summary

\n"; html += "\n"; html += "\n"; html += "\n"; html += "\n"; html += "
Balance$" + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + "
Equity$" + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "
Free Margin$" + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2) + "
\n"; //--- Active trades table html += "

Active Trades

\n"; html += "\n"; html += ""; html += "\n"; int trade_count = ArraySize(trades); for(int i = 0; i < trade_count; i++) { string pl_class = trades[i].profit >= 0 ? "profit" : "loss"; html += ""; html += ""; html += ""; html += ""; html += ""; html += ""; html += ""; html += ""; html += ""; html += ""; html += "\n"; } html += "
TicketSymbolTypeVolumeEntryCurrentP/LRiskSource
" + IntegerToString(trades[i].ticket) + "" + trades[i].symbol + "" + ((trades[i].type == POSITION_TYPE_BUY) ? "BUY" : "SELL") + "" + DoubleToString(trades[i].volume, 2) + "" + DoubleToString(trades[i].open_price, 5) + "" + DoubleToString(SymbolInfoDouble(trades[i].symbol, SYMBOL_BID), 5) + "$" + DoubleToString(trades[i].profit, 2) + "" + DoubleToString(trades[i].risk_percent, 2) + "%" + GetSourceName((long)trades[i].magic) + "
\n"; //--- Footer html += "\n"; 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 //+------------------------------------------------------------------+