| Metric |
Value |
| Total Net Credit Received: |
${formatCurrency(results.netCredit)} |
| Maximum Profit: |
${formatCurrency(results.maxProfit)} |
| Maximum Loss: |
${formatCurrency(Math.abs(results.maxLoss))} |
| Lower Breakeven Point: |
${formatCurrency(results.lowerBreakeven)} |
| Upper Breakeven Point: |
${formatCurrency(results.upperBreakeven)} |
Max Profit: Occurs if the underlying price at expiration is between the short put strike (P2) and the short call strike (C1).
Max Loss: Occurs if the underlying price at expiration is below the long put strike (P1) or above the long call strike (C2).
Breakeven Points: The underlying prices at expiration where the trade results in zero profit or loss.
Disclaimer: This tool provides *simulated* P&L calculations for educational and illustrative purposes only. It does not account for commissions, slippage, dividends, or early assignment risk. Options trading involves substantial risk.
`;
resultsOutput.innerHTML = tableHtml;
}
/**
* Renders the P&L chart.
* @param {Object} results - Results from calculatePnL.
*/
function renderPnLChart(results) {
if (!results || !results.params) {
chartPlaceholder.classList.remove('hidden');
if (pnlChartInstance) {
pnlChartInstance.destroy();
pnlChartInstance = null;
}
return;
}
chartPlaceholder.classList.add('hidden'); // Hide placeholder if chart can be rendered
const ctx = document.getElementById('pnlChart').getContext('2d');
// Destroy existing chart instance if it exists to prevent overlap
if (pnlChartInstance) {
pnlChartInstance.destroy();
}
const { p1, p2, c1, c2 } = results.params;
// Determine price range for the chart (a bit wider than the outer strikes)
const minPrice = Math.floor(p1 * 0.9) - 5;
const maxPrice = Math.ceil(c2 * 1.1) + 5;
const priceRange = [];
for (let p = minPrice; p <= maxPrice; p += 0.5) {
priceRange.push(p);
}
const pnlData = priceRange.map(price => calculatePnLAtPrice(price, results.params));
pnlChartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: priceRange.map(p => p.toFixed(2)),
datasets: [
{
label: 'P&L at Expiration',
data: pnlData,
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 cleaner line
fill: false,
},
{
label: 'Zero Line',
data: new Array(priceRange.length).fill(0),
borderColor: '#9CA3AF', // Gray 400
borderWidth: 1,
borderDash: [5, 5],
pointRadius: 0,
fill: false,
stepped: false // Ensure it's a straight line
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
font: {
size: 14,
family: 'Inter',
},
color: '#333'
}
},
title: {
display: true,
text: 'Iron Condor P&L Profile at Expiration',
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 += `$${context.parsed.y.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}
return label;
}
}
},
annotation: { // For vertical lines at strikes
annotations: {
p1Line: {
type: 'line',
xMin: p1,
xMax: p1,
borderColor: 'rgba(255, 99, 132, 0.7)',
borderWidth: 2,
label: {
content: `P1: ${p1}`,
enabled: true,
position: 'start',
font: { size: 10, family: 'Inter' }
}
},
p2Line: {
type: 'line',
xMin: p2,
xMax: p2,
borderColor: 'rgba(75, 192, 192, 0.7)',
borderWidth: 2,
label: {
content: `P2: ${p2}`,
enabled: true,
position: 'start',
font: { size: 10, family: 'Inter' }
}
},
c1Line: {
type: 'line',
xMin: c1,
xMax: c1,
borderColor: 'rgba(75, 192, 192, 0.7)',
borderWidth: 2,
label: {
content: `C1: ${c1}`,
enabled: true,
position: 'end',
font: { size: 10, family: 'Inter' }
}
},
c2Line: {
type: 'line',
xMin: c2,
xMax: c2,
borderColor: 'rgba(255, 99, 132, 0.7)',
borderWidth: 2,
label: {
content: `C2: ${c2}`,
enabled: true,
position: 'end',
font: { size: 10, family: 'Inter' }
}
}
}
}
},
scales: {
y: {
beginAtZero: false,
title: {
display: true,
text: 'Profit / Loss ($)',
font: {
size: 14,
family: 'Inter'
},
color: '#333'
},
ticks: {
callback: function(value) {
return '$' + value.toLocaleString();
},
font: { family: 'Inter' }
}
},
x: {
title: {
display: true,
text: 'Underlying 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 canvases to generate a PDF report.
*/
window.downloadPdf = async function() {
if (typeof html2canvas === 'undefined' || typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') {
displayMessage('PDF generation libraries not loaded. Please try again or refresh.');
return;
}
hideMessage();
const { jsPDF } = jspdf;
const doc = new jsPDF('p', 'pt', 'a4');
// Temporarily show the charts tab to ensure they render on canvas before capturing
const chartsTabWasHidden = chartsTab.classList.contains('hidden');
if (chartsTabWasHidden) {
chartsTab.classList.remove('hidden');
}
// Elements to capture for PDF: results table and then the chart canvas
const elementsToCapture = [
document.getElementById('resultsOutput'),
document.getElementById('pnlChart')
];
let yPos = 40;
doc.setFontSize(22);
doc.setTextColor(51, 51, 51);
doc.text('Iron Condor P&L Report', doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 30;
doc.setFontSize(12);
doc.setTextColor(100, 100, 100);
doc.text(`Generated on: ${new Date().toLocaleDateString()}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 40;
for (const element of elementsToCapture) {
try {
// For chart canvases, ensure they are rendered. For other elements, ensure visibility if needed.
let elementToRender = element;
if (element.tagName.toLowerCase() === 'canvas') {
if (element.id === 'pnlChart' && pnlChartInstance === null) {
console.warn(`Chart ${element.id} not yet rendered for PDF capture. Skipping.`);
continue;
}
} else {
// For non-canvas elements, ensure they are visible for html2canvas
const wasHidden = element.classList.contains('hidden');
if (wasHidden) {
element.classList.remove('hidden');
}
// Capture the element as a canvas
elementToRender = await 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'
});
if (wasHidden) {
element.classList.add('hidden'); // Restore hidden state
}
}
const imgData = elementToRender.toDataURL('image/png');
const imgWidth = 550; // Desired width for image in PDF
const imgHeight = (elementToRender.height * imgWidth) / elementToRender.width;
if (yPos + imgHeight > doc.internal.pageSize.getHeight() - 40) {
doc.addPage();
yPos = 40;
}
doc.addImage(imgData, 'PNG', (doc.internal.pageSize.getWidth() - imgWidth) / 2, yPos, imgWidth, imgHeight);
yPos += imgHeight + 30;
} catch (error) {
console.error('Error capturing element for PDF:', error);
displayMessage('Failed to generate part of the PDF. Please ensure all data is loaded and visible before downloading.');
}
}
// Restore charts tab hidden state if it was temporarily shown
if (chartsTabWasHidden) {
chartsTab.classList.add('hidden');
}
doc.save('Iron_Condor_PNL_Report.pdf');
};
// Initial setup: Show the input tab when the DOM is ready
showTab('input');
});