Contents

Matching a Cash flow with a Bond Portfolio

In this script we will import data on UK Gilts to construct a yield curve for UK interest rates. Using this we will then construct a bond portfolio to match a given cash flow. This will be done by optimisation, constraining the resulting portfolio to have the same duration and convexity as the Cash flow.

close all; clear all; clc

Change figure size

make the default figure position large so that the graphs are more
legible.
spos = get(0,'screensize');
fpos0 = get(0,'DefaultFigurePosition');
set(0,'defaultFigurePosition',[spos(3)*.1 spos(4)*.1 spos(3)*.8 spos(4)*.8]);

Fitting the Yield Curve

We do this using the function YieldCurveFit automatically generated by the Curve Fitting toolbox. This function will fit a Svensson curve to the yield and lifetime data for a collection of bonds. Here we provide it with a collection of UK Gilts.

To investigate doing this with the Curve Fitting Toolbox if you have it installed, type:

cftool

and import the data Life and Yield into the tool.

First we load the Gilt data into a dataset array from the Statistics Toolbox.

UKBonds = dataset('xlsfile','UK_Gilts.xlsx');
Life = UKBonds.Life;
Yield = UKBonds.Yield;

YC = YieldCurveFit(Life,Yield);

Portfolio Construction

We now load up a series of corporate bonds from which we are going to construct the portfolio. Again we use a dataset array to do this. We convert the date format from a string to a numerical representation.

Bonds = dataset('xlsfile','Corporate_Bonds.xlsx');
Bonds.Maturity = datenum(Bonds.Maturity,'dd/mm/yyyy');

view the yields on this bonds along with the UK Yield curve generated. We do this using createfigure2 which was automatically generated by MATLAB from a plot figure.

t = linspace(.1,50);
createfigure2(t,YC(t),Bonds.Life,Bonds.Yield)

We are suspicious of anything offering a yield in excess of 5% of the corresponding value from our Yields curve, so remove these bonds.

Idx = find(Bonds.Yield > YC(Bonds.Life)+5);
Bonds(Idx,:) = [];
createfigure2(t,YC(t),Bonds.Life,Bonds.Yield)

Load up the Cash flow

The cash flow we want to match can be found in the data file CashFlows.xlsx. Load this up - here xlsread is used as an alternative to dataset arrays.

Settle = datenum('12/03/2009','dd/mm/yyyy'); % Date of seminar
data = xlsread('CashFlows.xlsx');
T = data(:,1);
CF = data(:,2)/1000;

Optimisation Constraints

we want to find the cheapest portfolio which has the same present value, duration and convexity as our cash flow. These values are given by:

$$ PV = \sum_{i = 1}^{i = n}\frac{CF_i}{(1+r_i)^{t_{i}}} $$

$$ D = \frac{1}{PV}\sum_{i = 1}^{i = n}\frac{t_i CF_i}{(1+r_i)^{t_{i}}} $$

$$ C = \frac{1}{PV}\sum_{i = 1}^{i = n}\frac{t_i (t_i+1) CF_i}{(1+r_i)^{t_{i}+2}} $$

Where $CF_i $ is the cash flow at and $r_i$ the annual yield to time $t_i$.

Rates = YC(T);
rd = rate2disc(1,Rates/100,T,0);
PV = rd'*CF;
[D,Cv] = DurationConvexity(PV,CF,T,Rates/100);

We now need the present values, durations and convexities for the bonds we are looking to construct a portfolio from. We do this with the functions prbyzero, bnddurp and bndconvp from the Financial Toolbox.

rates = YC(1:50)/100; dates = datemnth(Settle,12*[1:50]');
Bond_PV = prbyzero([Bonds.Maturity Bonds.Coupon/100],Settle,rates,dates,1);
[modDur,Durations] = bnddurp(Bonds.Price,Bonds.Coupon/100,Settle,Bonds.Maturity,2,0);
Convexity = bndconvp(Bonds.Price,Bonds.Coupon/100,Settle,Bonds.Maturity,2,0);
Price = Bonds.Price;

Now create the constraint matrices

Aeq = [Bond_PV(:)'; Durations(:)'; Convexity(:)'];
beq = [PV;D;Cv];

Lower and upper bounds - no shorting of bonds and no bonds to have a value higher than 0.5 (£500).

ub = 0.5*ones(numel(Bonds.Price),1);
lb = zeros(numel(Bonds.Price),1);

Perform the optimisation

We invoke the function BondPFConstruction which was automatically generated by optimtool from the Optimisation toolbox.

[wts,fval] = BondPFConstruction(Price,Aeq,beq,lb,ub);

Display the chosen bonds

Idx = find(wts);
str = ['Bond',char(9),'Coupon',char(9),'Maturity',char(9),'Number',char(10)];
for ii = 1:numel(Idx)
    str = [str,char(Bonds.Issuer(Idx(ii))),char(9),num2str(Bonds.Coupon(Idx(ii))),char(9),...
        datestr(Bonds.Maturity(Idx(ii)),'dd/mm/yyyy'),char(9),num2str(100*wts(Idx(ii))),char(10)];
end
disp(str);
Bond	Coupon	Maturity	Number
Standard Chartered Bank 	6.75	27/04/2009	50
JTI (UK) Finance PLC 	6.625	21/05/2009	50
General Elec Cap Corp 	6.25	01/09/2009	4.0448
Halifax Plc 	11	17/01/2014	50
Hsbc Holdings Plc 	9.875	08/04/2018	50
Halifax Plc 	9.375	15/05/2021	4.5227
Prudential Finance Bv 	6.125	19/12/2031	24.8766

strip out relevant data for next piece of analysis

B = [Bonds.Coupon(Idx)/100 Bonds.Maturity(Idx) Bonds.Price(Idx)]; wts = wts(Idx);
Labels = Bonds.Issuer(Idx);

Sensitivity and Default analysis

We can investigate the cash flows from the portfolio and the outgoings to see that they do match each other.

CF_Dates = datemnth(Settle,T*12);
figure
ModelCashFlow(wts,B,Settle,CF,CF_Dates,YC,Labels);

However this is dependent on the yield curve staying as it is. What happens if it either shifts up or down or flattens or steepens?

figure
dy = linspace(-1,1,7);
s = linspace(-1,1,7);

for ii = 1:numel(dy)
    for jj = 1:numel(s)
        f = @(t) YC(t(:))+dy(ii)+(t(:)-5)/5*s(jj);
        Z(ii,jj) = ModelCashFlow(wts,B,Settle,CF,CF_Dates,f);
    end
end

Z(abs(Z) > 100) = NaN;

surf(dy*100,100*s,Z')

xlabel('Parallel shift in interest rates (bp)');
ylabel('Slope change in interest rates (bp/Yr)');

Defaults

What happens if our bonds default before the cash flow has run it's course? The following graph gives the present value of a default.

ModelBondDefault(wts,B,Settle,CF,CF_Dates,YC,Labels);

Reset figure positions

set(0,'DefaultFigurePosition',fpos0);