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 reprsents 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!

Contents