Yield Curve - Fitting a Cubic Spline

Overview

Data

Here, we just hard code the data as

let data = [ {price:96.60, maturity:1, coupon:2}, {price:93.71, maturity:2, coupon:2.5}, {price:91.56, maturity:3, coupon:3}, {price:90.24, maturity:4, coupon:3.5}, {price:89.74, maturity:5, coupon:4}, {price:90.04, maturity:6, coupon:4.5}, {price:91.09, maturity:7, coupon:5}, {price:92.82, maturity:8, coupon:5.5}, {price:95.19, maturity:9, coupon:6}, {price:98.14, maturity:10, coupon:6.5}, {price:101.60, maturity:11, coupon:7}, {price:105.54, maturity:12, coupon:7.5}, {price:109.90, maturity:13, coupon:8}, {price:114.64, maturity:14, coupon:8.5}, {price:119.73, maturity:15, coupon:9}, ];

Basis Functions

Next, the basis functions need to be implemented. The cubic library provides a function, g, which evaluates each basis function given the knot points and the time variable.

let cs = await import('/lib/finance/fixed-income/curves/v1.0.0/cubic.mjs'); let knots = [0,7.5,15]; let test1 = cs.g(0, 2, knots) let test2 = cs.g(0, 0, knots) let test3 = cs.g(1, 0, knots) let test4 = cs.g(2, 8, knots) let test5 = cs.g(3, 8, knots) let test6 = cs.g(2, 1, knots)
Try it!

Computing Cash Flows

Next, we compute the cash flows for each bond. This is done by defining a cash flow function to compute the cash flows from the bond defintion. (NOTE: we do not go to the full complexity of using day count conventions etc.... to exactly compute the cash flows. see cash flows. )

let cashFlows = function(item){ let ans = []; for(let i=1;i<=item.maturity;i++){ if(i==item.maturity){ ans.push({ date:i, value:100*(1+item.coupon/100) }); } else ans.push({ date:i, value:100*item.coupon }); } return ans; }
Try it!

Computing the C Matrix

The {% C %} matrix is defined by
{% C_{i k} = \sum_{j=1} CF_{ij} \times g_k(t_{j}) %}
That is, there is row for each bond in the dataset. Each column represents a single basis function.

let C = data.map((p,i)=>{ let row = []; let flows = cashFlows(p); for(let k=0;k<=knots.length;k++){ let value = 0; flows.forEach(q=>{ value += q.value * cs.g(k, q.date, knots); }) row.push(value); } return row; });
Try it!

Computing the Adjusted Price Vector

Next, the vector {% P' %} is computed, given the following defintion.
{% P'_i = P_i - \sum_{j=1} CF_j %}


let sums = []; data.map((p,i)=>{ let flows = cashFlows(p); let sum = 0; flows.forEach(p=>{ sum += p.value; }); sums.push(sum); }); let y = data.map((p,i)=>[p.price-sums[i]]);

Computing the Alpha Values

The final equation used to determine the {% \alpha %} values is
{% \vec{P'} = C \vec{\alpha} %}
On the surface, it appears that you could take the inverse of {% C %} and then multiply through. However, {% C %} is not a square matrix and hence is not invertible. On the other hand, the Moore Penrose Inverse can be applied. This can be effectively implemented using OLS regression.

let regression = olsregression.regress(y, C); let alphas = regression.Coefficients.map(p=>p[0]);

Implementation

Try it!