`;
renderCharts(result);
}
function renderCharts(result) {
const { historicalData } = state;
const priceCtx = document.getElementById('price-chart').getContext('2d');
const equityCtx = document.getElementById('equity-chart').getContext('2d');
const chartOptions = { color: '#d1d5db' };
Chart.defaults.color = chartOptions.color;
if (priceChart) priceChart.destroy();
priceChart = new Chart(priceCtx, {
type: 'line',
data: {
labels: historicalData.map(d => d.date),
datasets: [
{ label: 'Price', data: historicalData.map(d => d.price), borderColor: '#9ca3af', borderWidth: 2, pointRadius: 0 },
{ label: `SMA(${state.settings.shortSma})`, data: result.shortSma, borderColor: '#60a5fa', borderWidth: 1.5, pointRadius: 0 },
{ label: `SMA(${state.settings.longSma})`, data: result.longSma, borderColor: '#f87171', borderWidth: 1.5, pointRadius: 0 },
{ label: 'Buy', data: result.signals.buys, type: 'scatter', backgroundColor: '#22c55e', pointRadius: 5, pointStyle: 'triangle', rotation: 0 },
{ label: 'Sell', data: result.signals.sells, type: 'scatter', backgroundColor: '#ef4444', pointRadius: 5, pointStyle: 'triangle', rotation: 180 }
]
},
options: { scales: { x: { type: 'time', time: { unit: 'month' }, grid: { color: '#374151'} }, y: { grid: { color: '#374151'} } } }
});
if (equityChart) equityChart.destroy();
equityChart = new Chart(equityCtx, {
type: 'line',
data: {
labels: result.equityCurve.map(d => d.date),
datasets: [{ label: 'Equity', data: result.equityCurve.map(d => d.value), borderColor: '#3b82f6', borderWidth: 2, pointRadius: 0, fill: true, backgroundColor: 'rgba(59, 130, 246, 0.1)' }]
},
options: { scales: { x: { type: 'time', time: { unit: 'month' }, grid: { color: '#374151'} }, y: { grid: { color: '#374151'} } } }
});
}
function updateTradeLog() {
const logBody = document.getElementById('trade-log-body');
logBody.innerHTML = '';
if (!state.backtestResult || state.backtestResult.trades.length === 0) {
logBody.innerHTML = `No trades were executed. `;
return;
}
state.backtestResult.trades.forEach(trade => {
const tr = document.createElement('tr');
tr.innerHTML = `
${trade.entryDate}
${trade.exitDate}
$${trade.entryPrice.toFixed(2)}
$${trade.exitPrice.toFixed(2)}
$${trade.pnl.toFixed(2)}
`;
logBody.appendChild(tr);
});
}
runBacktestBtn.addEventListener('click', runBacktest);
// --- PDF GENERATION ---
async function generatePdfReport() {
const result = state.backtestResult;
if (!result) {
alert("Please run a backtest first.");
return;
}
downloadPdfBtn.disabled = true;
downloadPdfBtn.textContent = 'Generating...';
const priceChartImg = priceChart.toBase64Image();
const equityChartImg = equityChart.toBase64Image();
const kpis = result.kpis;
const settings = state.settings;
const reportHtml = `
`;
const pdfTemplate = document.getElementById('pdf-template');
pdfTemplate.innerHTML = reportHtml;
pdfTemplate.classList.remove('invisible');
try {
const { jsPDF } = window.jspdf;
const canvas = await html2canvas(pdfTemplate.querySelector('.pdf-report-container'), { scale: 2 });
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
pdf.save(`${settings.ticker}_Performance_Report.pdf`);
} catch(e) {
console.error('PDF Generation Error:', e);
} finally {
downloadPdfBtn.disabled = false;
downloadPdfBtn.textContent = 'Download Performance Report';
pdfTemplate.classList.add('invisible');
}
}
downloadPdfBtn.addEventListener('click', generatePdfReport);
// --- INITIALIZATION ---
generateSampleData();
populateSettingsUI();
renderDataTable();
runBacktest();
switchTab(0);
});
Trade Strategy & Performance
Ticker: ${settings.ticker.toUpperCase()}
Date: ${new Date().toLocaleDateString()}
Performance Metrics
TOTAL P/L
$${kpis.totalPnl.toFixed(2)}
TOTAL RETURN
${kpis.totalReturn.toFixed(2)}%
WIN RATE
${kpis.winRate.toFixed(2)}%
TRADES
${kpis.numTrades}
