Maximum Profit:
$${results.maxProfit.toFixed(2)}
Maximum Loss (Stock at $0):
$${results.maxLoss.toFixed(2)}
Return on Capital (if assigned):
${results.returnOnCapital.toFixed(2)}%
`;
let detailedTableHtml = `
Profit/Loss Scenarios at Expiration
| Scenario |
Underlying Price ($) |
P&L ($) |
| Current Stock Price |
${params.stockPurchasePrice.toFixed(2)} |
$${calculateCoveredCallPnL(params, params.stockPurchasePrice).toFixed(2)} |
| Breakeven Price |
${results.breakevenPrice.toFixed(2)} |
$0.00 |
| Call Strike Price (Assigned) |
${params.callStrikePrice.toFixed(2)} |
$${results.maxProfit.toFixed(2)} |
| Underlying Price at $0.00 |
$0.00 |
$${results.maxLoss.toFixed(2)} |
`;
resultsOutput.innerHTML = summaryHtml + detailedTableHtml;
}
/**
* Renders the Profit & Loss chart using Chart.js.
* @param {Object} results - The results object containing chart data.
*/
function renderChart(results) {
if (!results.chart || !results.chart.labels || results.chart.labels.length === 0) {
chartPlaceholder.classList.remove('hidden');
if (plChartInstance) {
plChartInstance.destroy();
plChartInstance = null;
}
return;
}
chartPlaceholder.classList.add('hidden'); // Hide placeholder if chart can be rendered
const ctx = document.getElementById('plChart').getContext('2d');
// Destroy existing chart instance if it exists to prevent overlap
if (plChartInstance) {
plChartInstance.destroy();
}
plChartInstance = new Chart(ctx, {
type: 'line', // Line chart for P&L over price range
data: {
labels: results.chart.labels,
datasets: [{
label: `P&L for ${results.assetSymbol} Covered Call`,
data: results.chart.data,
borderColor: '#3B82F6', // Blue 500
backgroundColor: 'rgba(59, 130, 246, 0.2)', // Light blue fill
borderWidth: 2,
tension: 0.1, // Smooth curve
pointRadius: 0, // No points for a clean line
fill: true, // Fill area under the curve
},
{ // Dataset for Breakeven line
label: 'Breakeven',
data: results.chart.labels.map(price => parseFloat(price) === parseFloat(results.breakevenPrice.toFixed(2)) ? 0 : null),
borderColor: '#F59E0B', // Amber 500
borderWidth: 2,
borderDash: [5, 5], // Dashed line
pointRadius: 5,
pointBackgroundColor: '#F59E0B',
showLine: true,
fill: false,
},
{ // Dataset for Max Profit line (at strike price)
label: 'Max Profit (at Strike)',
data: results.chart.labels.map(price => parseFloat(price) === parseFloat(results.strategyParams.callStrikePrice.toFixed(2)) ? results.maxProfit.toFixed(2) : null),
borderColor: '#10B981', // Emerald 500
borderWidth: 2,
borderDash: [5, 5],
pointRadius: 5,
pointBackgroundColor: '#10B981',
showLine: true,
fill: false,
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
font: {
size: 14,
family: 'Inter',
},
color: '#333'
}
},
title: {
display: true,
text: `Covered Call P&L for ${results.assetSymbol}`,
font: {
size: 18,
family: 'Inter',
weight: 'bold'
},
color: '#1F2937'
},
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += `$${parseFloat(context.parsed.y).toFixed(2)}`;
}
return label;
},
title: function(context) {
return `Price: $${context[0].label}`;
}
}
}
},
scales: {
y: {
beginAtZero: false,
title: {
display: true,
text: 'Profit / Loss ($)',
font: {
size: 14,
family: 'Inter'
},
color: '#333'
},
ticks: {
callback: function(value) {
return '$' + value;
},
font: { family: 'Inter' }
}
},
x: {
title: {
display: true,
text: 'Underlying Asset Price at Expiration ($)',
font: {
size: 14,
family: 'Inter'
},
color: '#333'
},
ticks: { font: { family: 'Inter' } }
}
}
}
});
}
/**
* Handles the PDF download functionality.
* Captures the results output and chart canvas to generate a PDF report.
*/
window.downloadPdf = async function() {
// Ensure jsPDF and html2canvas libraries are loaded
if (typeof html2canvas === 'undefined' || typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') {
displayMessage('PDF generation libraries not loaded. Please try again or refresh.');
return;
}
hideMessage(); // Clear any existing messages
const { jsPDF } = jspdf;
const doc = new jsPDF('p', 'pt', 'a4'); // 'p' for portrait, 'pt' for points, 'a4' for A4 size
// Define the elements to be captured for the PDF report
const elementsToCapture = [
document.getElementById('resultsOutput'),
document.getElementById('plChart')
];
// Create promises for capturing each element as a canvas image
const promises = elementsToCapture.map(element => {
// Temporarily make the element visible if it's currently hidden
const wasHidden = element.classList.contains('hidden');
if (wasHidden) {
element.classList.remove('hidden');
}
return html2canvas(element, {
scale: 2, // Increase scale for better resolution in PDF
useCORS: true, // Required for images/fonts loaded from other origins if any
backgroundColor: '#ffffff' // Ensure white background for captured content
}).then(canvas => {
// Restore hidden state if it was temporarily made visible
if (wasHidden) {
element.classList.add('hidden');
}
return canvas;
});
});
let yPos = 40; // Initial Y position for content in the PDF
// Add a main title to the PDF report
doc.setFontSize(22);
doc.setTextColor(51, 51, 51); // Dark gray
doc.text('Covered Call Writing Strategy Simulation Report', doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 30; // Move down for next element
// Add generation date
doc.setFontSize(12);
doc.setTextColor(100, 100, 100); // Medium gray
doc.text(`Generated on: ${new Date().toLocaleDateString()}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 40; // Move down for next element
// Iterate through the captured canvases and add them to the PDF
for (const promise of promises) {
try {
const canvas = await promise;
const imgData = canvas.toDataURL('image/png'); // Convert canvas to PNG data URL
const imgWidth = 550; // Desired width for the image in the PDF (A4 is approx 595pt wide)
const imgHeight = (canvas.height * imgWidth) / canvas.width; // Maintain aspect ratio
// Check if the image fits on the current page. If not, add a new page.
if (yPos + imgHeight > doc.internal.pageSize.getHeight() - 40) { // Check against a 40pt bottom margin
doc.addPage(); // Add a new page
yPos = 40; // Reset Y position for the new page
}
// Add the image to the PDF, centered horizontally
doc.addImage(imgData, 'PNG', (doc.internal.pageSize.getWidth() - imgWidth) / 2, yPos, imgWidth, imgHeight);
yPos += imgHeight + 30; // Move Y position down, adding some padding
} catch (error) {
console.error('Error capturing element for PDF:', error);
displayMessage('Failed to generate part of the PDF. Please ensure all data is loaded.');
}
}
// Save the generated PDF file
doc.save('Covered_Call_Strategy_Simulation.pdf');
};
// Initial setup: Show the input tab and update navigation buttons when the DOM is ready
showTab('input');
});