mirror of
https://github.com/FxPouya/FxMathQuantWebApp.git
synced 2026-07-30 11:37:43 +00:00
273 lines
9.7 KiB
JavaScript
273 lines
9.7 KiB
JavaScript
|
|
/**
|
|||
|
|
* HTML Report Generator - Creates interactive performance reports
|
|||
|
|
*/
|
|||
|
|
class ReportGenerator {
|
|||
|
|
constructor(strategy, strategyName) {
|
|||
|
|
this.strategy = strategy;
|
|||
|
|
this.name = strategyName || 'Strategy Report';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
generate() {
|
|||
|
|
const m = this.strategy.metrics;
|
|||
|
|
|
|||
|
|
return `<!DOCTYPE html>
|
|||
|
|
<html lang="en">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>${this.name} - Performance Report</title>
|
|||
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|||
|
|
<style>
|
|||
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|||
|
|
body {
|
|||
|
|
font-family: 'Inter', -apple-system, sans-serif;
|
|||
|
|
background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%);
|
|||
|
|
color: #ffffff;
|
|||
|
|
padding: 40px 20px;
|
|||
|
|
}
|
|||
|
|
.container { max-width: 1200px; margin: 0 auto; }
|
|||
|
|
h1 { font-size: 32px; margin-bottom: 10px; color: #667eea; }
|
|||
|
|
.subtitle { color: #a0aec0; margin-bottom: 30px; }
|
|||
|
|
.metrics-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|||
|
|
gap: 20px;
|
|||
|
|
margin-bottom: 40px;
|
|||
|
|
}
|
|||
|
|
.metric-card {
|
|||
|
|
background: #1a1f3a;
|
|||
|
|
padding: 20px;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
border: 1px solid #2d3748;
|
|||
|
|
}
|
|||
|
|
.metric-label {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #718096;
|
|||
|
|
text-transform: uppercase;
|
|||
|
|
letter-spacing: 1px;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
.metric-value {
|
|||
|
|
font-size: 28px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
}
|
|||
|
|
.positive { color: #48bb78; }
|
|||
|
|
.negative { color: #f56565; }
|
|||
|
|
.neutral { color: #667eea; }
|
|||
|
|
.chart-container {
|
|||
|
|
background: #1a1f3a;
|
|||
|
|
padding: 30px;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
margin-bottom: 30px;
|
|||
|
|
border: 1px solid #2d3748;
|
|||
|
|
}
|
|||
|
|
.rules-section {
|
|||
|
|
background: #1a1f3a;
|
|||
|
|
padding: 30px;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
margin-bottom: 30px;
|
|||
|
|
border: 1px solid #2d3748;
|
|||
|
|
}
|
|||
|
|
.rule {
|
|||
|
|
padding: 10px;
|
|||
|
|
margin: 5px 0;
|
|||
|
|
background: #0a0e27;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
font-family: 'Courier New', monospace;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
table {
|
|||
|
|
width: 100%;
|
|||
|
|
border-collapse: collapse;
|
|||
|
|
background: #1a1f3a;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
th, td {
|
|||
|
|
padding: 12px;
|
|||
|
|
text-align: left;
|
|||
|
|
border-bottom: 1px solid #2d3748;
|
|||
|
|
}
|
|||
|
|
th {
|
|||
|
|
background: #0a0e27;
|
|||
|
|
color: #a0aec0;
|
|||
|
|
font-weight: 600;
|
|||
|
|
text-transform: uppercase;
|
|||
|
|
font-size: 11px;
|
|||
|
|
}
|
|||
|
|
tr:hover { background: #252b4a; }
|
|||
|
|
.footer {
|
|||
|
|
text-align: center;
|
|||
|
|
margin-top: 40px;
|
|||
|
|
color: #718096;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="container">
|
|||
|
|
<h1>${this.name}</h1>
|
|||
|
|
<p class="subtitle">Generated by FxMathQuant Web - AI-Powered Strategy Generator</p>
|
|||
|
|
|
|||
|
|
<div class="metrics-grid">
|
|||
|
|
<div class="metric-card">
|
|||
|
|
<div class="metric-label">Profit Factor</div>
|
|||
|
|
<div class="metric-value ${m.profitFactor >= 1.5 ? 'positive' : 'negative'}">
|
|||
|
|
${m.profitFactor.toFixed(2)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="metric-card">
|
|||
|
|
<div class="metric-label">Win Rate</div>
|
|||
|
|
<div class="metric-value neutral">${m.winRate.toFixed(1)}%</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="metric-card">
|
|||
|
|
<div class="metric-label">Total Trades</div>
|
|||
|
|
<div class="metric-value neutral">${m.totalTrades}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="metric-card">
|
|||
|
|
<div class="metric-label">Max Drawdown</div>
|
|||
|
|
<div class="metric-value negative">${m.maxDrawdown.toFixed(2)}%</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="metric-card">
|
|||
|
|
<div class="metric-label">Total Profit</div>
|
|||
|
|
<div class="metric-value ${m.totalProfit >= 0 ? 'positive' : 'negative'}">
|
|||
|
|
$${m.totalProfit.toFixed(2)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="metric-card">
|
|||
|
|
<div class="metric-label">Final Balance</div>
|
|||
|
|
<div class="metric-value positive">$${m.finalBalance.toFixed(2)}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="metric-card">
|
|||
|
|
<div class="metric-label">Avg Win</div>
|
|||
|
|
<div class="metric-value positive">$${m.avgWin.toFixed(2)}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="metric-card">
|
|||
|
|
<div class="metric-label">Avg Loss</div>
|
|||
|
|
<div class="metric-value negative">$${Math.abs(m.avgLoss).toFixed(2)}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="chart-container">
|
|||
|
|
<h2 style="margin-bottom: 20px;">Equity Curve</h2>
|
|||
|
|
<canvas id="equityChart"></canvas>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="rules-section">
|
|||
|
|
<h2 style="margin-bottom: 20px;">Strategy Rules</h2>
|
|||
|
|
<p style="margin-bottom: 15px; color: #a0aec0;">BUY when ALL of the following conditions are true:</p>
|
|||
|
|
${this.generateRulesHTML()}
|
|||
|
|
<div style="margin-top: 20px; padding: 15px; background: #0a0e27; border-radius: 6px;">
|
|||
|
|
<strong>Parameters:</strong><br>
|
|||
|
|
ATR Period: ${this.strategy.atrPeriod} |
|
|||
|
|
SL Multiplier: ${this.strategy.slMultiplier.toFixed(2)} |
|
|||
|
|
TP Multiplier: ${this.strategy.tpMultiplier.toFixed(2)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
${this.generateTradesTable()}
|
|||
|
|
|
|||
|
|
<div class="footer">
|
|||
|
|
<p>© 2025 FxMathQuant. All rights reserved.</p>
|
|||
|
|
<p>This report was generated automatically by AI-powered genetic algorithms.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
// Equity Curve Chart
|
|||
|
|
const ctx = document.getElementById('equityChart').getContext('2d');
|
|||
|
|
new Chart(ctx, {
|
|||
|
|
type: 'line',
|
|||
|
|
data: {
|
|||
|
|
labels: ${JSON.stringify(m.equity.map((_, i) => i))},
|
|||
|
|
datasets: [{
|
|||
|
|
label: 'Account Balance',
|
|||
|
|
data: ${JSON.stringify(m.equity)},
|
|||
|
|
borderColor: '#667eea',
|
|||
|
|
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
|||
|
|
tension: 0.4,
|
|||
|
|
fill: true
|
|||
|
|
}]
|
|||
|
|
},
|
|||
|
|
options: {
|
|||
|
|
responsive: true,
|
|||
|
|
plugins: {
|
|||
|
|
legend: { display: false },
|
|||
|
|
tooltip: {
|
|||
|
|
backgroundColor: '#1a1f3a',
|
|||
|
|
titleColor: '#fff',
|
|||
|
|
bodyColor: '#a0aec0',
|
|||
|
|
borderColor: '#2d3748',
|
|||
|
|
borderWidth: 1
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
scales: {
|
|||
|
|
y: {
|
|||
|
|
beginAtZero: false,
|
|||
|
|
grid: { color: '#2d3748' },
|
|||
|
|
ticks: { color: '#a0aec0' }
|
|||
|
|
},
|
|||
|
|
x: {
|
|||
|
|
grid: { color: '#2d3748' },
|
|||
|
|
ticks: { color: '#a0aec0' }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
generateRulesHTML() {
|
|||
|
|
return this.strategy.rules.map((rule, index) => {
|
|||
|
|
let ruleText = '';
|
|||
|
|
if (rule.type === 'simple') {
|
|||
|
|
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${rule.operator} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
|||
|
|
} else {
|
|||
|
|
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${rule.operator} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
|||
|
|
}
|
|||
|
|
return `<div class="rule">${index + 1}. ${ruleText}</div>`;
|
|||
|
|
}).join('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
generateTradesTable() {
|
|||
|
|
const trades = this.strategy.metrics.trades || [];
|
|||
|
|
if (trades.length === 0) return '';
|
|||
|
|
|
|||
|
|
// Show first 50 trades
|
|||
|
|
const displayTrades = trades.slice(0, 50);
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div style="background: #1a1f3a; padding: 30px; border-radius: 12px; border: 1px solid #2d3748;">
|
|||
|
|
<h2 style="margin-bottom: 20px;">Trade History ${trades.length > 50 ? '(First 50 trades)' : ''}</h2>
|
|||
|
|
<table>
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th>#</th>
|
|||
|
|
<th>Type</th>
|
|||
|
|
<th>Entry</th>
|
|||
|
|
<th>Exit</th>
|
|||
|
|
<th>Profit</th>
|
|||
|
|
<th>Reason</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
${displayTrades.map((trade, i) => `
|
|||
|
|
<tr>
|
|||
|
|
<td>${i + 1}</td>
|
|||
|
|
<td>${trade.type}</td>
|
|||
|
|
<td>${trade.entry.toFixed(5)}</td>
|
|||
|
|
<td>${trade.exit.toFixed(5)}</td>
|
|||
|
|
<td class="${trade.profit >= 0 ? 'positive' : 'negative'}">
|
|||
|
|
$${trade.profit.toFixed(2)}
|
|||
|
|
</td>
|
|||
|
|
<td>${trade.reason.replace('_', ' ')}</td>
|
|||
|
|
</tr>
|
|||
|
|
`).join('')}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>`;
|
|||
|
|
}
|
|||
|
|
}
|