579 lines
No EOL
43 KiB
MQL5
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
|
|
//+------------------------------------------------------------------+ |