Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
eqcommoptionsurfacestripper.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 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#include <boost/make_shared.hpp>
21#include <ql/instruments/impliedvolatility.hpp>
22#include <ql/math/solver1d.hpp>
23#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
24#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
28
29using std::function;
30using std::map;
31using std::pair;
32using std::set;
33using std::vector;
34using namespace QuantLib;
35
36namespace {
37
38// Utility method to create the list of options to be used at an expiry date for stripping.
39function<bool(Real,Real)> comp = [](Real a, Real b) { return !close(a, b) && a < b; };
40
41map<Real, Option::Type, decltype(comp)> createStrikes(Real forward, const vector<Real>& cStrikes,
42 const vector<Real>& pStrikes, bool preferOutOfTheMoney) {
43
44 // Firstly create the restricted vector of call and put strikes.
45 vector<Real> rcStks;
46 copy_if(cStrikes.begin(), cStrikes.end(), back_inserter(rcStks),[forward, preferOutOfTheMoney](Real stk) {
47 return (preferOutOfTheMoney && stk >= forward) || (!preferOutOfTheMoney && stk <= forward); });
48 vector<Real> rpStks;
49 copy_if(pStrikes.begin(), pStrikes.end(), back_inserter(rpStks), [forward, preferOutOfTheMoney](Real stk) {
50 return (preferOutOfTheMoney && stk <= forward) || (!preferOutOfTheMoney && stk >= forward); });
51
52 // Create the empty map.
53 map<Real, Option::Type, decltype(comp)> res(comp);
54
55 // If both restricted vectors are empty, return an empty map
56 if (rcStks.empty() && rpStks.empty())
57 return res;
58
59 // At least one of the restricted strike vectors are non empty so populate the map.
60 if (!rcStks.empty() && !rpStks.empty()) {
61 // Most common case hopefully. Use both sets of strikes.
62 // Could have the fwd strike in both restricted sets from the logic above. Favour Call here via overwrite.
63 for (Real stk : rpStks)
64 res[stk] = Option::Put;
65 for (Real stk : rcStks)
66 res[stk] = Option::Call;
67 } else if (rpStks.empty()) {
68 // If restricted put strikes are empty, use all the call strikes
69 for (Real stk : cStrikes)
70 res[stk] = Option::Call;
71 } else if (rcStks.empty()) {
72 // If restricted call strikes are empty, use all the put strikes
73 for (Real stk : pStrikes)
74 res[stk] = Option::Put;
75 }
76
77 return res;
78}
79
80}
81
82namespace QuantExt {
83
85 const QuantLib::ext::shared_ptr<OptionInterpolatorBase>& callSurface,
86 const QuantLib::ext::shared_ptr<OptionInterpolatorBase>& putSurface,
87 const Calendar& calendar,
88 const DayCounter& dayCounter,
89 Exercise::Type type,
90 bool lowerStrikeConstExtrap,
91 bool upperStrikeConstExtrap,
92 bool timeFlatExtrapolation,
93 bool preferOutOfTheMoney,
94 Solver1DOptions solverOptions)
95 : callSurface_(callSurface),
96 putSurface_(putSurface),
97 calendar_(calendar),
98 dayCounter_(dayCounter),
99 type_(type),
100 lowerStrikeConstExtrap_(lowerStrikeConstExtrap),
101 upperStrikeConstExtrap_(upperStrikeConstExtrap),
102 timeFlatExtrapolation_(timeFlatExtrapolation),
103 preferOutOfTheMoney_(preferOutOfTheMoney),
104 solverOptions_(solverOptions),
105 havePrices_(QuantLib::ext::dynamic_pointer_cast<OptionPriceSurface>(callSurface_)) {
106
107 QL_REQUIRE(callSurface_->referenceDate() == putSurface_->referenceDate(),
108 "Mismatch between Call and Put reference dates in OptionSurfaceStripper");
109
110 registerWith(Settings::instance().evaluationDate());
111
112 // Set up that is only needed if we have price based surfaces and we are stripping volatilities.
113 if (havePrices_) {
114
115 // Check that there is also a put price surface
116 QL_REQUIRE(QuantLib::ext::dynamic_pointer_cast<OptionPriceSurface>(putSurface_),
117 "OptionSurfaceStripper: call price surface provided but no put price surface.");
118
119 setUpSolver();
120 }
121}
122
123OptionSurfaceStripper::PriceError::PriceError(const VanillaOption& option, SimpleQuote& volatility, Real targetPrice)
124 : option_(option), volatility_(volatility), targetPrice_(targetPrice) {}
125
126Real OptionSurfaceStripper::PriceError::PriceError::operator()(Volatility x) const {
127
128 volatility_.setValue(x);
129
130 // Barone Adesi Whaley fails for very small variance, so wrap in a try catch
131 Real npv;
132 try {
133 npv = option_.NPV();
134 } catch (...) {
135 npv = 0.0;
136 }
137
138 return npv - targetPrice_;
139}
140
142
143 // Create a set of all expiries
144 auto tmp = callSurface_->expiries();
145 set<Date> allExpiries(tmp.begin(), tmp.end());
146 tmp = putSurface_->expiries();
147 allExpiries.insert(tmp.begin(), tmp.end());
148
149 QuantLib::ext::shared_ptr<BlackVarianceSurfaceSparse> callVolSurface;
150 QuantLib::ext::shared_ptr<BlackVarianceSurfaceSparse> putVolSurface;
151
152 // Switch based on whether surface is direct volatilities or prices to be stripped.
153 QuantLib::ext::shared_ptr<PricingEngine> engine;
154 QuantLib::ext::shared_ptr<SimpleQuote> volQuote = QuantLib::ext::make_shared<SimpleQuote>(0.1);
155 if (havePrices_) {
156
157 // a black scholes process
158 QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess> gbsp = process(volQuote);
159
160 // hard code the engines here
161 if (type_ == Exercise::American) {
162 engine = QuantLib::ext::make_shared<QuantExt::BaroneAdesiWhaleyApproximationEngine>(gbsp);
163 } else if (type_ == Exercise::European) {
164 engine = QuantLib::ext::make_shared<QuantExt::AnalyticEuropeanEngine>(gbsp);
165 } else {
166 QL_FAIL("Unsupported exercise type for option stripping");
167 }
168
169 } else {
170 // we have variance surfaces, explicitly cast so we can look up vol later
171 callVolSurface = QuantLib::ext::dynamic_pointer_cast<BlackVarianceSurfaceSparse>(callSurface_);
172 putVolSurface = QuantLib::ext::dynamic_pointer_cast<BlackVarianceSurfaceSparse>(putSurface_);
173 }
174
175 // Need to populate these below to feed to BlackVarianceSurfaceSparse
176 vector<Real> volStrikes;
177 vector<Real> volData;
178 vector<Date> volExpiries;
179
180 // Loop over each expiry
181 for (const Date& expiry : allExpiries) {
182
183 // Get the forward price at expiry
184 Real fwd = forward(expiry);
185
186 // Get the call and put strikes at the expiry date. Each may be empty.
187 vector<Real> callStrikes = strikes(expiry, true);
188 vector<Real> putStrikes = strikes(expiry, false);
189
190 // We want a set of prices both sides of ATM forward
191 // If preferOutOfTheMoney_ is false, we take calls where strike < atm and puts where strike > atm.
192 // If preferOutOfTheMoney_ is true, we take calls where strike > atm and puts where strike < atm.
193 auto relevantStrikes = createStrikes(fwd, callStrikes, putStrikes, preferOutOfTheMoney_);
194 for (const auto& kv : relevantStrikes) {
195 if (havePrices_) {
196 // Only use the volatility if the root finding was successful
197 Real v = implyVol(expiry, kv.first, kv.second, engine, *volQuote);
198 if (v != Null<Real>()) {
199 volExpiries.push_back(expiry);
200 volStrikes.push_back(kv.first);
201 volData.push_back(v);
202 }
203 } else {
204 volExpiries.push_back(expiry);
205 volStrikes.push_back(kv.first);
206 Real v = kv.second == Option::Call ? callVolSurface->blackVol(expiry, kv.first) :
207 putVolSurface->blackVol(expiry, kv.first);
208 volData.push_back(v);
209 }
210 }
211 }
212
213 // Populate the variance surface.
214 volSurface_ = QuantLib::ext::make_shared<BlackVarianceSurfaceSparse>(
215 callSurface_->referenceDate(), calendar_, volExpiries, volStrikes, volData, dayCounter_,
217}
218
219vector<Real> OptionSurfaceStripper::strikes(const Date& expiry, bool isCall) const {
220
221 const QuantLib::ext::shared_ptr<OptionInterpolatorBase>& surface = isCall ? callSurface_ : putSurface_;
222 auto expiries = surface->expiries();
223 auto it = find(expiries.begin(), expiries.end(), expiry);
224
225 if (it != expiries.end()) {
226 return surface->strikes().at(distance(expiries.begin(), it));
227 } else {
228 return {};
229 }
230
231}
232
233Real OptionSurfaceStripper::implyVol(Date expiry, Real strike, Option::Type type,
234 QuantLib::ext::shared_ptr<PricingEngine> engine, SimpleQuote& volQuote) const {
235
236 // Create the option instrument used in the solver.
237 QuantLib::ext::shared_ptr<StrikedTypePayoff> payoff = QuantLib::ext::make_shared<PlainVanillaPayoff>(type, strike);
238 QuantLib::ext::shared_ptr<Exercise> exercise;
239 if (type_ == Exercise::American) {
240 exercise = QuantLib::ext::make_shared<AmericanExercise>(expiry);
241 } else if (type_ == Exercise::European) {
242 exercise = QuantLib::ext::make_shared<EuropeanExercise>(expiry);
243 } else {
244 QL_FAIL("OptionSurfaceStripper: unsupported exercise type for option stripping.");
245 }
246 VanillaOption option(payoff, exercise);
247 option.setPricingEngine(engine);
248
249 // Get the target price from the surface.
250 Real targetPrice = type == Option::Call ? callSurface_->getValue(expiry, strike)
251 : putSurface_->getValue(expiry, strike);
252
253 // Attempt to calculate the implied volatility.
254 Real vol = Null<Real>();
255 try {
256 PriceError f(option, volQuote, targetPrice);
257 vol = solver_(f);
258 } catch (const Error&) {
259 }
260
261 return vol;
262}
263
265
266 // Check that enough solver options have been provided.
267 const Real& guess = solverOptions_.initialGuess;
268 QL_REQUIRE(guess != Null<Real>(), "OptionSurfaceStripper: need a valid initial " <<
269 "guess for a price based surface.");
270
271 const Real& accuracy = solverOptions_.accuracy;
272 QL_REQUIRE(accuracy != Null<Real>(), "OptionSurfaceStripper: need a valid accuracy " <<
273 "for a price based surface.");
274
275 // Set maximum evaluations if provided.
276 if (solverOptions_.maxEvaluations != Null<Size>())
277 brent_.setMaxEvaluations(solverOptions_.maxEvaluations);
278
279 // Check and set the lower bound and upper bound
280 if (solverOptions_.lowerBound != Null<Real>() && solverOptions_.upperBound != Null<Real>()) {
281 QL_REQUIRE(solverOptions_.lowerBound < solverOptions_.upperBound, "OptionSurfaceStripper: lowerBound (" <<
282 solverOptions_.lowerBound << ") should be less than upperBound (" << solverOptions_.upperBound << ")");
283 }
284
285 if (solverOptions_.lowerBound != Null<Real>())
286 brent_.setLowerBound(solverOptions_.lowerBound);
287 if (solverOptions_.upperBound != Null<Real>())
288 brent_.setUpperBound(solverOptions_.upperBound);
289
290 // Choose a min/max or step solver based on parameters provided, favouring the min/max based version.
291 const Real& min = solverOptions_.minMax.first;
292 const Real& max = solverOptions_.minMax.second;
293 const Real& step = solverOptions_.step;
294 using std::placeholders::_1;
295 if (min != Null<Real>() && max != Null<Real>()) {
296 typedef Real (Brent::* MinMaxSolver)(const PriceError&, Real, Real, Real, Real) const;
297 solver_ = std::bind(static_cast<MinMaxSolver>(&Brent::solve), &brent_, _1, accuracy, guess, min, max);
298 } else if (step != Null<Real>()) {
299 typedef Real(Brent::* StepSolver)(const PriceError&, Real, Real, Real) const;
300 solver_ = std::bind(static_cast<StepSolver>(&Brent::solve), &brent_, _1, accuracy, guess, step);
301 } else {
302 QL_FAIL("OptionSurfaceStripper: need a valid step size or (min, max) pair for a price based surface.");
303 }
304
305}
306
307QuantLib::ext::shared_ptr<BlackVolTermStructure> OptionSurfaceStripper::volSurface() {
308 calculate();
309 return volSurface_;
310}
311
313 const Handle<QuantExt::EquityIndex2>& equityIndex,
314 const QuantLib::ext::shared_ptr<OptionInterpolatorBase>& callSurface,
315 const QuantLib::ext::shared_ptr<OptionInterpolatorBase>& putSurface,
316 const Calendar& calendar,
317 const DayCounter& dayCounter,
318 Exercise::Type type,
319 bool lowerStrikeConstExtrap,
320 bool upperStrikeConstExtrap,
321 bool timeFlatExtrapolation,
322 bool preferOutOfTheMoney,
323 Solver1DOptions solverOptions)
324 : OptionSurfaceStripper(callSurface, putSurface, calendar, dayCounter, type, lowerStrikeConstExtrap,
325 upperStrikeConstExtrap, timeFlatExtrapolation, preferOutOfTheMoney, solverOptions), equityIndex_(equityIndex) {
326 registerWith(equityIndex_);
327}
328
329QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess> EquityOptionSurfaceStripper::process(
330 const QuantLib::ext::shared_ptr<QuantLib::SimpleQuote>& volatilityQuote) const {
331
332 Handle<BlackVolTermStructure> vts(QuantLib::ext::make_shared<BlackConstantVol>(
333 callSurface_->referenceDate(), calendar_, Handle<Quote>(volatilityQuote), dayCounter_));
334
335 return QuantLib::ext::make_shared<BlackScholesMertonProcess>(equityIndex_->equitySpot(),
336 equityIndex_->equityDividendCurve(), equityIndex_->equityForecastCurve(), vts);
337}
338
339Real EquityOptionSurfaceStripper::forward(const Date& date) const {
340 return equityIndex_->forecastFixing(date);
341}
342
344 const Handle<PriceTermStructure>& priceCurve,
345 const Handle<YieldTermStructure>& discountCurve,
346 const QuantLib::ext::shared_ptr<OptionInterpolatorBase>& callSurface,
347 const QuantLib::ext::shared_ptr<OptionInterpolatorBase>& putSurface,
348 const Calendar& calendar,
349 const DayCounter& dayCounter,
350 Exercise::Type type,
351 bool lowerStrikeConstExtrap,
352 bool upperStrikeConstExtrap,
353 bool timeFlatExtrapolation,
354 bool preferOutOfTheMoney,
355 Solver1DOptions solverOptions)
356 : OptionSurfaceStripper(callSurface, putSurface, calendar, dayCounter, type, lowerStrikeConstExtrap,
357 upperStrikeConstExtrap, timeFlatExtrapolation, preferOutOfTheMoney, solverOptions),
358 priceCurve_(priceCurve), discountCurve_(discountCurve) {
359 registerWith(priceCurve_);
360 registerWith(discountCurve_);
361}
362
363QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess> CommodityOptionSurfaceStripper::process(
364 const QuantLib::ext::shared_ptr<QuantLib::SimpleQuote>& volatilityQuote) const {
365
366 QL_REQUIRE(!priceCurve_.empty(), "CommodityOptionSurfaceStripper: price curve is empty");
367 QL_REQUIRE(!discountCurve_.empty(), "CommodityOptionSurfaceStripper: discount curve is empty");
368
369 // Volatility term structure for the process
370 Handle<BlackVolTermStructure> vts(QuantLib::ext::make_shared<BlackConstantVol>(
371 callSurface_->referenceDate(), calendar_, Handle<Quote>(volatilityQuote), dayCounter_));
372
373 // Generate "spot" and "yield" curve for the process.
374 Handle<Quote> spot(QuantLib::ext::make_shared<DerivedPriceQuote>(priceCurve_));
375 Handle<YieldTermStructure> yield(QuantLib::ext::make_shared<PriceTermStructureAdapter>(*priceCurve_, *discountCurve_));
376 yield->enableExtrapolation();
377
378 return QuantLib::ext::make_shared<QuantLib::GeneralizedBlackScholesProcess>(spot, yield, discountCurve_, vts);
379}
380
381Real CommodityOptionSurfaceStripper::forward(const Date& date) const {
382 QL_REQUIRE(!priceCurve_.empty(), "CommodityOptionSurfaceStripper: price curve is empty");
383 return priceCurve_->price(date);
384}
385
386}
Barone-Adesi and Whaley approximation engine.
Black volatility surface modeled as variance surface.
QuantLib::Handle< QuantExt::PriceTermStructure > priceCurve_
QuantLib::Handle< QuantLib::YieldTermStructure > discountCurve_
CommodityOptionSurfaceStripper(const QuantLib::Handle< QuantExt::PriceTermStructure > &priceCurve, const QuantLib::Handle< QuantLib::YieldTermStructure > &discountCurve, const QuantLib::ext::shared_ptr< OptionInterpolatorBase > &callSurface, const QuantLib::ext::shared_ptr< OptionInterpolatorBase > &putSurface, const QuantLib::Calendar &calendar, const QuantLib::DayCounter &dayCounter, QuantLib::Exercise::Type type=QuantLib::Exercise::European, bool lowerStrikeConstExtrap=true, bool upperStrikeConstExtrap=true, bool timeFlatExtrapolation=false, bool preferOutOfTheMoney=false, Solver1DOptions solverOptions={})
QuantLib::Real forward(const QuantLib::Date &date) const override
Return the forward price at a given date.
QuantLib::ext::shared_ptr< QuantLib::GeneralizedBlackScholesProcess > process(const QuantLib::ext::shared_ptr< QuantLib::SimpleQuote > &volatilityQuote) const override
Generate the relevant Black Scholes process for the underlying.
QuantLib::Handle< QuantExt::EquityIndex2 > equityIndex_
QuantLib::Real forward(const QuantLib::Date &date) const override
Return the forward price at a given date.
QuantLib::ext::shared_ptr< QuantLib::GeneralizedBlackScholesProcess > process(const QuantLib::ext::shared_ptr< QuantLib::SimpleQuote > &volatilityQuote) const override
Generate the relevant Black Scholes process for the underlying.
EquityOptionSurfaceStripper(const QuantLib::Handle< QuantExt::EquityIndex2 > &equityIndex, const QuantLib::ext::shared_ptr< OptionInterpolatorBase > &callSurface, const QuantLib::ext::shared_ptr< OptionInterpolatorBase > &putSurface, const QuantLib::Calendar &calendar, const QuantLib::DayCounter &dayCounter, QuantLib::Exercise::Type type=QuantLib::Exercise::European, bool lowerStrikeConstExtrap=true, bool upperStrikeConstExtrap=true, bool timeFlatExtrapolation=false, bool preferOutOfTheMoney=false, Solver1DOptions solverOptions={})
PriceError(const QuantLib::VanillaOption &option, QuantLib::SimpleQuote &volatility, QuantLib::Real targetPrice)
Abstract base class for the option stripper.
const QuantLib::DayCounter & dayCounter_
virtual QuantLib::ext::shared_ptr< QuantLib::GeneralizedBlackScholesProcess > process(const QuantLib::ext::shared_ptr< QuantLib::SimpleQuote > &volatilityQuote) const =0
Generate the relevant Black Scholes process for the underlying.
QuantLib::ext::shared_ptr< QuantLib::BlackVolTermStructure > volSurface_
OptionSurfaceStripper(const QuantLib::ext::shared_ptr< OptionInterpolatorBase > &callSurface, const QuantLib::ext::shared_ptr< OptionInterpolatorBase > &putSurface, const QuantLib::Calendar &calendar, const QuantLib::DayCounter &dayCounter, QuantLib::Exercise::Type type=QuantLib::Exercise::European, bool lowerStrikeConstExtrap=true, bool upperStrikeConstExtrap=true, bool timeFlatExtrapolation=false, bool preferOutOfTheMoney=false, Solver1DOptions solverOptions={})
bool havePrices_
Set to true if we must strip volatilities from prices.
virtual QuantLib::Real forward(const QuantLib::Date &date) const =0
Return the forward price at a given date.
std::vector< QuantLib::Real > strikes(const QuantLib::Date &expiry, bool isCall) const
Retrieve the vector of strikes at a given expiry date.
QuantLib::ext::shared_ptr< QuantLib::BlackVolTermStructure > volSurface()
Return the stripped volatility structure.
QuantLib::ext::shared_ptr< OptionInterpolatorBase > putSurface_
std::function< Real(const PriceError &)> solver_
Store the function that will be called each time to solve for volatility.
Brent brent_
Solver used when implying volatility from price.
QuantLib::Real implyVol(QuantLib::Date expiry, QuantLib::Real strike, QuantLib::Option::Type type, QuantLib::ext::shared_ptr< QuantLib::PricingEngine > engine, QuantLib::SimpleQuote &volQuote) const
QuantLib::ext::shared_ptr< OptionInterpolatorBase > callSurface_
Imply equity or commodity volatility surface from put/call price surfaces.
CompiledFormula min(CompiledFormula x, const CompiledFormula &y)
CompiledFormula max(CompiledFormula x, const CompiledFormula &y)
PriceTermStructure adapter.
std::pair< QuantLib::Real, QuantLib::Real > minMax
Set the minimum and maximum search.
QuantLib::Real accuracy
The accuracy for the search.
QuantLib::Real initialGuess
The initial guess for the search.
QuantLib::Real upperBound
The upper bound of the search domain. A Null<Real>() indicates that the bound should not be set.
QuantLib::Real lowerBound
The lower bound of the search domain. A Null<Real>() indicates that the bound should not be set.
QuantLib::Real step
Set the step size for the search.
QuantLib::Size maxEvaluations
The maximum number of evaluations. Default used if not set.
vector< Real > strikes