Markov Functional Model Kaspers
Markov Functional Model Kaspers
Peter Caspers
IKB
The contents of this presentation are the sole and personal opinion of the
author and do not express IKB’s opinion on any subject presented in the
following.
1 Code Example
2 Theoretical Background
3 Model description
4 Calibration
5 Numerics
7 Implementation in QuantLib
8 References
9 Questions
Term Sheet
Model requirements
1
one important requirement - the decorrelation of Euribor6m and CMS10y
rates - is missing here, and actually not satisfied by the Markov 1F model
Peter Caspers (IKB) Markov Functional Model November 13, 2013 5 / 72
Code Example
This is where the Markov Functional Model jumps in. Before going into
details on how it works, we give an example in terms of QuantLib Code.
Let’s suppose you have already constructed a
Handle<YieldTermStructure> yts
representing a 6m swap curve and a
Handle<SwaptionVolatilityStructure> swaptionVol
representing a swaption volatility cube (suitable for CMS coupons pricing).
Model construction
Model calibration
Instrument
Our callable swap is represented as two instruments, the swap without the
call right and the call right itself. The former can be constructed as
boost::shared_ptr<FloatFloatSwap> swap(new FloatFloatSwap( ... ));
boost::shared_ptr<Exercise>
exercise(new BermudanExercise(exerciseDates));
boost::shared_ptr<FloatFloatSwaption>
swaption(new FloatFloatSwaption(underlying, exercise));
Pricing Engine
The npv of the swap on the other hand can be retrieved with standard cms
coupon pricers implementing a replication in some swap rate model 2 .
2
this is slightly different from pricing the full structured swap in the markov
model, since there are theoretical differences between the models (in addition to
differences in their numerical solution)
Peter Caspers (IKB) Markov Functional Model November 13, 2013 11 / 72
Code Example
#include <ql/quantlib.hpp>
using namespace QuantLib;
try {
iborIndex->addFixing(refDate, 0.0200);
swapIndex->addFixing(refDate, 0.0315);
std::vector<Date> exerciseDates;
std::vector<Date> sigmaSteps;
std::vector<Real> sigma;
sigma.push_back(0.01);
for (Size i = 1; i < sched1.size() - 1; i++) {
exerciseDates.push_back(swapIndex->fixingDate(sched1[i]));
sigmaSteps.push_back(exerciseDates.back());
sigma.push_back(0.01);
}
boost::shared_ptr<Exercise> exercise(
new BermudanExercise(exerciseDates));
boost::shared_ptr<FloatFloatSwaption> callRight(
new FloatFloatSwaption(cmsswap, exercise));
std::vector<Date> cmsFixingDates(exerciseDates);
std::vector<Period> cmsTenors(exerciseDates.size(), 10 * Years);
boost::shared_ptr<NumericHaganPricer> haganPricer(
new NumericHaganPricer(swaptionVol,
GFunctionFactory::NonParallelShifts,
reversionLevel));
setCouponPricer(cmsswap->leg(0), haganPricer);
boost::shared_ptr<Gaussian1dFloatFloatSwaptionEngine> floatEngine(
new Gaussian1dFloatFloatSwaptionEngine(mf));
callRight->setPricingEngine(floatEngine);
boost::shared_ptr<SwapIndex> swapBase(
new EuriborSwapIsdaFixA(30 * Years, yts));
boost::shared_ptr<Gaussian1dSwaptionEngine> stdEngine(
new Gaussian1dSwaptionEngine(mf));
std::cout << "model vol & swaption market & swaption model \\\\" << std::endl;
for (Size i = 0; i < basket.size(); i++) {
std::cout << mf->volatility()[i] << " & "
<< basket[i]->marketValue() << " & "
<< basket[i]->modelValue() << " \\\\" << std::endl;
}
std::cout << mf->volatility().back() << std::endl;
std::cout << "Swap Npv (Hagan) & " << analyticSwapNpv << "\\\\" << std::endl;
std::cout << "Call Right Npv (MF) & " << callRightNpv << "\\\\" << std::endl;
std::cout << "Underlying Npv (MF) & " << underlyingNpv << "\\\\" << std::endl;
std::cout << "Model trace : " << std::endl << mf->modelOutputs() << std::endl;
}
catch (std::exception &e) {
std::cerr << e.what() << std::endl;
return 1;
}
}
The expectation is to get a similar price of the underlying cms swap both
in the Markov and the replication model, since both are consistent with
the input swaption smile. Note that the underlying of the swaption is
receiving the cms side while the cms swap is paying.
The match is very close (0.1 bp times the nominal). It should be noted
that from theory we can not even expect a perfect match since the rate
dynamics is not the same in the Hagan model and the Markov model
respectively.
We change the code to replace the Markov model with a Hull White
model. This is easily done by
std::vector<Date> sigmaSteps2(sigmaSteps.begin(), sigmaSteps.end() - 1);
std::vector<Real> sigma2(sigma.begin(), sigma.end() - 1);
boost::shared_ptr<Gsr> gsr(new Gsr(yts,sigmaSteps2,sigma2,reversionLevel->value()));
and replacing mf by gsr (we could have use a generic name of course...).
The calibration now reads
gsr->calibrate(basket, opt, ec, Constraint(), std::vector<Real>(), model->FixedReversions());
or also
gsr->calibrateVolatilitiesIterative(basket, opt, ec);
The fit to the coterminals is exact, just as above (with different model
volatilities of course). The pricing compares ot the Markov model as
follows:
The underlying and the option are drastically overpriced (both) by over
60bp in the Hull White model.
Swaption smiles are usually far from being flat. We test the model with a
SABR volatility cube (with constant parameters α = 0.15, β = 0.80,
ν = 0.20, ρ = −0.30) by setting
Handle<SwaptionVolatilityStructure> swaptionVol(
new SingleSabrSwaptionVolatility(refDate, TARGET(), Following, 0.15,
0.80, -0.30, 0.20,
Actual365Fixed(), swapIndex));
Again the underlying and the option is overpriced (by 40bp resp. 53bp) in
the Hull White model. In the Markov model the fit is still good (1.4bp
underlying price difference).
0.7
implied lognormal black volatility
0.6
0.5
0.4
0.3
0.2
0.1
0
0 0.02 0.04 0.06 0.08 0.1
strike
Peter Caspers (IKB) Markov Functional Model November 13, 2013 23 / 72
Code Example
Any gaussian one factor HJM model which satisfies separability, i.e.
Set x(t) := r(t) − f (0, t) and fix a horizon T , then in the T -forward
measure the numeraire can be written
Pricing of callable fix versus Libor swaps may be done in a Hull White
model which is calibrated as follows:
For each call date find a market quoted swaption which is equivalent
to the call right (in some sense, e.g. by matching the npv and its first
and second derivative of the underlying at E(x(t))).
Calibrate the volatility function σ(t) to match the basket of these
swaptions.
Choose the mean reversion of the model to control serial correlations.
Intertemporal correlations
The calll rights in a callable cms swap are options on a swap exchanging
cms coupons against fix or Libor rates. Such underlying swaps are
drastically mispriced in the Hull White model in general.
cms coupons are replicated using swaptions covering the whole strike
continuum (0, ∞)
The swaption smile in the Hull White model is generally not
consistent with the market smile and so are the prices of cms coupons
Obviously we need a more flexible model to price such structures
Model requirements
Given the market smile of S(t) we can compute the market price
digmkt (K) of digitals for strikes K. For given y ∗ we can solve the equation
Z ∞
A(t, y)
digmkt (K) = P (0, T ) φ(y)dy (8)
y ∗ P (t, T )
for K to find the swap rate corresponding to the state variable value y ∗ .
For this digmkt (·) should be a monotonic function whose image is equal to
the possible digital prices (0, A(0)]. We will revisit this later.
we observe that
P (t, u) 1
=E y(t) (10)
P (t, T ) y(t) P (u, T )
i.e. we have to integrate the reciprocal of the numeraire at future times.
Working backward in time we can assume that we know the numeraire at
these times (starting with N (T ) ≡ 1).
Having computed the swap rate S(t) we have to convert this value to a
numeraire value N (t). Since
all terms on the right hand side computable via deflated zerobonds as
shown above. Note that we use a slightly modified swap rate here, namely
one without start delay.
Up to now we have not made use of the volatility σ(t) in the driving
markov process of the model. This parameter can be used to calibrate the
model to a second instrument set, however only a single strike can be
matched obviously for each expiry. A typical set up would be
calibrate the numeraire to an underlying rate smile, e.g. constant
maturity swaptions for cms coupon pricing
calibrate σ(t) to (standard atm or possibly adjusted) coterminal
swaptions for call right calibration
Note that after changing σ(t) the numeraire surface needs to be updated,
too.
Kahale extrapolation
SABR 14y/1y implied black lognormal volatilities as of 14-11-2012, input (solid)
and Kahale (dashed)
0.9
0.8
0.7
black lognormal volatility
0.6
0.5
0.4
0.3
0.2
0.1
0
0 0.05 0.1 0.15 0.2
Peter Caspers (IKB) Markov strike
Functional Model November 13, 2013 43 / 72
Calibration
Kahale extrapolation
SABR 14y/1y call prices as of 14-11-2012, input (solid) and Kahale (dashed)
0.035
0.03
0.025
0.02
call
0.015
0.01
0.005
0
0 0.05 0.1 0.15 0.2
strike
Peter Caspers (IKB) Markov Functional Model November 13, 2013 44 / 72
Calibration
Kahale extrapolation
SABR 14y/1y digital prices as of 14-11-2012, input (solid) and Kahale (dashed)
1
0.8
0.6
digital call
0.4
0.2
0
0 0.05 0.1 0.15 0.2
strike
Peter Caspers (IKB) Markov Functional Model November 13, 2013 45 / 72
Calibration
Kahale extrapolation
SABR 14y/1y density as of 14-11-2012, input (solid) and Kahale (dashed)
40
20
density
-20
-40
1 1.1
0.8 1
0.9
N(t,y) 0.6 0.8
0.4 0.7
0.2 0.6
0.5
0 0.4
0.3
-3
-2
-1
0 0
y 1 4 2
2 8 6
12 10 t
3 14
Interpolation of payoffs
0.031
0.0308
0.0306
0.0304
0.0302
zero rate
0.03
0.0298
0.0296
0.0294
0.0292
0.029
0 10 20 30 40 50
maturity
Peter Caspers (IKB) Markov Functional Model November 13, 2013 54 / 72
Numerics
The NTL and boost libraries provide support for arbitrary floating point
precision. We incorporated NTL support as an option replacing the
standard double precision by an arbitrary mantissa length precision in the
critical sections of the computation (which turned out to be the integration
of payoffs against the gaussian density, where large integrand values are
multiplied by small density values). NTL is activated by commenting out
// uncomment to enable NTL support
#define GAUSS1D_ENABLE_NTL
0.03001
0.030008
0.030006
0.030004
0.030002
zero rate
0.03
0.029998
0.029996
0.029994
0.029992
0.02999
0 10 20 30 40 50
maturity
Peter Caspers (IKB) Markov Functional Model November 13, 2013 57 / 72
Numerics
Computations with NTL are slow. A more practical way to stabilize the
calibration in difficult circumstances are adjustment factors forcing the
numeraire to match the market input yield curve. The adjustment factor is
introduced by replacing
P model (0, ti )
N (ti , yj ) → N (ti , yj ) (14)
P market (0, ti )
This option should be used with some care because it may lower the
accuracy of the volatility smile match. In most situations the adjustment
factors are moderate though, in the example we had before:
3
which means the first derivative w.r.t. the state variable of the model here
Peter Caspers (IKB) Markov Functional Model November 13, 2013 64 / 72
Secondary instrument set calibration
0
npv
-5
-10
-15
-20
-4 -2 0 2 4
y
Peter Caspers (IKB) Markov Functional Model November 13, 2013 65 / 72
Secondary instrument set calibration
-5000
npv
-10000
-15000
-20000
-25000
-4 -2 0 2 4
y
Peter Caspers (IKB) Markov Functional Model November 13, 2013 66 / 72
Secondary instrument set calibration
-5000
npv
-10000
-15000
-20000
-25000
-4 -2 0 2 4
y
Peter Caspers (IKB) Markov Functional Model November 13, 2013 67 / 72
Secondary instrument set calibration
It does not seem to be clear to what secondary instrument set you should
calibrate your model in order to represent calls on a cms (or more generally
float float) swaps, although in practice atm coterminals seem to be a
common choice.
QuantLib Implementation
References