QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.34
Loading...
Searching...
No Matches
markovfunctional.hpp
Go to the documentation of this file.
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2013, 2018 Peter Caspers
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20/*! \file markovfunctional.hpp
21 \brief Markov Functional 1 Factor Model
22*/
23
24#ifndef quantlib_markovfunctional_hpp
25#define quantlib_markovfunctional_hpp
26
33#include <utility>
34
35namespace QuantLib {
36
37 /*! One factor Markov Functional model class. Some documentation is
38 available here
39 http://ssrn.com/abstract_id=2183721
40 http://quantlib.org/slides/qlws13/caspers.pdf
41 */
42
43 /*! The model requires a suitable input smile which means it should be
44 arbitrage free, smooth (at least implying a C^1 call price function) and
45 with a call price function not decreasing too slow in strike direction.
46
47 A method for arbitrage free extra- and interpolation due to Kahale is
48 provided and may be used to improve an input smile. Alternatively a
49 SABR smile with arbitrage free wings can be fitted to the input smile
50 to provide an appropriate input smile.
51
52 If you use the Kahale or SABR method for smile pretreatment then this
53 implies zero density for underlying rates below minus the displacement
54 parameter. This means that in this case the market yield term structure
55 must imply underlying atm forward rates greater than minus displacement.
56
57 If you do not use a smile pretreatment you should ensure that the input
58 smileSection is arbitrage free and that the input smileSection covers the
59 strikes from lowerRateBound to upperRateBound.
60
61 During calibration a monocurve setup is assumed with the given yield term
62 structure determining the rates throughout, no matter what curves are
63 linked to the indices in the volatility term structures. The yield term
64 structure should therefore be the main risk curve, i.e. the forwarding curve
65 for the respective swaption or cap underlyings.
66
67 The model uses a simplified formula for the npv of a swaps floating leg
68 namely $P(t,T_0)-P(t,T_1)$ with $T_0$ being the start date of the leg
69 and $T_1$ being the last payment date, which is an approximation to the
70 true npv.
71
72 The model calibrates to slightly modified market options in the sense that
73 the start date is set equal to the fixing date, i.e. there is no delay.
74 The model diagnostic outputs refer to this modified instrument. In general
75 the actual market instrument including the delay is still matched very
76 well though the calibration is done on a slightly different instrument.
77
78 AdjustYts and AdjustDigitals are experimental options. Specifying
79 AdjustYts may have a negative impact on the volatility smile match, so
80 it should be used with special care. For long term calibration it seems
81 an interesting option though.
82
83 A bad fit to the initial yield term structure may be due to a non suitable
84 input smile or accumulating numerical errors in very long term calibrations.
85 The former point is adressed by smile pretreatment options. The latter point
86 may be tackled by higher values for the numerical parameters possibly
87 together with NTL high precision computing.
88
89 When using a shifted lognormal smile input the lower rate bound is adjusted
90 by the shift so that a lower bound of 0.0 always corresponds to the lower
91 bound of the shifted distribution.
92
93 If a custom smile is used, this will take full responsibility of inverting
94 digital prices to market rates, so digitalGap, marketRateAccuracy,
95 lowerRateBound, upperRateBound are irrelavant and the smile moneyness
96 checkpoints are only used for the debug model output in this setup.
97 */
98
100
101 public:
102
104 public:
105 virtual Real inverseDigitalCall(Real price, Real discount = 1.0) const = 0;
106 };
107
109 public:
110 virtual ~CustomSmileFactory() = default;
111 virtual ext::shared_ptr<CustomSmileSection>
112 smileSection(const ext::shared_ptr<SmileSection>& source, Real atm) const = 0;
113 };
114
116
117 // NoPayoffExtrapolation overrides ExtrapolatePayoffFlat
121 AdjustYts = 1 << 1,
124 KahaleSmile = 1 << 4,
128 SabrSmile = 1 << 8,
129 CustomSmile = 1 << 9
130 };
131
133
134 ModelSettings(Size yGridPoints,
135 Real yStdDevs,
136 Size gaussHermitePoints,
137 Real digitalGap,
138 Real marketRateAccuracy,
139 Real lowerRateBound,
140 Real upperRateBound,
141 int adjustments,
142 std::vector<Real> smileMoneyCheckpoints = std::vector<Real>(),
143 ext::shared_ptr<CustomSmileFactory> customSmileFactory =
144 ext::shared_ptr<CustomSmileFactory>())
145 : yGridPoints_(yGridPoints), yStdDevs_(yStdDevs),
146 gaussHermitePoints_(gaussHermitePoints), digitalGap_(digitalGap),
147 marketRateAccuracy_(marketRateAccuracy), lowerRateBound_(lowerRateBound),
148 upperRateBound_(upperRateBound), adjustments_(adjustments),
149 smileMoneynessCheckpoints_(std::move(smileMoneyCheckpoints)),
150 customSmileFactory_(std::move(customSmileFactory)) {}
151
152 void validate() {
153
156
157 if ((adjustments_ & KahaleSmile) != 0 &&
160 }
161
163 (adjustments_ & KahaleSmile) == 0 ||
165 ,
166 "Only one of KahaleSmile, SabrSmile and CustomSmile"
167 "can be specified at the same time");
168 QL_REQUIRE(yGridPoints_ > 0, "At least one grid point ("
169 << yGridPoints_
170 << ") for the state process "
171 "discretization must be "
172 "given");
173 QL_REQUIRE(yStdDevs_ > 0.0,
174 "Multiple of standard deviations covered by state "
175 "process discretization ("
176 << yStdDevs_ << ") must be positive");
178 "Number of gauss hermite integration points ("
179 << gaussHermitePoints_ << ") must be positive");
180 QL_REQUIRE(digitalGap_ > 0.0, "Digital gap ("
181 << digitalGap_
182 << ") must be positive");
184 "Market rate accuracy (" << marketRateAccuracy_
185 << ") must be positive");
187 (adjustments_ & KahaleSmile) == 0 || lowerRateBound_ == 0.0,
188 "If Kahale extrapolation is used, the lower rate bound ("
189 << lowerRateBound_ << ") must be zero.");
192 "Lower rate bound ("
194 << ") must be strictly less than upper rate bound ("
195 << upperRateBound_ << ")");
198 "missing CustomSmileFactoy");
199 }
200
202 yGridPoints_ = n;
203 return *this;
204 }
206 yStdDevs_ = s;
207 return *this;
208 }
211 return *this;
212 }
214 digitalGap_ = d;
215 return *this;
216 }
219 return *this;
220 }
222 upperRateBound_ = u;
223 return *this;
224 }
226 lowerRateBound_ = l;
227 return *this;
228 }
230 adjustments_ = a;
231 return *this;
232 }
234 adjustments_ |= a;
235 return *this;
236 }
238 adjustments_ &= ~a;
239 return *this;
240 }
241 ModelSettings &withSmileMoneynessCheckpoints(const std::vector<Real>& m) {
243 return *this;
244 }
245 ModelSettings &withCustomSmileFactory(const ext::shared_ptr<CustomSmileFactory>& f) {
247 return *this;
248 }
249
256 std::vector<Real> smileMoneynessCheckpoints_;
257 ext::shared_ptr<CustomSmileFactory> customSmileFactory_;
258 };
259
263 std::vector<Date> paymentDates_;
264 std::vector<Real> yearFractions_;
267 ext::shared_ptr<SmileSection> smileSection_;
268 ext::shared_ptr<SmileSection> rawSmileSection_;
271 };
272
273// utility macro to write messages to the model outputs
274
275#define QL_MFMESSAGE(o, message) \
276 { \
277 std::ostringstream os; \
278 os << message; \
279 o.messages_.push_back(os.str()); \
280 }
281
283 bool dirty_;
285 std::vector<Date> expiries_;
286 std::vector<Period> tenors_;
287 std::vector<Real> atm_;
288 std::vector<Real> annuity_;
289 std::vector<Real> adjustmentFactors_;
290 std::vector<Real> digitalsAdjustmentFactors_;
291 std::vector<std::string> messages_;
292 std::vector<std::vector<Real> > smileStrikes_;
293 std::vector<std::vector<Real> > marketRawCallPremium_;
294 std::vector<std::vector<Real> > marketRawPutPremium_;
295 std::vector<std::vector<Real> > marketCallPremium_;
296 std::vector<std::vector<Real> > marketPutPremium_;
297 std::vector<std::vector<Real> > modelCallPremium_;
298 std::vector<std::vector<Real> > modelPutPremium_;
299 std::vector<std::vector<Real> > marketVega_;
300 std::vector<Real> marketZerorate_;
301 std::vector<Real> modelZerorate_;
302 };
303
304 // Constructor for a swaption smile calibrated model
306 Real reversion,
307 std::vector<Date> volstepdates,
308 std::vector<Real> volatilities,
309 const Handle<SwaptionVolatilityStructure>& swaptionVol,
310 const std::vector<Date>& swaptionExpiries,
311 const std::vector<Period>& swaptionTenors,
312 const ext::shared_ptr<SwapIndex>& swapIndexBase,
314
315 // Constructor for a caplet smile calibrated model
317 Real reversion,
318 std::vector<Date> volstepdates,
319 std::vector<Real> volatilities,
321 const std::vector<Date>& capletExpiries,
322 ext::shared_ptr<IborIndex> iborIndex,
324
325 const ModelSettings &modelSettings() const { return modelSettings_; }
326 const ModelOutputs &modelOutputs() const;
327
328 const Date &numeraireDate() const { return numeraireDate_; }
329 const Time &numeraireTime() const { return numeraireTime_; }
330
331 const Array &volatility() const { return sigma_.params(); }
332
333 void calibrate(const std::vector<ext::shared_ptr<CalibrationHelper> >& helpers,
334 OptimizationMethod& method,
337 const std::vector<Real>& weights = std::vector<Real>(),
338 const std::vector<bool>& fixParameters = std::vector<bool>()) override {
339
340 CalibratedModel::calibrate(helpers, method, endCriteria, constraint, weights,
341 fixParameters.empty() ? FixedFirstVolatility() :
342 fixParameters);
343 }
344
345 void calibrate(const std::vector<ext::shared_ptr<BlackCalibrationHelper> >& helpers,
346 OptimizationMethod& method,
349 const std::vector<Real>& weights = std::vector<Real>(),
350 const std::vector<bool>& fixParameters = std::vector<bool>()) {
351
352 std::vector<ext::shared_ptr<CalibrationHelper> > tmp(helpers.size());
353 for (Size i=0; i<helpers.size(); ++i)
354 tmp[i] = ext::static_pointer_cast<CalibrationHelper>(helpers[i]);
355
356 calibrate(tmp, method, endCriteria, constraint, weights, fixParameters);
357 }
358
359 void update() override { LazyObject::update(); }
360
361 // returns the indices of the af region from the last smile update
362 std::vector<std::pair<Size, Size> > arbitrageIndices() const {
363 calculate();
364 return arbitrageIndices_;
365 }
366
367 // forces the indices of the af region (useful for sensitivity calculation)
368 // if an empty vector is given, the dynamic calculation is used again
369 void forceArbitrageIndices(const std::vector<std::pair<Size,Size> >& indices) {
370 forcedArbitrageIndices_ = indices;
371 this->update();
372 }
373
374 protected:
375 Real numeraireImpl(Time t, Real y, const Handle<YieldTermStructure>& yts) const override;
376
377 Real
378 zerobondImpl(Time T, Time t, Real y, const Handle<YieldTermStructure>& yts) const override;
379
380 void generateArguments() override {
381 // if calculate triggers performCalculations, updateNumeraireTabulations
382 // is called twice. If we can not check the lazy object status this seem
383 // hard to avoid though.
384 calculate();
387 }
388
389 void performCalculations() const override {
391 updateTimes();
392 updateSmiles();
394 }
395
396 std::vector<bool> FixedFirstVolatility() const {
397 std::vector<bool> c(volatilities_.size(), false);
398 c[0] = true;
399 return c;
400 }
401
402 private:
403
404 void initialize();
405 void updateTimes() const;
406 void updateTimes1() const;
407 void updateTimes2() const;
408
409 void updateSmiles() const;
410 void updateNumeraireTabulation() const;
411
412 void makeSwaptionCalibrationPoint(const Date &expiry,
413 const Period &tenor);
414 void makeCapletCalibrationPoint(const Date &expiry);
415
416 Real marketSwapRate(const Date& expiry,
417 const CalibrationPoint& p,
418 Real digitalPrice,
419 Real guess = 0.03,
420 Real shift = 0.0) const;
421 Real marketDigitalPrice(const Date& expiry,
422 const CalibrationPoint& p,
423 const Option::Type& type,
424 Real strike) const;
425
426 Array deflatedZerobondArray(Time T, Time t, const Array& y) const;
427 Array numeraireArray(Time t, const Array& y) const;
428 Array zerobondArray(Time T, Time t, const Array& y) const;
429
430 Real deflatedZerobond(Time T, Time t = 0.0, Real y = 0.0) const;
431
432 // the following methods (tagged internal) are indended only to produce
433 // the volatility diagnostics in the model outputs
434 // due to the special convention of the instruments used for numeraire
435 // calibration there is on direct way to use the usual pricing engines
436 // for this purpose
437
439 const Date& fixing,
440 const Date& referenceDate = Null<Date>(),
441 Real y = 0.0,
442 bool zeroFixingDays = false,
443 ext::shared_ptr<IborIndex> iborIdx = ext::shared_ptr<IborIndex>()) const;
444
445 Real
446 swapRateInternal(const Date& fixing,
447 const Period& tenor,
448 const Date& referenceDate = Null<Date>(),
449 Real y = 0.0,
450 bool zeroFixingDays = false,
451 ext::shared_ptr<SwapIndex> swapIdx = ext::shared_ptr<SwapIndex>()) const;
452
454 const Date& fixing,
455 const Period& tenor,
456 const Date& referenceDate = Null<Date>(),
457 Real y = 0.0,
458 bool zeroFixingDays = false,
459 ext::shared_ptr<SwapIndex> swapIdx = ext::shared_ptr<SwapIndex>()) const;
460
462 const Option::Type& type,
463 const Date& expiry,
464 Rate strike,
465 const Date& referenceDate = Null<Date>(),
466 Real y = 0.0,
467 bool zeroFixingDays = false,
468 ext::shared_ptr<IborIndex> iborIdx = ext::shared_ptr<IborIndex>()) const;
469
471 const Option::Type& type,
472 const Date& expiry,
473 const Period& tenor,
474 Rate strike,
475 const Date& referenceDate = Null<Date>(),
476 Real y = 0.0,
477 bool zeroFixingDays = false,
478 const ext::shared_ptr<SwapIndex>& swapIdx = ext::shared_ptr<SwapIndex>()) const;
479
481 public:
482 ZeroHelper(const MarkovFunctional *model, const Date &expiry,
483 const CalibrationPoint &p, const Real marketPrice)
484 : model_(model), marketPrice_(marketPrice), expiry_(expiry),
485 p_(p) {}
486 Real operator()(Real strike) const {
487 Real modelPrice = model_->marketDigitalPrice(
488 expiry_, p_, Option::Call, strike);
489 return modelPrice - marketPrice_;
490 };
493 const Date &expiry_;
495 };
496
499
501
502 ext::shared_ptr<Matrix> discreteNumeraire_;
503 // vector of interpolated numeraires in y direction for all calibration
504 // times
505 std::vector<ext::shared_ptr<Interpolation> > numeraire_;
506
509
510 std::vector<Date> volstepdates_;
511 mutable std::vector<Time> volsteptimes_;
512 mutable Array volsteptimesArray_; // FIXME this is redundant (just a copy of
513 // volsteptimes_)
514 std::vector<Real> volatilities_;
515
518
521
523 std::vector<Period> swaptionTenors_;
524 ext::shared_ptr<SwapIndex> swapIndexBase_;
525 ext::shared_ptr<IborIndex> iborIndex_;
526
527 mutable std::map<Date, CalibrationPoint> calibrationPoints_;
528 mutable std::vector<Real> times_;
530
533
534 mutable std::vector<std::pair<Size,Size> > arbitrageIndices_;
535 std::vector<std::pair<Size,Size> > forcedArbitrageIndices_;
536 };
537
538 std::ostream &operator<<(std::ostream &out,
540}
541
542#endif
1-D array used in linear algebra.
Definition: array.hpp:52
Calibrated model class.
Definition: model.hpp:86
EndCriteria::Type endCriteria() const
Returns end criteria result.
Definition: model.hpp:113
const ext::shared_ptr< Constraint > & constraint() const
Definition: model.hpp:160
virtual void calibrate(const std::vector< ext::shared_ptr< CalibrationHelper > > &, OptimizationMethod &method, const EndCriteria &endCriteria, const Constraint &constraint=Constraint(), const std::vector< Real > &weights=std::vector< Real >(), const std::vector< bool > &fixParameters=std::vector< bool >())
Calibrate to a set of market instruments (usually caps/swaptions)
Definition: model.cpp:75
Base constraint class.
Definition: constraint.hpp:35
Concrete date class.
Definition: date.hpp:125
Criteria to end optimization process:
Definition: endcriteria.hpp:40
void performCalculations() const override
Shared handle to an observable.
Definition: handle.hpp:41
virtual void calculate() const
Definition: lazyobject.hpp:253
void update() override
Definition: lazyobject.hpp:188
virtual ext::shared_ptr< CustomSmileSection > smileSection(const ext::shared_ptr< SmileSection > &source, Real atm) const =0
virtual Real inverseDigitalCall(Real price, Real discount=1.0) const =0
ZeroHelper(const MarkovFunctional *model, const Date &expiry, const CalibrationPoint &p, const Real marketPrice)
std::vector< Date > volstepdates_
Real zerobondImpl(Time T, Time t, Real y, const Handle< YieldTermStructure > &yts) const override
void calibrate(const std::vector< ext::shared_ptr< BlackCalibrationHelper > > &helpers, OptimizationMethod &method, const EndCriteria &endCriteria, const Constraint &constraint=Constraint(), const std::vector< Real > &weights=std::vector< Real >(), const std::vector< bool > &fixParameters=std::vector< bool >())
void performCalculations() const override
std::vector< std::pair< Size, Size > > arbitrageIndices() const
Array zerobondArray(Time T, Time t, const Array &y) const
const ModelOutputs & modelOutputs() const
Handle< SwaptionVolatilityStructure > swaptionVol_
void forceArbitrageIndices(const std::vector< std::pair< Size, Size > > &indices)
ext::shared_ptr< SwapIndex > swapIndexBase_
Real swapRateInternal(const Date &fixing, const Period &tenor, const Date &referenceDate=Null< Date >(), Real y=0.0, bool zeroFixingDays=false, ext::shared_ptr< SwapIndex > swapIdx=ext::shared_ptr< SwapIndex >()) const
std::vector< std::pair< Size, Size > > forcedArbitrageIndices_
Real swapAnnuityInternal(const Date &fixing, const Period &tenor, const Date &referenceDate=Null< Date >(), Real y=0.0, bool zeroFixingDays=false, ext::shared_ptr< SwapIndex > swapIdx=ext::shared_ptr< SwapIndex >()) const
Handle< OptionletVolatilityStructure > capletVol_
Array deflatedZerobondArray(Time T, Time t, const Array &y) const
std::map< Date, CalibrationPoint > calibrationPoints_
ext::shared_ptr< IborIndex > iborIndex_
ext::shared_ptr< Matrix > discreteNumeraire_
Real marketDigitalPrice(const Date &expiry, const CalibrationPoint &p, const Option::Type &type, Real strike) const
const Array & volatility() const
const Date & numeraireDate() const
Real swaptionPriceInternal(const Option::Type &type, const Date &expiry, const Period &tenor, Rate strike, const Date &referenceDate=Null< Date >(), Real y=0.0, bool zeroFixingDays=false, const ext::shared_ptr< SwapIndex > &swapIdx=ext::shared_ptr< SwapIndex >()) const
Real deflatedZerobond(Time T, Time t=0.0, Real y=0.0) const
void makeCapletCalibrationPoint(const Date &expiry)
std::vector< Period > swaptionTenors_
std::vector< Date > swaptionExpiries_
std::vector< ext::shared_ptr< Interpolation > > numeraire_
std::vector< Date > capletExpiries_
std::vector< Real > volatilities_
std::vector< std::pair< Size, Size > > arbitrageIndices_
std::vector< bool > FixedFirstVolatility() const
Array numeraireArray(Time t, const Array &y) const
void makeSwaptionCalibrationPoint(const Date &expiry, const Period &tenor)
const ModelSettings & modelSettings() const
Real marketSwapRate(const Date &expiry, const CalibrationPoint &p, Real digitalPrice, Real guess=0.03, Real shift=0.0) const
Real forwardRateInternal(const Date &fixing, const Date &referenceDate=Null< Date >(), Real y=0.0, bool zeroFixingDays=false, ext::shared_ptr< IborIndex > iborIdx=ext::shared_ptr< IborIndex >()) const
std::vector< Time > volsteptimes_
Real capletPriceInternal(const Option::Type &type, const Date &expiry, Rate strike, const Date &referenceDate=Null< Date >(), Real y=0.0, bool zeroFixingDays=false, ext::shared_ptr< IborIndex > iborIdx=ext::shared_ptr< IborIndex >()) const
Real numeraireImpl(Time t, Real y, const Handle< YieldTermStructure > &yts) const override
const Time & numeraireTime() const
void calibrate(const std::vector< ext::shared_ptr< CalibrationHelper > > &helpers, OptimizationMethod &method, const EndCriteria &endCriteria, const Constraint &constraint=Constraint(), const std::vector< Real > &weights=std::vector< Real >(), const std::vector< bool > &fixParameters=std::vector< bool >()) override
Calibrate to a set of market instruments (usually caps/swaptions)
template class providing a null value for a given type.
Definition: null.hpp:76
Abstract class for constrained optimization method.
Definition: method.hpp:36
Base class for model arguments.
Definition: parameter.hpp:38
const Array & params() const
Definition: parameter.hpp:50
interest rate volatility smile section
const Handle< YieldTermStructure > & termStructure() const
Definition: model.hpp:77
const DefaultType & t
#define QL_REQUIRE(condition, message)
throw an error if the given pre-condition is not verified
Definition: errors.hpp:117
Date d
basic interface for one factor interest rate models
Real Time
continuous quantity with 1-year units
Definition: types.hpp:62
QL_REAL Real
real number
Definition: types.hpp:50
Real Rate
interest rates
Definition: types.hpp:70
std::size_t Size
size of a container
Definition: types.hpp:58
base class for 1-D interpolations
State process for markov functional model.
Definition: any.hpp:35
std::ostream & operator<<(std::ostream &out, GFunctionFactory::YieldCurveModel type)
STL namespace.
optionlet (caplet/floorlet) volatility structure
Smile section base class.
ext::shared_ptr< SmileSection > smileSection_
ext::shared_ptr< SmileSection > rawSmileSection_
std::vector< std::vector< Real > > modelPutPremium_
std::vector< std::vector< Real > > marketPutPremium_
std::vector< std::vector< Real > > smileStrikes_
std::vector< std::vector< Real > > modelCallPremium_
std::vector< std::vector< Real > > marketVega_
std::vector< std::vector< Real > > marketCallPremium_
std::vector< std::vector< Real > > marketRawCallPremium_
std::vector< std::vector< Real > > marketRawPutPremium_
ModelSettings & withCustomSmileFactory(const ext::shared_ptr< CustomSmileFactory > &f)
ModelSettings(Size yGridPoints, Real yStdDevs, Size gaussHermitePoints, Real digitalGap, Real marketRateAccuracy, Real lowerRateBound, Real upperRateBound, int adjustments, std::vector< Real > smileMoneyCheckpoints=std::vector< Real >(), ext::shared_ptr< CustomSmileFactory > customSmileFactory=ext::shared_ptr< CustomSmileFactory >())
ext::shared_ptr< CustomSmileFactory > customSmileFactory_
ModelSettings & withSmileMoneynessCheckpoints(const std::vector< Real > &m)
Swaption volatility structure.