Error: Please ensure all input fields are valid numbers.
";
if (!isReport) document.getElementById('capc-analysisOutput').innerHTML = errorMsg;
else {
document.getElementById('capc-reportInputsSummary').innerHTML = errorMsg;
document.getElementById('capc-reportAnalysisResults').innerHTML = "";
document.getElementById('capc-reportChartContainer').innerHTML = "";
}
return;
}
// Calculations
// Conversion ratio per single bond (assuming bondFaceValuePerBond input is used to scale)
const conversionRatioPerBond = conversionRatioPer1000FV * (bondFaceValuePerBond / 1000);
const totalBondFaceValue = numBonds * bondFaceValuePerBond;
const totalBondCost = numBonds * (bondPricePer100 / 100) * bondFaceValuePerBond;
const totalSharesShorted = numBonds * conversionRatioPerBond;
const initialShortValue = totalSharesShorted * stockPrice;
// Annual Figures
const annualCouponIncome = totalBondFaceValue * annualCouponRate;
const annualDividendCost = totalSharesShorted * annualDividendPerShare;
const annualStockBorrowCost = initialShortValue * stockBorrowFeeRate;
const netAnnualCashFlow = annualCouponIncome - annualDividendCost - annualStockBorrowCost;
const netAnnualYieldOnBondCost = totalBondCost > 0 ? (netAnnualCashFlow / totalBondCost) * 100 : 0;
// Total Over Horizon
const totalCouponIncomeHorizon = annualCouponIncome * investmentHorizonYears;
const totalDividendCostHorizon = annualDividendCost * investmentHorizonYears;
const totalStockBorrowCostHorizon = annualStockBorrowCost * investmentHorizonYears;
const netCashFlowHorizon = netAnnualCashFlow * investmentHorizonYears;
// Optional: Parity & Implied Conversion Price
const bondParityValue = stockPrice * conversionRatioPerBond; // Parity value of one bond
const impliedConversionPrice = (bondPricePer100 / 100 * bondFaceValuePerBond) / conversionRatioPerBond;
// --- Display Results ---
let resultsHTML = `
Position Setup & Initial Costs
Total Bonds Purchased: ${numBonds.toLocaleString()}
Total Face Value of Bonds: ${capc_formatCurrency(totalBondFaceValue)}
Total Cost of Bonds Purchased: ${capc_formatCurrency(totalBondCost)}
Total Shares to Short (Delta Hedge): ${totalSharesShorted.toLocaleString()} shares
Initial Value of Short Position: ${capc_formatCurrency(initialShortValue)}
Annual Profitability Analysis
Gross Annual Coupon Income: ${capc_formatCurrency(annualCouponIncome)}
Annual Dividend Payout on Short: (${capc_formatCurrency(annualDividendCost)})
Annual Stock Borrowing Cost: (${capc_formatCurrency(annualStockBorrowCost)})
Net Annual Cash Flow: ${capc_formatCurrency(netAnnualCashFlow)}
Net Annual Yield on Bond Cost: ${capc_formatPercent(netAnnualYieldOnBondCost)}
Bond Valuation Metrics (per bond)
Conversion Ratio (per bond): ${conversionRatioPerBond.toFixed(2)} shares
Bond Parity Value (Stock Price * Conv. Ratio): ${capc_formatCurrency(bondParityValue)}
Implied Conversion Price (Bond Cost / Conv. Ratio): ${capc_formatCurrency(impliedConversionPrice)}
${investmentHorizonYears > 1 || investmentHorizonYears !== 1 ? `
Total Over Investment Horizon (${investmentHorizonYears} Years)
Total Coupon Income: ${capc_formatCurrency(totalCouponIncomeHorizon)}
Total Dividend Payout: (${capc_formatCurrency(totalDividendCostHorizon)})
Total Stock Borrow Cost: (${capc_formatCurrency(totalStockBorrowCostHorizon)})
Net Cash Flow Over Horizon: ${capc_formatCurrency(netCashFlowHorizon)}
` : ''}
`;
let chartHTML = `
Annual Income vs. Expenses
`;
if (isReport) {
let inputsSummaryHTML = `
Inputs Summary
Bond Purchase Price: ${capc_formatCurrency(bondPricePer100, '')}/$100 FV
Bond Face Value: ${capc_formatCurrency(bondFaceValuePerBond)}
Annual Coupon Rate: ${capc_formatPercent(annualCouponRate*100)}
Conversion Ratio: ${conversionRatioPer1000FV} shares per $1000 FV
Current Stock Price: ${capc_formatCurrency(stockPrice)}
Annual Dividend/Share: ${capc_formatCurrency(annualDividendPerShare)}
Stock Borrow Fee: ${capc_formatPercent(stockBorrowFeeRate*100)}
Number of Bonds: ${numBonds.toLocaleString()}
Investment Horizon: ${investmentHorizonYears} Years
`;
document.getElementById('capc-reportInputsSummary').innerHTML = inputsSummaryHTML;
document.getElementById('capc-reportAnalysisResults').innerHTML = resultsHTML;
document.getElementById('capc-reportChartContainer').innerHTML = chartHTML; // Add chart to report tab
} else {
document.getElementById('capc-analysisOutput').innerHTML = resultsHTML + chartHTML; // Add chart to analysis tab
}
// Chart
if (capc_incomeExpenseChart) {
capc_incomeExpenseChart.destroy();
}
const ctx = document.getElementById('capc-profitabilityChart')?.getContext('2d');
if (ctx) {
capc_incomeExpenseChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Gross Coupon Income', 'Dividend Cost', 'Stock Borrow Cost', 'Net Annual Cash Flow'],
datasets: [{
label: 'Amount ($)',
data: [annualCouponIncome, -annualDividendCost, -annualStockBorrowCost, netAnnualCashFlow],
backgroundColor: [
'rgba(40, 167, 69, 0.7)', // Income (Green)
'rgba(220, 53, 69, 0.7)', // Expense (Red)
'rgba(220, 53, 69, 0.5)', // Expense (Lighter Red)
netAnnualCashFlow >= 0 ? 'rgba(0, 123, 255, 0.7)' : 'rgba(255, 193, 7, 0.7)' // Net (Blue or Amber)
],
borderColor: [
'rgba(40, 167, 69, 1)',
'rgba(220, 53, 69, 1)',
'rgba(220, 53, 69, 0.8)',
netAnnualCashFlow >= 0 ? 'rgba(0, 123, 255, 1)' : 'rgba(255, 193, 7, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: {
beginAtZero: true,
ticks: { callback: function(value) { return capc_formatCurrency(value, '$'); } }
}
}
}
});
}
}
async function capc_downloadPDF() {
const downloadButton = document.getElementById('capc-downloadPdfButton');
if (!downloadButton) return;
const originalButtonText = downloadButton.textContent;
downloadButton.textContent = 'Generating PDF...';
downloadButton.disabled = true;
const { jsPDF } = window.jspdf;
const reportContentElement = document.getElementById('capc-reportContainerForPdf');
if (!reportContentElement) {
alert("Error: Report content element not found.");
downloadButton.textContent = originalButtonText;
downloadButton.disabled = false;
return;
}
// Make sure results are up-to-date for the PDF
capc_calculateAndDisplay(true);
// Allow chart to render before capturing
await new Promise(resolve => setTimeout(resolve, 500));
try {
// Clone the report content for PDF generation to isolate it and potentially modify for print
const clonedReportElement = reportContentElement.cloneNode(true);
// Ensure chart is part of the cloned content
const chartCanvas = document.getElementById('capc-profitabilityChart');
if (chartCanvas && capc_incomeExpenseChart) {
const chartImgData = capc_incomeExpenseChart.toBase64Image();
const img = document.createElement('img');
img.src = chartImgData;
img.style.maxWidth = '90%';
img.style.height = 'auto';
img.style.display = 'block';
img.style.margin = '15px auto';
const chartContainerInClone = clonedReportElement.querySelector('#capc-profitabilityChart');
if (chartContainerInClone) { // if canvas is directly in report structure (as it is)
chartContainerInClone.parentNode.replaceChild(img, chartContainerInClone);
} else { // fallback if chart is deeply nested or ID changes in clone
const targetChartDiv = clonedReportElement.querySelector('.capc-chart-section canvas'); // more generic
if(targetChartDiv) targetChartDiv.parentNode.replaceChild(img, targetChartDiv);
}
}
// Append to body to ensure rendering IF complex CSS dependencies exist, then remove
document.body.appendChild(clonedReportElement);
const canvas = await html2canvas(clonedReportElement, {
scale: 2, // Improves quality
useCORS: true,
logging: false,
width: clonedReportElement.scrollWidth, // ensure full width is captured
height: clonedReportElement.scrollHeight, // ensure full height is captured
windowWidth: clonedReportElement.scrollWidth,
windowHeight: clonedReportElement.scrollHeight
});
document.body.removeChild(clonedReportElement); // Clean up clone
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'p', unit: 'pt', format: 'a4'
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgWidth = canvas.width;
const imgHeight = canvas.height;
// Calculate the aspect ratio
const canvasAspectRatio = imgWidth / imgHeight;
const pageAspectRatio = pdfWidth / pdfHeight;
let newImgWidth, newImgHeight;
let xOffset = 0;
let yOffset = 15; // top margin
if (imgHeight > pdfHeight - (2 * yOffset) || imgWidth > pdfWidth - (2* xOffset)) { // if image is larger than printable area
if (canvasAspectRatio > (pdfWidth / (pdfHeight - 2 * yOffset))) { // image is wider
newImgWidth = pdfWidth - (2 * xOffset);
newImgHeight = newImgWidth / canvasAspectRatio;
} else { // image is taller
newImgHeight = pdfHeight - (2 * yOffset);
newImgWidth = newImgHeight * canvasAspectRatio;
}
} else { // image is smaller than page
newImgWidth = imgWidth;
newImgHeight = imgHeight;
}
// Center the image
xOffset = (pdfWidth - newImgWidth) / 2;
pdf.addImage(imgData, 'PNG', xOffset, yOffset, newImgWidth, newImgHeight);
pdf.save('Convertible_Arbitrage_Report.pdf');
} catch (error) {
console.error("Error generating PDF:", error);
alert("An error occurred while generating the PDF. Please check the console.");
} finally {
downloadButton.textContent = originalButtonText;
downloadButton.disabled = false;
}
}