# Gaussian Models

This is going to be a guided tour through some example code I wrote to illustrate the usage of the Markov Functional and Gsr (a.k.a. Hull White) model implementations.

Hull White / Gsr is without any doubt the bread and butter model for rates. It calibrates to a given series of vanilla instruments, it has a parameter (the mean reversion) to control intertemporal correlations (which is important both for bermudan pricing and time travelling), but you can not alter its “factory settings” regarding the smile. At least it is not flat but a skew. Not unrealistic from a qualitative standpoint, but you would have to be lucky to match the market skew decently of course. Markov Functional on the other hand mimics any given smile termstructure exactly as long as it is arbitrage free.

Recently I added Hagan’s internal adjusters to the Gsr implementation, trying to make up for the comparative disadvantage. I coming back to them at the end of this article. Internal adjusters here is in distinction to external adjusters of course, which I am working on as well. More on them later.

Let’s delve into the example. I do not reproduce the whole code here, just the main points of interest. You can find the full example in one of the recent releases of QuantLib under Examples / Gaussian1dModels.

First we set the global evaluation date.

  Date refDate(30, April, 2014);
Settings::instance().evaluationDate() = refDate;


The rate curves will be flat, but we assume basis spreads to demonstrate that the models can handle them in a decent way.

  Real forward6mLevel = 0.025;
Real oisLevel = 0.02;


I will omit the code to set up the quotes and termstructures here. Swaption volatilities are chosen flat as well

  Real volLevel = 0.20;


Now we set up a deal that we can price later on. The underlying is a standard vanilla spot starting swap with $4\%$ fixed rate against Euribor 6M.

  Real strike = 0.04;
boost::shared_ptr<NonstandardSwap> underlying =
boost::make_shared<NonstandardSwap>(VanillaSwap(
VanillaSwap::Payer, 1.0, fixedSchedule, strike,
Thirty360(),
floatingSchedule, euribor6m, 0.00, Actual360()));


Of course there is a reason that we use a NonstandardSwap instead of a VanillaSwap. You will see later.

We define a bermudan swaption on that underlying with yearly exercise dates, where the notification of a call should be given two TARGET days before the next accrual start period.

 boost::shared_ptr<Exercise> exercise =
boost::make_shared<BermudanExercise>(
exerciseDates, false);
boost::shared_ptr<NonstandardSwaption> swaption =
boost::make_shared<NonstandardSwaption>(
underlying, exercise);


To set up the Gsr model we need to define the grid on which the model volatility is piecewise constant. Since we want to match the market quotes for european calls later on we chose the grid points identical to the exercise dates, except that we do not need a step at the last exercise date obviously. The initial model volatility is set to $1\%$

        std::vector<Date> stepDates(exerciseDates.begin(),
exerciseDates.end() - 1);
std::vector<Real> sigmas(stepDates.size() + 1, 0.01);


The reversion speed is $1\%$ as well.

        Real reversion = 0.01;


And we are ready to define the model!

  boost::shared_ptr<Gsr> gsr = boost::make_shared<Gsr>(
yts6m, stepDates, sigmas, reversion);


We will need a swaption engine for calibration

 boost::shared_ptr<PricingEngine> swaptionEngine =
boost::make_shared<Gaussian1dSwaptionEngine>(
gsr, 64, 7.0, true,
false, ytsOis);


Normally it is enough to pass the model gsr. The $64$ and $7.0$ are the default parameters for the numerical integration scheme that is used by the engine as well as true and false indicating that the payoff should be extrapolated outside the integration domain in a non-flat manner (this is not really important here). The last parameter denotes the discounting curve that should be used for the swaption valuation. Note that this is different from the model’s “main” yield curve, which is the Eurior 6M forward curve (see above).

We set up a second engine for our instrument we want to price.

boost::shared_ptr<PricingEngine>
nonstandardSwaptionEngine =
boost::make_shared<Gaussian1dNonstandardSwaptionEngine>(
gsr, 64, 7.0, true, false, Handle<Quote>(), ytsOis);


On top of the parameters from above, we have an empty quote here. This can be used to introduce a flat credit termstructure into the pricing. We will see later how to use this in exotic bond valuations. For the moment it is just empty, so ignored.

Now we assign the engine to our bermudan swaption

  swaption->setPricingEngine(nonstandardSwaptionEngine);


How do we calibrate our Gsr model to price this swaption ? Actually there are some handy methods thanks to the fact that we chose an engine which implements the BasketGeneratingEngine interface, so we can just say

std::vector<boost::shared_ptr<CalibrationHelper> > basket =


to get a coterminal basket of at the money swaptions fitting the date schedules of our deal. The swapBase here encodes the conventions for standard market instruments. The last parameter Naive tells the engine just to take the exercise dates of the deal and the maturity date of the underlying and create at the money swaptions from it using the standard market conventions.

We can do more involved things and we will below: as soon as the deal specifics are not matching the standard market swaption conventions, we can choose an adjusted basket of calibration instruments! This can be a little thing like five instead of two notification dates for a call, different day count conventions on the legs, a non-yearly fixed leg payment frequency, or bigger things like a different Euribor index, an amortizing notional schedule and so on. I wrote a short note some time ago on this which you can get here if you are interested.

In any case the naive basket looks like this:

Expiry              Maturity            Nominal             Rate          Pay/Rec     Market ivol
==================================================================================================
April 30th, 2015    May 6th, 2024       1.000000            0.025307      Receiver    0.200000
May 3rd, 2016       May 6th, 2024       1.000000            0.025300      Receiver    0.200000
May 3rd, 2017       May 6th, 2024       1.000000            0.025303      Receiver    0.200000
May 3rd, 2018       May 6th, 2024       1.000000            0.025306      Receiver    0.200000
May 2nd, 2019       May 6th, 2024       1.000000            0.025311      Receiver    0.200000
April 30th, 2020    May 6th, 2024       1.000000            0.025300      Receiver    0.200000
May 3rd, 2021       May 6th, 2024       1.000000            0.025306      Receiver    0.200000
May 3rd, 2022       May 6th, 2024       1.000000            0.025318      Receiver    0.200000
May 3rd, 2023       May 6th, 2024       1.000000            0.025353      Receiver    0.200000


The calibration of the model to this basket is done via

   gsr->calibrateVolatilitiesIterative(basket, method, ec);


where method and ec are an optimization method (Levenberg-Marquardt in our case) and end criteria for the optimization. I should note that the calibration method is not the default one defined in CalibratedModel, which does a global optimization on all instruments, but a serialized version calibrating one step of the sigma function to one instrument at a time, which is much faster.

Here is the result of the calibration.

Expiry              Model sigma   Model price         market price        Model ivol    Market ivol
====================================================================================================
April 30th, 2015    0.005178      0.016111            0.016111            0.199999      0.200000
May 3rd, 2016       0.005156      0.020062            0.020062            0.200000      0.200000
May 3rd, 2017       0.005149      0.021229            0.021229            0.200000      0.200000
May 3rd, 2018       0.005129      0.020738            0.020738            0.200000      0.200000
May 2nd, 2019       0.005132      0.019096            0.019096            0.200000      0.200000
April 30th, 2020    0.005074      0.016537            0.016537            0.200000      0.200000
May 3rd, 2021       0.005091      0.013253            0.013253            0.200000      0.200000
May 3rd, 2022       0.005097      0.009342            0.009342            0.200000      0.200000
May 3rd, 2023       0.005001      0.004910            0.004910            0.200000      0.200000


and the price of our swaption, retrieved in the QuantLib standard way,

Real npv = swaption->NPV();


is around $38$ basispoints.

Bermudan swaption NPV (ATM calibrated GSR) = 0.003808


Now let’s come back to what I mentioned above. Actually the european call rights are not exactly matching the atm swaptions we used for calibration. Namely our underlying swap is not atm, but has a fixed rate of $4\%$. So we should use an apdapted basket. Of course in this case you can guess what one should take, but I will use the general machinery to make it trustworthy. The adapted basket can be retrieved by

  basket = swaption->calibrationBasket(
swapBase, *swaptionVol,


with the parameter MaturityStrikeByDeltaGamma indicating that the market swaptions for calibration are chosen from the set of all possible market swaptions (defined by the swapBase, remember ?) by an optimization of the nominal, strike and maturity as the remaining free parameters such that the zeroth, first and second order derivatives of the exotic’s underlying by the model’s state variable (evaluated at some suitable central point) are matched.

To put it differently, per expiry we seek a market underlying that in all states of the world (here for all values of the state variable of our model) has the same value as the exotic underlying we wish to price. To get this we match the Taylor expansions up to order two of our exotic and market underlying.

Let’s see what this gets us in our four percent strike swaption case. The calibration basket becomes:

Expiry              Maturity            Nominal             Rate          Pay/Rec     Market ivol
==================================================================================================
April 30th, 2015    May 6th, 2024       0.999995            0.040000      Payer       0.200000
May 3rd, 2016       May 6th, 2024       1.000009            0.040000      Payer       0.200000
May 3rd, 2017       May 6th, 2024       1.000000            0.040000      Payer       0.200000
May 3rd, 2018       May 7th, 2024       0.999953            0.040000      Payer       0.200000
May 2nd, 2019       May 6th, 2024       0.999927            0.040000      Payer       0.200000
April 30th, 2020    May 6th, 2024       0.999996            0.040000      Payer       0.200000
May 3rd, 2021       May 6th, 2024       1.000003            0.040000      Payer       0.200000
May 3rd, 2022       May 6th, 2024       0.999997            0.040000      Payer       0.200000
May 3rd, 2023       May 6th, 2024       1.000002            0.040000      Payer       0.200000


As you can see the calibrated rate for the market swaption is $4\%$ as expected. What you can also see is that payer swaptions were generated. This is because always out of the money options are chosen to be calibration instruments for the usual reason. The nominal is slightly different from $1.0$, but practically did not change. This is more to prove that some numerical procedure worked for you here.

Recalibrating the model to the new basket gives

Expiry              Model sigma   Model price         market price        Model ivol    Market ivol
====================================================================================================
April 30th, 2015    0.006508      0.000191            0.000191            0.200000      0.200000
May 3rd, 2016       0.006502      0.001412            0.001412            0.200000      0.200000
May 3rd, 2017       0.006480      0.002905            0.002905            0.200000      0.200000
May 3rd, 2018       0.006464      0.004091            0.004091            0.200000      0.200000
May 2nd, 2019       0.006422      0.004766            0.004766            0.200000      0.200000
April 30th, 2020    0.006445      0.004869            0.004869            0.200000      0.200000
May 3rd, 2021       0.006433      0.004433            0.004433            0.200000      0.200000
May 3rd, 2022       0.006332      0.003454            0.003454            0.200000      0.200000
May 3rd, 2023       0.006295      0.001973            0.001973            0.200000      0.200000


indeed different, and the option price

Bermudan swaption NPV (deal strike calibrated GSR) = 0.007627


almost doubled from $38$ to $76$ basispoints. Well actually it more than doubled. Whatever. Puzzle: We did not define a smile for our market swaption surface. So it shouldn’t matter which strike we choose for the calibration instrument, should it ?

There are other applications of the delta-gamma-method. For example we can use an amortizing nominal going linear from $1.0$ to $0.1$. The calibration basket then becomes

Expiry              Maturity            Nominal             Rate          Pay/Rec     Market ivol
==================================================================================================
April 30th, 2015    August 5th, 2021    0.719236            0.039997      Payer       0.200000
May 3rd, 2016       December 6th, 2021  0.641966            0.040003      Payer       0.200000
May 3rd, 2017       May 5th, 2022       0.564404            0.040005      Payer       0.200000
May 3rd, 2018       September 7th, 2022 0.486534            0.040004      Payer       0.200000
May 2nd, 2019       January 6th, 2023   0.409763            0.040008      Payer       0.200000
April 30th, 2020    May 5th, 2023       0.334098            0.039994      Payer       0.200000
May 3rd, 2021       September 5th, 2023 0.255759            0.039995      Payer       0.200000
May 3rd, 2022       January 5th, 2024   0.177041            0.040031      Payer       0.200000
May 3rd, 2023       May 6th, 2024       0.100000            0.040000      Payer       0.200000


First of all, the nominal of the swaptions is adjusted to the amortizing schedule, being some average over the coming periods respectively. Furthermore, the effective maturity is reduced.

As a side note. The nominal is of course not relevant at all for the calibration step. It does not matter, if you calibrate to a swaption with nominal $1.0$ or $0.1$ or $100000000.0$. But it is a nice piece of information as in the last example anyhow, to see if it is plausible what happens.

Now consider a callable bond. You can set this up as a swap, too, with one zero leg and final notional exchange. The NonStandardSwap allows for all this. The exercise has to be extended to carry a rebate payment reflecting the notional reimbursement in case of exercise. This is handled by the RebatedExercise extension. The delta-gamma calibration basket now looks as follows.

Expiry              Maturity            Nominal             Rate          Pay/Rec     Market ivol
==================================================================================================
April 30th, 2015    April 5th, 2024     0.984093            0.039952      Payer       0.200000
May 3rd, 2016       April 5th, 2024     0.985539            0.039952      Payer       0.200000
May 3rd, 2017       May 6th, 2024       0.987068            0.039952      Payer       0.200000
May 3rd, 2018       May 7th, 2024       0.988455            0.039952      Payer       0.200000
May 2nd, 2019       May 6th, 2024       0.990023            0.039952      Payer       0.200000
April 30th, 2020    May 6th, 2024       0.991622            0.039951      Payer       0.200000
May 3rd, 2021       May 6th, 2024       0.993111            0.039951      Payer       0.200000
May 3rd, 2022       May 6th, 2024       0.994190            0.039952      Payer       0.200000
May 3rd, 2023       May 6th, 2024       0.996715            0.039949      Payer       0.200000


The notionals are slightly below $1.0$ (as well as the maturities and strikes not exactly matching the bermudan swaption case). This is expected however, since the market swaptions are discounted on OIS level, while for the bond we chose to use the 6m curve as a benchmark discounting curve. The effect is small however. Put 6m as discounting to cross check this.

What is more interesting is to assume a positive credit spread. Let’s set this to $100$ basispoints for example. The spread is interpreted as an option adjusted spread, continuously compounded with Actual365Fixed day count convention. The calibration basket gets

Expiry              Maturity            Nominal             Rate          Pay/Rec     Market ivol
==================================================================================================
April 30th, 2015    February 5th, 2024  0.961289            0.029608      Payer       0.200000
May 3rd, 2016       March 5th, 2024     0.965356            0.029605      Payer       0.200000
May 3rd, 2017       April 5th, 2024     0.969520            0.029608      Payer       0.200000
May 3rd, 2018       April 8th, 2024     0.973629            0.029610      Payer       0.200000
May 2nd, 2019       April 8th, 2024     0.978124            0.029608      Payer       0.200000
April 30th, 2020    May 6th, 2024       0.982682            0.029612      Payer       0.200000
May 3rd, 2021       May 6th, 2024       0.987316            0.029609      Payer       0.200000
May 3rd, 2022       May 6th, 2024       0.991365            0.029603      Payer       0.200000
May 3rd, 2023       May 6th, 2024       0.996646            0.029586      Payer       0.200000


Look what the rate is doing. It is adjusted by roughly the credit spread. Again this is natural, since the hedge swaption for the bond’s call right would have roughly $100$ basispoints margin on the float side. Here it is coming automatically out of our optimization procedure.

Let’s come to our final example. The underlying is a swap exchanging a CMS10y rate against Euribor 6M. To make the numbers a bit nicer I changed the original example code to include a $10$ basispoint margin on the Euribor leg.

We start with the underlying price retrieved from a replication approach. I am using the LinearTsrPricer here, with the same mean reversion as for the Gsr model above. The pricing is

Underlying CMS     Swap NPV = 0.004447
CMS     Leg  NPV = -0.231736
Euribor Leg  NPV = 0.236183


so $44.5$ basispoints. Now we consider a bermudan swaption (as above, with yearly exercises) on this underlying. A naively calibrated Gsr model yields

Float swaption NPV (GSR) = 0.004291
Float swap     NPV (GSR) = 0.005250


The npv of the option is $42.9$ basispoints. The underlying price, which can be retrieved as an additional result from the engine as follows

swaption4->result<Real>("underlyingValue")


is $52.5$ basispoints. Please note that the option has its first exercise in one year time, so the first year’s coupons are not included in the exercised into deal. This is why the underlying price is higher than the option value.

What do we see here: The Gsr model is not able to price the underlying swap correctly, the price is around $8$ basispoints higher than in the analytical pricer. This is because of the missing smile fit (in our example the fit to a flat smile, which the Gsr can not do). The Markov Functional model on the other hand can exactly do this. We can calibrate the numeraire of the model such that the market swaption surface is reproduced on the fixing dates of the CMS coupons for swaptions with 10y maturity. The goal is to get a better match with the replication price. Let’s go: The model is set up like this

  boost::shared_ptr<MarkovFunctional> markov =
boost::make_shared<MarkovFunctional>(
yts6m, reversion, markovStepDates,
markovSigmas, swaptionVol,
cmsFixingDates, tenors, swapBase,
MarkovFunctional::ModelSettings()
.withYGridPoints(16));


It is not that different from the Gsr model construction. We just have to provide the CMS coupons’ fixing dates and tenors (and the conventions of the swaptions behind), so that we can calibrate to the corresponding smiles. The last parameter is optional and overwrites some numerical parameter with a more relaxed value, so that the whole thing works a bit faster in our example. Ok, what does the Markov model spit out:

Float swaption NPV (Markov) = 0.003549
Float swap NPV (Markov)     = 0.004301


The underlying is now matched much better than in the Gsr model, it is up to $1.5$ basispoints accurate. A perfect match is not expected from theory, because the dynamics of the linear TSR model is not the same as in the Markov model, of course.

The option price, accordingly, is around $7.5$ basispoints lower compared to the Gsr model. This is around the same magnitude of the underlying mismatch in the Gsr model.

To complete the picture, the Markov model also has a volatility function that can be calibrated to a second instrument set like coterminal swaptions to approximate call rights. It is rather questionable if a call right of a CMS versus Euribor swap is well approximated by a coterminal swaption. Actually I tried to use the delta-gamma-method to search for any representation of such a call right in the Markov model. The following picture is from a different case (it is taken from the paper I mentioned above), but showing what is going on in principle

The exotic underlying is actually well matched by a market swaption’s underlying around the model’s state $y=0$, which is the expansion point for the method, so it does what it is supposed to do. But the global match is poor, so there does not seem to be a good reason to include additional swaptions into the calibration to represent call rights. They do not hurt, but do not specifically represent the call rights, so just add some more market information to the model.

Anyhow, we can do it, so we do it. If we just take atm coterminals and calibrate the Markov model’s volatility function to them, we get as a calibration result

Expiry              Model sigma   Model price         market price        Model ivol    Market ivol
====================================================================================================
April 30th, 2015    0.010000      0.016111            0.016111            0.199996      0.200000
May 3rd, 2016       0.012276      0.020062            0.020062            0.200002      0.200000
May 3rd, 2017       0.010534      0.021229            0.021229            0.200001      0.200000
May 3rd, 2018       0.010414      0.020738            0.020738            0.200001      0.200000
May 2nd, 2019       0.010361      0.019096            0.019096            0.199998      0.200000
April 30th, 2020    0.010339      0.016537            0.016537            0.200002      0.200000
May 3rd, 2021       0.010365      0.013253            0.013253            0.199998      0.200000
May 3rd, 2022       0.010382      0.009342            0.009342            0.200001      0.200000
May 3rd, 2023       0.010392      0.004910            0.004910            0.200001      0.200000
0.009959


I am not going into details about the volatility function here, but note, that the first step is fixed (at its initial value of $1\%$) and the step after the last expiry date matters. In addition a global calibration to all coterminals simultaneously is necessary, the iterative approach will not work for the model.

The pricing results for the underlying does not change that much, the fit is still good as desired:

Float swap NPV (Markov) = 0.004331


There is one last thing I want to mention and which is not yet part of the library or the example code. Hagan introduced a technique called “internal adjusters” to make the Gsr model work in situations like the one we have here, namely that the underlying is not matched well. He mentions this approach in his paper on callable range accrual notes where he uses his LGM (same as Gsr or Hull White) model for pricing and observes that he does not calibrate to underlying Libor caplets or floorlets very well. He suggests to introduce an adjusting factor to be multiplied with the model volatility in case we are evaluating such a caplet or floorlet during the pricing of the exotic. So the missing model fit is compensated by using a modified model volatility “when needed” (and only then, i.e. when evaluating the exotic coupon we want to match).

This sounds like a dirty trick, destroying the model in a way and introducing arbitrage. On the other hand mispricing the market vanillas introduces arbitrage in a much more obvious and undesirable way. So why not. If Pat Hagan says we can do it, it is safe I guess. We should say however that Hagan’s original application was restricted to adjust Libor volatilities. Here we make up for a completely wrong model smile. And we could even go a step further and match e.g. market CMS spread coupon prices in a Gsr model, although the model does not even allow for rate decorrelation. So one should be careful, how far one wants to go with this trick.

I added these adjusters to the Gsr model. If you don’t specify or calibrate them, they are not used though, so nothing changes from an end user perspective. In our example we would set up a calibration basket like this

 std::vector<boost::shared_ptr<CalibrationHelperBase> >
for (Size i = 0; i < leg0.size(); ++i) {
boost::shared_ptr<CmsCoupon> coupon =
boost::dynamic_pointer_cast<CmsCoupon>(leg0[i]);
if (coupon->fixingDate() > refDate) {
swapBase, coupon->fixingDate(),
coupon->date());
tmp->setCouponPricer(cmsPricer);
tmp->setPricingEngine(floatSwaptionEngine);
}
}


The adjuster helper created here corresponds to the CMS coupons of our trade. We set the linear TSR pricer (cmsPricer) to produce the reference results and the Gsr pricing engine in order to be able to calibrate the adjusters to match the reference prices. Now we can say

  gsr->calibrateAdjustersIterative(adjusterBasket,
method, ec);


like before for the volatilities. What we get is

Expiry              Adjuster      Model price         Reference price
================================================================================
April 30th, 2015    1.0032        2447560.9183        2447560.9183
May 3rd, 2016       1.0353        2402631.1363        2402631.1363
May 3rd, 2017       1.0640        2378624.8507        2378624.8507
May 3rd, 2018       1.0955        2324333.7739        2324333.7739
May 2nd, 2019       1.1239        2295880.1247        2295880.1247
April 30th, 2020    1.1643        2261229.3425        2261229.3425
May 3rd, 2021       1.1948        2228406.7519        2228406.7519
May 3rd, 2022       1.2214        2196901.0808        2196901.0808
May 3rd, 2023       1.2732        2177227.7967        2177227.7967


The prices here are with reference to a notional of one hundred million. The adjuster values needed to match the reference prices from the replication pricer are not too far from one, which is good, because it means that the model is not bent too much in order to produce the CMS coupon prices.

The pricing in the new model is as follows

GSR (adjusted) option value     = 0.003519
GSR (adjusted) underlying value = 0.004452


The underlying match is (by construction) very good thanks to the adjusters. Also the option value is adjusted down as desired, we are now very close to the markov model’s value. Needless to say that this does not work out that well all the time. Also as an important disclaimer, an option on CMS10Y against Euribor6M has features of a spread option, which is highly correlation sensitive. Neither the (adjusted) Gsr nor the Markov model can produce correlations other than one for the spread components, so they will always underprice the option in this sense.

Enough for today, look at the example and play around with the code. Or read the paper.

# Shifted SABR, CMS and Markov Functional

The other day I found this piece of code in QuantLib

QL_REQUIRE(values[i]>0.0,
"non positive fixing (" << values[i] <<
") at date " << dates[i] << " not allowed");


Puzzle: Where is this snippet taken from ? Anyway, financial markets do not care. We currently have negative Euribor Fixings for 1w and 2w. Brokers started to quote zero strike floors on Euribor 3M and 6M already a year ago or so. Now, market participants start to assign significant premia to zero strike floors on the EUR CMS 10y rate.

The classic market model for options on Euribor or a swap annuity is the Black76 model

$dF = F \sigma dW$

implying zero probability for negative underlying values, so a zero strike floor is worth zero always. One way to get around this is the shifted Black76 model

$dF = (F+\alpha) \sigma dW$

with a positive (or of course zero) shift $\alpha$. For EUR caps and floors $\alpha=1\%$ is a common value currently.

It is quite simple to give a solution of such a shifted model in terms of the original model: Set $X:= F+\alpha$, then you have the original model back, but in $X$ instead of $F$. So to get an option price, plug in the shifted forward and the shifted strike into the original option pricing formula.

This is the density of the underlying implied by this model at $t=10$ (years) with an initial forward level of $0.50\%$ and a volatility of $\sigma=20\%$:

The model sets the lower bound for the underlying rate to $-1\%$. One can ask what the classic (i.e. non-shifted) Black76 implied volatility for option prices produced by our shifted model is. Here you go.

The shift parameter has produced a skew. Actually shifted underlyings have been a popular technique to produce skews, e.g. in the Libor market model, for a long time. Here our motivation is different, namely to produce negative rates.

You will have noticed that there is no implied volatility for strikes near zero, even if they are positive. That is because the call option price function in the shifted model looks like this (the red line)

starting at $1.5\%$ which is the sum of the forward and the shift and crossing the y axis (not very prominent in this plot) above the forward level because of its convexity, thus producing non-attainable prices for the non-shifted Black model. The green line marks the maximum price attainable in a non-shifted Black76 model.

I should say that I am always talking about non discounted prices – the discount or in case of swaptions the annuity factor is unity.

Next we need a shifted SABR model. It is constructed the same way as the shifted Black76 model:

$dF = (F+d)^\beta \sigma dW \\ d\sigma = \sigma \nu dV$

with $\sigma(0) = \alpha$ and $dWdV = \rho dt$ and our shift $d\ge 0$. Here is a density with same parameters as above and $\alpha=0.012, \beta=0.30, \nu=0.15, \rho=-0.10$.

The negative density for strikes near $-1\%$ is nothing special, we already know this defect from the classic non-shifted Hagan model (we are using the standard expansion from 2002 here). The point is again that the density now covers the region from $-1\%$ to zero strike level.

It seems that brokers like ICAP changed their primary model for swaption smiles to this one. They publish an ATM matrix together with a shift matrix assigning an individual shift to each option tenor / swap length pair. The shifts are constant across option tenors, but different for the different underlyings. At the moment the shifts are $0.50\%$ for the 1y underlying, going down to $0.30\%$ for the 30y underlying. They also publish smile spreads in the usual way, but as differences of shifted lognormal itm or otm volatilities and the shifted atm volatility.

In QuantLib we need to do several things to adopt these new quotation standards. I will try to package the changes into a pull request soon. The highlights are as follows.

The Smile Section class needs to know what kind of volatility nature it represents.

enum Nature { ShiftedLognormal, Normal };
SmileSection(const Date& d,
const DayCounter&a dc = DayCounter(),
const Date& referenceDate = Date(),
const Nature nature = ShiftedLognormal,
const Rate shift = 0.0);


As you can see we can also have normal smile sections. This is another standard for volatility quotation using the normal Black model

$dF = \sigma dW$

as a basis.

The ATM matrix now takes a shift matrix as an optional parameter.

SwaptionVolatilityMatrix::SwaptionVolatilityMatrix(
const Calendar& cal,
const std::vector<Period>& optionT,
const std::vector<Period>& swapT,
const std::vector<std::vector<Handle<Quote> > >& vols,
const DayCounter& dc,
const bool flatExtrapolation,
const std::vector<std::vector<Real> >& shifts)


The two swaption volatility cubes need to be adapted as well. Their interface does not change, but we have to use a shifted SABR model for the SABR cube for example. Also the moneyness definition for smile spread interpolation has to be adapted. And some other things I guess.

To do CMS pricing we need to get our hands on some CMS coupon pricer. My favourite one is the LinearTsrPricer (it’s fast, it respects the put call parity, it just does not disappoint you). A terminal swap rate model for CMS coupons does not depend on certain strike ranges or a particular model for vanilla swaption valuation. Such a model is given by a mapping $\alpha$

$\alpha( S(t) ) = \frac{P(t,t_P)}{A(t)}$

where $t_p$ is the coupon payment date and $A(t)$ the annuity of the underlying swap rate $S$. Then (integration by parts) the npv of a general CMS coupon

$A(0) E^A( P(t,t_p) A(t)^{-1} g(S(t)) )$

is given by

$A(0)S(0)\alpha(S(0))+\int_{-\infty}^{S(0)} w(k)R(k)dk+\int_{S(0)}^\infty w(k)P(k)dk$

with $t$ begin the fixing date of the coupon, $R$ and $P$ prices of market receiver and payer swaptions and weights $w(s)=\{\alpha(s)g(s)\}''.$ This nice and concise derivation is taken from the three volume Piterbarg bible on interest rate derivatives.

The linear terminal swap rate model is given by a linear function $\alpha(S) = aS+b$. $b$ is determined by a no arbitrage condition already, but $a$ is free, and can for example be linked to a one factor mean reversion. Or just set as a free user choice directly.

The derivation says that you should integrate over the range where you have non zero put or call prices. For a shifted lognormal smile section input this means that we need to integrate over $[-\alpha,\infty)$ for a plain CMS coupon, if $\alpha$ is the shift. The shift can be read from the SmileSection which is input to the CMS coupon pricers and we are done.

The last thing I did so far is to make the Markov Functional Model work with the shifted smile sections. The result is a fully dynamic and consistent model which can reproduce densities given by marginal shifted SABR inputs for example. This way we get consistent with the TSR CMS pricing described above.

Enough for today, I will go into more details about the Markov functional model (and the linear TSR pricer) in one of the next posts.