Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
localvolmodelbuilder.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2020 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
20
23
25
26#include <ql/exercise.hpp>
27#include <ql/instruments/payoffs.hpp>
28#include <ql/instruments/vanillaoption.hpp>
29#include <ql/termstructures/volatility/equityfx/andreasenhugelocalvoladapter.hpp>
30#include <ql/termstructures/volatility/equityfx/andreasenhugevolatilityinterpl.hpp>
31#include <ql/termstructures/volatility/equityfx/localconstantvol.hpp>
32#include <ql/termstructures/volatility/equityfx/localvolsurface.hpp>
33#include <ql/termstructures/volatility/equityfx/noexceptlocalvolsurface.hpp>
34#include <ql/time/daycounters/actualactual.hpp>
35#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
36
37namespace ore {
38namespace data {
39
41 const std::vector<Handle<YieldTermStructure>>& curves,
42 const std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess>>& processes,
43 const std::set<Date>& simulationDates, const std::set<Date>& addDates, const Size timeStepsPerYear,
44 const Type lvType, const std::vector<Real>& calibrationMoneyness, const bool dontCalibrate)
45 : BlackScholesModelBuilderBase(curves, processes, simulationDates, addDates, timeStepsPerYear), lvType_(lvType),
46 calibrationMoneyness_(calibrationMoneyness), dontCalibrate_(dontCalibrate) {
47 // we have to observe the whole vol surface for the Dupire implementation unfortunately; we can specify the time
48 // steps that are relevant, but not a set of discrete strikes
49 if (lvType == Type::Dupire) {
50 for (auto const& p : processes_) {
51 marketObserver_->registerWith(p->blackVolatility());
52 }
53 }
54}
55
56std::vector<QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess>> LocalVolModelBuilder::getCalibratedProcesses() const {
57
58 QL_REQUIRE(lvType_ != Type::AndreasenHuge || !calibrationMoneyness_.empty(), "no calibration moneyness provided");
59
60 calculate();
61
62 std::vector<QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess>> processes;
63
64 for (Size l = 0; l < processes_.size(); ++l) {
65
66 Handle<LocalVolTermStructure> localVol;
67 if (dontCalibrate_) {
68 localVol = Handle<LocalVolTermStructure>(
69 QuantLib::ext::make_shared<LocalConstantVol>(0, NullCalendar(), 0.10, ActualActual(ActualActual::ISDA)));
70 } else if (lvType_ == Type::AndreasenHuge) {
71 // for checking arbitrage free input prices, just for logging purposes at this point
72 // notice that we need a uniform strike grid here, so this is not the same as the one below
73 // we choose the strike grid to be the one for the last calibration point
74 std::vector<Real> checkMaturities, checkMoneynesses, atmForwards;
75 std::vector<std::vector<Real>> callPrices;
76
77 // set up Andreasen Huge Volatility interpolation for each underyling
78 // we choose the calibration set to be OTM options on the effective future simulation dates
79 // with strikes given in terms of moneyness K / atmForward
80 AndreasenHugeVolatilityInterpl::CalibrationSet calSet;
81 for (auto const& d : effectiveSimulationDates_) {
82 if (d <= curves_.front()->referenceDate())
83 continue;
84 Real t = processes_.front()->riskFreeRate()->timeFromReference(d);
85 checkMaturities.push_back(t);
86 Real atmLevel =
87 atmForward(processes_[l]->x0(), processes_[l]->riskFreeRate(), processes_[l]->dividendYield(), t);
88 Real atmMarketVol = std::max(1e-4, processes_[l]->blackVolatility()->blackVol(t, atmLevel));
89 callPrices.push_back(std::vector<Real>());
90 atmForwards.push_back(atmLevel);
91 for (Size i = 0; i < calibrationMoneyness_.size(); ++i) {
92 Real strike = atmLevel * std::exp(calibrationMoneyness_[i] * atmMarketVol * std::sqrt(t));
93 Real marketVol = processes_[l]->blackVolatility()->blackVol(t, strike);
94 // skip option with effective moneyness < 0.0001 or > 0.9999 (TODO, hardcoded limits here?)
95 if (std::fabs(calibrationMoneyness_[i]) > 3.72)
96 continue;
97 auto option =
98 QuantLib::ext::make_shared<VanillaOption>(QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Call, strike),
99 QuantLib::ext::make_shared<EuropeanExercise>(d));
100 calSet.push_back(std::make_pair(option, QuantLib::ext::make_shared<SimpleQuote>(marketVol)));
101 option->setPricingEngine(QuantLib::ext::make_shared<AnalyticEuropeanEngine>(processes_[l]));
102 callPrices.back().push_back(option->NPV());
103 if (d == *effectiveSimulationDates_.rbegin()) {
104 checkMoneynesses.push_back(strike / atmLevel);
105 }
106 }
107 }
108
109 // arbitrage check
110 QuantExt::CarrMadanSurface cmCheck(checkMaturities, checkMoneynesses, processes_[l]->x0(), atmForwards,
111 callPrices);
112 if (!cmCheck.arbitrageFree()) {
113 WLOG("Andreasen-Huge local vol calibration for process #" << l
114 << ":, input vol is not arbitrage free:");
115 DLOG("time,moneyness,callSpread,butterfly,calendar");
116 for (Size i = 0; i < checkMaturities.size(); ++i)
117 for (Size j = 0; j < checkMoneynesses.size(); ++j)
118 DLOG(checkMaturities[i] << "," << checkMoneynesses[i] << "," << std::boolalpha
119 << cmCheck.callSpreadArbitrage()[i][j] << ","
120 << cmCheck.butterflyArbitrage()[i][j] << ","
121 << cmCheck.calendarArbitrage()[i][j]);
122 }
123
124 // TODO using some hardcoded values here, expose to configuration?
125 auto ah = QuantLib::ext::make_shared<AndreasenHugeVolatilityInterpl>(
126 calSet, processes_[l]->stateVariable(), processes_[l]->riskFreeRate(), processes_[l]->dividendYield(),
127 AndreasenHugeVolatilityInterpl::CubicSpline, AndreasenHugeVolatilityInterpl::Call, 500, Null<Real>(),
128 Null<Real>());
129 localVol = Handle<LocalVolTermStructure>(QuantLib::ext::make_shared<AndreasenHugeLocalVolAdapter>(ah));
130 //localVol->enableExtrapolation();
131 DLOG("Andreasen-Huge local vol calibration for process #"
132 << l
133 << ": "
134 "calibration error min="
135 << std::scientific << std::setprecision(6) << QuantLib::ext::get<0>(ah->calibrationError()) << " max="
136 << QuantLib::ext::get<1>(ah->calibrationError()) << " avg=" << QuantLib::ext::get<2>(ah->calibrationError()));
137 } else if (lvType_ == Type::Dupire) {
138 localVol = Handle<LocalVolTermStructure>(
139 QuantLib::ext::make_shared<LocalVolSurface>(processes_[l]->blackVolatility(), processes_[l]->riskFreeRate(),
140 processes_[l]->dividendYield(), processes_[l]->stateVariable()));
141 } else if (lvType_ == Type::DupireFloored) {
142 localVol = Handle<LocalVolTermStructure>(
143 QuantLib::ext::make_shared<NoExceptLocalVolSurface>(processes_[l]->blackVolatility(), processes_[l]->riskFreeRate(),
144 processes_[l]->dividendYield(), processes_[l]->stateVariable(),
145 0.0));
146 } else {
147 QL_FAIL("unexpected local vol type");
148 }
149
150 processes.push_back(QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
151 processes_[l]->stateVariable(), processes_[l]->dividendYield(), processes_[l]->riskFreeRate(),
152 processes_[l]->blackVolatility(), localVol));
153 }
154
155 return processes;
156}
157
158std::vector<std::vector<Real>> LocalVolModelBuilder::getCurveTimes() const {
159 std::vector<Real> timesExt(discretisationTimeGrid_.begin() + 1, discretisationTimeGrid_.end());
160 for (auto const& d : addDates_) {
161 if (d > curves_.front()->referenceDate()) {
162 timesExt.push_back(curves_.front()->timeFromReference(d));
163 }
164 }
165 std::sort(timesExt.begin(), timesExt.end());
166 auto it = std::unique(timesExt.begin(), timesExt.end(),
167 [](const Real x, const Real y) { return QuantLib::close_enough(x, y); });
168 timesExt.resize(std::distance(timesExt.begin(), it));
169 return std::vector<std::vector<Real>>(allCurves_.size(), timesExt);
170}
171
172std::vector<std::vector<std::pair<Real, Real>>> LocalVolModelBuilder::getVolTimesStrikes() const {
173 std::vector<std::vector<std::pair<Real, Real>>> volTimesStrikes;
174 // for the Dupire implementation we observe the whole vol surface anyhow (see ctor above)
175 if (lvType_ == Type::Dupire)
176 return volTimesStrikes;
177 std::vector<Real> times;
179 for (auto const& d : effectiveSimulationDates_) {
180 if (d > curves_.front()->referenceDate())
181 times.push_back(processes_.front()->riskFreeRate()->timeFromReference(d));
182 }
183 } else {
184 times = std::vector<Real>(discretisationTimeGrid_.begin() + 1, discretisationTimeGrid_.end());
185 }
186 for (auto const& p : processes_) {
187 volTimesStrikes.push_back(std::vector<std::pair<Real, Real>>());
188 for (auto const t : times) {
189 Real atmLevel = atmForward(p->x0(), p->riskFreeRate(), p->dividendYield(), t);
190 Real atmMarketVol = std::max(1e-4, p->blackVolatility()->blackVol(t, atmLevel));
191 for (auto const m : calibrationMoneyness_) {
192 Real strike = atmLevel * std::exp(m * atmMarketVol * std::sqrt(t));
193 volTimesStrikes.back().push_back(std::make_pair(t, strike));
194 }
195 }
196 }
197 return volTimesStrikes;
198}
199
200} // namespace data
201} // namespace ore
const std::vector< std::vector< bool > > & butterflyArbitrage() const
const std::vector< std::vector< bool > > & calendarArbitrage() const
const std::vector< std::vector< bool > > & callSpreadArbitrage() const
std::vector< Handle< YieldTermStructure > > allCurves_
QuantLib::ext::shared_ptr< MarketObserver > marketObserver_
const std::vector< QuantLib::ext::shared_ptr< GeneralizedBlackScholesProcess > > processes_
const std::vector< Handle< YieldTermStructure > > curves_
std::vector< ext::shared_ptr< GeneralizedBlackScholesProcess > > getCalibratedProcesses() const override
LocalVolModelBuilder(const std::vector< Handle< YieldTermStructure > > &curves, const std::vector< ext::shared_ptr< GeneralizedBlackScholesProcess > > &processes, const std::set< Date > &simulationDates={}, const std::set< Date > &addDates={}, const Size timeStepsPerYear=1, const Type lvType=Type::Dupire, const std::vector< Real > &calibrationMoneyness={ -2.0, -1.0, 0.0, 1.0, 2.0 }, const bool dontCalibrate=false)
std::vector< std::vector< std::pair< Real, Real > > > getVolTimesStrikes() const override
std::vector< std::vector< Real > > getCurveTimes() const override
const std::vector< Real > calibrationMoneyness_
builder for an array of local vol processes
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define WLOG(text)
Logging Macro (Level = Warning)
Definition: log.hpp:550
Shared utilities for model building and calibration.
Real atmForward(const Real s0, const Handle< YieldTermStructure > &r, const Handle< YieldTermStructure > &q, const Real t)
helper function that computes the atm forward
Definition: utilities.cpp:483
Serializable Credit Default Swap.
Definition: namespaces.docs:23