function calculateSpotRates() {
var maturities = [];
var parYields = [];
var spotRates = [];
var resultHTML = "";
var m1 = parseFloat(document.getElementById("maturity1").value);
var py1 = parseFloat(document.getElementById("parYield1").value) / 100;
var m2 = parseFloat(document.getElementById("maturity2").value);
var py2 = parseFloat(document.getElementById("parYield2").value) / 100;
var m3 = parseFloat(document.getElementById("maturity3").value);
var py3 = parseFloat(document.getElementById("parYield3").value) / 100;
if (isNaN(m1) || isNaN(py1) || isNaN(m2) || isNaN(py2) || isNaN(m3) || isNaN(py3)) {
document.getElementById("spotRateResult").innerHTML = "Please enter valid numbers for all inputs.";
return;
}
if (m1 <= 0 || m2 <= 0 || m3 <= 0 || py1 < 0 || py2 < 0 || py3 = m2 || m2 >= m3) {
document.getElementById("spotRateResult").innerHTML = "Maturities must be in increasing order.";
return;
}
maturities.push(m1, m2, m3);
parYields.push(py1, py2, py3);
// Bootstrapping logic
// Spot rate for maturity 1
var s1 = py1;
spotRates.push(s1);
// Spot rate for maturity 2
// (1 + s2 * m2) = (1 + py2 * m2) / (1 + s1 * m1)
// 1 + s2 * m2 = (1 + py2 * m2) / (1 + s1 * m1)
// s2 * m2 = (1 + py2 * m2) / (1 + s1 * m1) – 1
// s2 = [(1 + py2 * m2) / (1 + s1 * m1) – 1] / m2
if (1 + s1 * m1 === 0) { // Avoid division by zero
document.getElementById("spotRateResult").innerHTML = "Calculation error for maturity 2. Check input values.";
return;
}
var s2 = ((1 + py2 * m2) / (1 + s1 * m1) – 1) / m2;
spotRates.push(s2);
// Spot rate for maturity 3
// (1 + s3 * m3) = (1 + py3 * m3) / [(1 + s1 * m1) * (1 + s2 * (m2-m1))] 1, this calculation becomes iterative or requires more info.
// Let's stick to the assumption that the first maturity implies the first spot rate.
// For a generic case: (1 + s_m1 * m1) = 1 / (1 – py1 * m1) — This isn't right.
// We'll assume the first maturity is 1 for simplicity of bootstrapping:
if (m1 !== 1) {
document.getElementById("spotRateResult").innerHTML = "For accurate bootstrapping, the first maturity (Maturity 1) should be 1 year.";
return;
}
spotRates.push(py1);
}
// Maturity 2
var C2 = py2; // Annual coupon for year 2 bond
var PV_CF1_M2 = C2 / Math.pow(1 + spotRates[0], maturities[0]); // Present value of coupon at time m1
var remaining_value_at_m2 = 1 – PV_CF1_M2; // Remaining value to be discounted at spot rate s2
var discount_factor_for_s2 = Math.pow(1 + s2, maturities[1]); // Placeholder, needs to be solved
// Equation for s2: 1 = C2/(1+s1)^m1 + (C2+1)/(1+s2)^m2
// Rearranging: (1+s2)^m2 = (C2+1) / (1 – C2/(1+s1)^m1)
var denominator_s2 = 1 – C2 / Math.pow(1 + spotRates[0], maturities[0]);
if (denominator_s2 <= 0) {
document.getElementById("spotRateResult").innerHTML = "Calculation error for Maturity 2. Denominator is zero or negative. Check inputs.";
return;
}
var factor_s2 = Math.pow((C2 + 1) / denominator_s2, 1 / maturities[1]);
var s2_calc = factor_s2 – 1;
spotRates.push(s2_calc);
// Maturity 3
var C3 = py3; // Annual coupon for year 3 bond
var PV_CF1_M3 = C3 / Math.pow(1 + spotRates[0], maturities[0]);
var PV_CF2_M3 = C3 / Math.pow(1 + spotRates[1], maturities[1]);
var remaining_value_at_m3 = 1 – PV_CF1_M3 – PV_CF2_M3;
// Equation for s3: 1 = C3/(1+s1)^m1 + C3/(1+s2)^m2 + (C3+1)/(1+s3)^m3
// Rearranging: (1+s3)^m3 = (C3+1) / (1 – C3/(1+s1)^m1 – C3/(1+s2)^m2)
var denominator_s3 = 1 – C3 / Math.pow(1 + spotRates[0], maturities[0]) – C3 / Math.pow(1 + spotRates[1], maturities[1]);
if (denominator_s3 <= 0) {
document.getElementById("spotRateResult").innerHTML = "Calculation error for Maturity 3. Denominator is zero or negative. Check inputs.";
return;
}
var factor_s3 = Math.pow((C3 + 1) / denominator_s3, 1 / maturities[2]);
var s3_calc = factor_s3 – 1;
spotRates.push(s3_calc);
resultHTML += "
Calculated Spot Rates:
";
resultHTML += "
";
resultHTML += "- Maturity " + maturities[0] + " Years: " + (spotRates[0] * 100).toFixed(4) + "%
";
resultHTML += "- Maturity " + maturities[1] + " Years: " + (spotRates[1] * 100).toFixed(4) + "%
";
resultHTML += "- Maturity " + maturities[2] + " Years: " + (spotRates[2] * 100).toFixed(4) + "%
";
resultHTML += "
";
document.getElementById("spotRateResult").innerHTML = resultHTML;
}
.calculator-widget {
font-family: sans-serif;
border: 1px solid #e0e0e0;
padding: 20px;
border-radius: 8px;
max-width: 700px;
margin: 20px auto;
background-color: #f9f9f9;
}
.calculator-title {
text-align: center;
color: #333;
margin-bottom: 20px;
}
.calculator-description {
margin-bottom: 30px;
color: #555;
line-height: 1.6;
font-size: 0.95em;
}
.calculator-form {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-bottom: 25px;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group label {
margin-bottom: 5px;
font-weight: bold;
color: #444;
font-size: 0.9em;
}
.form-group input[type="number"] {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1em;
width: 100%;
box-sizing: border-box;
}
.calculator-button {
grid-column: 1 / -1;
padding: 12px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 1.1em;
cursor: pointer;
transition: background-color 0.3s ease;
}
.calculator-button:hover {
background-color: #0056b3;
}
.calculator-result {
margin-top: 20px;
padding: 15px;
background-color: #e7f3ff;
border: 1px solid #cce5ff;
border-radius: 4px;
color: #004085;
font-size: 1em;
}
.calculator-result h3 {
margin-top: 0;
color: #003366;
}
.calculator-result ul {
list-style: disc;
padding-left: 20px;
}
.calculator-result li {
margin-bottom: 8px;
}
.error {
color: #dc3545;
font-weight: bold;
}