Institutional Fixed-Income Portfolio Analyzer (Conceptual Simulator)

Portfolio Setup

$
If you input allocations as %, this value helps convert to $ for some reports. If holdings are input with market values, this will be calculated.

Fixed-Income Holdings Input

Add individual fixed-income securities or aggregated categories. Duration and Convexity are direct inputs for this conceptual tool.

Total Value/Allocation: $0 (0%)

Portfolio Analysis & Summary

Portfolio summary metrics will appear here.

Allocation by Credit Quality

Allocation by Maturity Bucket

Interest Rate Sensitivity Analysis

bps
E.g., 100 for +1.00%, -50 for -0.50% shift.

Enter holdings and click 'Analyze Portfolio'.

"; creditChartContainer.innerHTML = '

Credit Allocation Chart

'; maturityChartContainer.innerHTML = '

Maturity Allocation Chart

'; return; } const res = ifipa_model.analysisResults; summarySection.innerHTML = `

Portfolio Aggregate Characteristics

Total Market Value: ${ifipa_formatCurrency(res.totalMarketValue)}
Weighted Avg. YTM: ${ifipa_formatPercent(res.waytm)}
Weighted Avg. Coupon: ${ifipa_formatPercent(res.wac)}
Weighted Avg. Maturity: ${ifipa_formatYears(res.wam)}
Portfolio Duration: ${ifipa_formatYears(res.portfolioDuration)}
Portfolio Convexity (Optional Input): ${res.portfolioConvexity.toFixed(2)}
Avg. Credit Quality (Conceptual): ${res.portfolioAvgCreditRatingString} (Score: ${res.portfolioAvgCreditScore.toFixed(1)})
`; // Placeholder for charts - simple text for now, SVG can be complex ifipa_renderAllocationBarChart(ifipa_collateByCredit(), 'Credit Quality Allocation', creditChartContainer, (val) => val); ifipa_renderAllocationBarChart(ifipa_collateByMaturity(), 'Maturity Allocation', maturityChartContainer, (val) => val + ' Yrs'); } function ifipa_collateByCredit() { const creditAlloc = {}; const totalMV = ifipa_model.analysisResults.totalMarketValue; if(totalMV === 0) return []; ifipa_model.holdings.forEach(h => { const rating = h.creditRating || 'Unrated'; const weight = h.inputType === 'value' ? (h.marketValue / totalMV) * 100 : h.allocationPercent; creditAlloc[rating] = (creditAlloc[rating] || 0) + weight; }); return Object.entries(creditAlloc).map(([name, value]) => ({name, value, color: ifipa_getColorForCredit(name)})); } function ifipa_getColorForCredit(rating){ const score = ifipa_creditRatingMap[rating] || 16; if(score <=2.5) return '#6EE7B7'; // AAA-AA if(score <= 4.5) return '#A5B4FC'; // A if(score <= 6.5) return '#FBBF24'; // BBB if(score <= 10.5) return '#F87171'; // BB-B return '#9CA3AF'; // CCC and below or Unrated } function ifipa_collateByMaturity() { const maturityBuckets = { "0-1":0, "1-3":0, "3-5":0, "5-10":0, "10+":0 }; const totalMV = ifipa_model.analysisResults.totalMarketValue; if(totalMV === 0) return []; ifipa_model.holdings.forEach(h => { const mat = h.yearsToMaturity || 0; const weight = h.inputType === 'value' ? (h.marketValue / totalMV) * 100 : h.allocationPercent; if (mat <= 1) maturityBuckets["0-1"] += weight; else if (mat <= 3) maturityBuckets["1-3"] += weight; else if (mat <= 5) maturityBuckets["3-5"] += weight; else if (mat <= 10) maturityBuckets["5-10"] += weight; else maturityBuckets["10+"] += weight; }); const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EC4899', '#8B5CF6']; return Object.entries(maturityBuckets).map(([name,value], i) => ({name, value, color:colors[i % colors.length]})); } function ifipa_renderAllocationBarChart(data, title, container, labelFormatter = (val) => val) { container.innerHTML = ''; // Clear const svgWidth = Math.min(400, (ifipa_getEl('ifipaContainer_main').offsetWidth || 400)/2 - 40); const svgHeight = 200; const m = {top: 20, right: 10, bottom: 50, left: 40}; const w = svgWidth - m.left - m.right; const h = svgHeight - m.top - m.bottom; const filteredData = data.filter(d => d.value > 0.01); if(filteredData.length === 0) { container.innerHTML = `

No data for ${title} chart.

`; return;} const yMax = Math.max(...filteredData.map(d=>d.value), 0); const barWidth = w / filteredData.length * 0.8; const barSpacing = w / filteredData.length * 0.2; const yScale = val => h - ((val / (yMax === 0 ? 1: yMax)) * h); let chartHTML = ` {/* X-axis base */} {/* Y-axis */} ${yMax.toFixed(0)}% 0% `; filteredData.forEach((d,i) => { const xPos = i * (barWidth + barSpacing) + barSpacing/2; const barH = h - yScale(d.value); chartHTML += `${d.name}: ${d.value.toFixed(1)}%`; chartHTML += `${labelFormatter(d.name)}`; }); chartHTML += ``; container.innerHTML = chartHTML; } function ifipa_displaySensitivityResults() { const resultsSection = ifipa_getEl('ifipa_sensitivityResultsSection'); if (!resultsSection) { console.error("Sensitivity results display element missing."); return; } if (!ifipa_model.sensitivityResults) { resultsSection.innerHTML = "

Run a scenario to see impact.

"; resultsSection.style.display = 'none'; return; } const res = ifipa_model.sensitivityResults; const origVal = res.originalValue; const durChangeClass = res.valueChangeDur >=0 ? 'ifipa_positive_change' : 'ifipa_negative_change'; const durConvChangeClass = res.valueChangeDurConv >=0 ? 'ifipa_positive_change' : 'ifipa_negative_change'; resultsSection.innerHTML = `

Estimated Impact of ${res.rateShiftBps > 0 ? '+' : ''}${res.rateShiftBps} bps Rate Shift:

Original Portfolio Value:${ifipa_formatCurrency(origVal)}

Using Duration Only:

Est. Value Change:${ifipa_formatCurrency(res.valueChangeDur)} (${ifipa_formatPercent(res.valueChangePercentDur)})
Est. New Portfolio Value:${ifipa_formatCurrency(res.newValueDur)}
${ifipa_model.analysisResults.portfolioConvexity !== 0 ? `

Using Duration & Convexity:

Est. Value Change:${ifipa_formatCurrency(res.valueChangeDurConv)} (${ifipa_formatPercent(res.valueChangePercentDurConv)})
Est. New Portfolio Value:${ifipa_formatCurrency(res.newValueDurConv)}
` : '

Portfolio convexity is zero or not provided for a more precise estimate.

'} `; resultsSection.style.display = 'block'; } // --- PDF Generation --- function ifipa_downloadPDF() { if (!ifipa_model.analysisResults) { alert("Please analyze portfolio first."); return; } if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Core PDF library (jsPDF) is not loaded.'); console.error('jsPDF library not found.'); return; } const { jsPDF: JSPDF } = window.jspdf; const doc = new JSPDF(); if (typeof doc.autoTable !== 'function') { alert('PDF Table plugin (jsPDF-AutoTable) not loaded correctly. Tables in PDF may be missing.'); console.error('doc.autoTable is not a function.'); } let y = 15; const m = 15; const cw = doc.internal.pageSize.getWidth() - (2 * m); const res = ifipa_model.analysisResults; const inputs = ifipa_model; function addLine(text, size, style = 'normal', indent = 0, spacing = 2.5) { if (y > 275) { doc.addPage(); y = m; } doc.setFontSize(size); doc.setFont(undefined, style); const lines = doc.splitTextToSize(text, cw - indent); doc.text(lines, m + indent, y); y += (lines.length * (size * 0.35)) + spacing; } addLine(`Institutional Fixed-Income Portfolio Analysis: ${inputs.portfolioName || 'N/A'}`, 16, 'bold', 0, 5); addLine(`Report Date: ${new Date().toLocaleDateString()}`, 9, 'italic', 0, 5); addLine("Portfolio Holdings Summary:", 12, 'bold', 0, 3); if(typeof doc.autoTable === 'function' && res.holdingsSnapshot.length > 0) { const head = [['Holding', 'MV ($) / Alloc(%)', 'Coupon(%)', 'Maturity(Yrs)', 'YTM(%)', 'Duration(Yrs)', 'Convexity', 'Credit']]; const body = res.holdingsSnapshot.map(h => [ h.name, h.inputType === 'value' ? ifipa_formatCurrency(h.marketValue) : ifipa_formatPercent(h.allocationPercent,1), h.couponRate.toFixed(2), h.yearsToMaturity.toFixed(1), h.ytm.toFixed(2), h.duration.toFixed(1), (h.convexity || 0).toFixed(2), h.creditRating ]); doc.autoTable({startY: y, head: head, body: body, theme: 'grid', headStyles:{fillColor:[55,65,81], fontSize:8}, styles:{fontSize:7.5, cellPadding:1, halign:'right'}, columnStyles:{0:{halign:'left',fontStyle:'bold'}}}); y = doc.lastAutoTable.finalY + 5; } else { addLine("Holdings table cannot be generated (plugin/data issue).", 8, 'italic'); y+=5; } addLine("Aggregate Portfolio Characteristics:", 12, 'bold', 0, 3); let summaryData = [ ["Total Market Value:", ifipa_formatCurrency(res.totalMarketValue)], ["Weighted Avg. YTM:", ifipa_formatPercent(res.waytm)], ["Weighted Avg. Coupon:", ifipa_formatPercent(res.wac)], ["Weighted Avg. Maturity:", ifipa_formatYears(res.wam)], ["Portfolio Duration:", ifipa_formatYears(res.portfolioDuration)], ["Portfolio Convexity:", res.portfolioConvexity.toFixed(2)], ["Avg. Credit Quality (Conceptual):", `${res.portfolioAvgCreditRatingString} (Score: ${res.portfolioAvgCreditScore.toFixed(1)})`], ]; if (typeof doc.autoTable === 'function') { doc.autoTable({startY: y, body: summaryData, theme: 'plain', styles:{fontSize:9, cellPadding:1.2}, columnStyles:{0:{fontStyle:'bold'}}}); y = doc.lastAutoTable.finalY + 6; } else { summaryData.forEach(row => addLine(`${row[0]} ${row[1]}`, 9)); y+=5; } if (ifipa_model.sensitivityResults) { const sensRes = ifipa_model.sensitivityResults; addLine(`Interest Rate Sensitivity (Shift: ${sensRes.rateShiftBps > 0 ? '+' : ''}${sensRes.rateShiftBps} bps):`, 12, 'bold', 0, 4); let sensData = [ ["Metric", "Value"], ["Original Portfolio Value:", ifipa_formatCurrency(sensRes.originalValue)], ["Est. Change (Duration Only):", `${ifipa_formatCurrency(sensRes.valueChangeDur)} (${ifipa_formatPercent(sensRes.valueChangePercentDur)})`], ["Est. New Value (Duration Only):", ifipa_formatCurrency(sensRes.newValueDur)], ]; if(res.portfolioConvexity !== 0) { sensData.push(["Est. Change (Duration & Convexity):", `${ifipa_formatCurrency(sensRes.valueChangeDurConv)} (${ifipa_formatPercent(sensRes.valueChangePercentDurConv)})`]); sensData.push(["Est. New Value (Duration & Convexity):", ifipa_formatCurrency(sensRes.newValueDurConv)]); } if (typeof doc.autoTable === 'function') { doc.autoTable({startY: y, body: sensData, theme: 'plain', styles:{fontSize:9, cellPadding:1.2}, columnStyles:{0:{fontStyle:'bold'}}}); y = doc.lastAutoTable.finalY + 7; } else { sensData.forEach(row => addLine(`${row[0]} ${row[1]}`, 9)); y+=5;} } addLine("Note: This is a conceptual simulator based on user-defined inputs. Duration and Convexity for individual holdings are user inputs. Calculations are approximate. Not investment advice.", 7, 'italic'); doc.save(`${(inputs.portfolioName || 'FixedIncomePortfolio').replace(/\s+/g, '_')}_Analysis.pdf`); }
Scroll to Top