/src/quantlib/ql/termstructures/volatility/equityfx/andreasenhugevolatilityinterpl.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2017, 2018 Klaus Spanderen |
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 | | <https://www.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 | | #include <ql/exercise.hpp> |
21 | | #include <ql/instruments/vanillaoption.hpp> |
22 | | #include <ql/math/array.hpp> |
23 | | #include <ql/math/comparison.hpp> |
24 | | #include <ql/math/interpolations/backwardflatinterpolation.hpp> |
25 | | #include <ql/math/interpolations/cubicinterpolation.hpp> |
26 | | #include <ql/methods/finitedifferences/meshers/concentrating1dmesher.hpp> |
27 | | #include <ql/methods/finitedifferences/meshers/fdmmeshercomposite.hpp> |
28 | | #include <ql/methods/finitedifferences/operators/fdmlinearoplayout.hpp> |
29 | | #include <ql/methods/finitedifferences/operators/firstderivativeop.hpp> |
30 | | #include <ql/methods/finitedifferences/operators/secondderivativeop.hpp> |
31 | | #include <ql/methods/finitedifferences/tridiagonaloperator.hpp> |
32 | | #include <ql/pricingengines/blackcalculator.hpp> |
33 | | #include <ql/termstructures/volatility/equityfx/andreasenhugevolatilityinterpl.hpp> |
34 | | #include <ql/termstructures/yieldtermstructure.hpp> |
35 | | #include <ql/timegrid.hpp> |
36 | | #include <ql/utilities/null.hpp> |
37 | | #include <cmath> |
38 | | #include <limits> |
39 | | #include <utility> |
40 | | |
41 | | namespace QuantLib { |
42 | | |
43 | | namespace { |
44 | | |
45 | | struct close_enough_to { |
46 | | Real y; |
47 | | Size n; |
48 | 0 | explicit close_enough_to(Real y, Size n=42) : y(y), n(n) {} |
49 | 0 | bool operator()(Real x) const { return close_enough(x, y, n); } |
50 | | }; |
51 | | |
52 | | } |
53 | | |
54 | | class AndreasenHugeCostFunction : public CostFunction { |
55 | | public: |
56 | | AndreasenHugeCostFunction( |
57 | | Array marketNPVs, |
58 | | Array marketVegas, |
59 | | Array lnMarketStrikes, |
60 | | Array previousNPVs, |
61 | | const ext::shared_ptr<FdmMesherComposite>& mesher, |
62 | | Time dT, |
63 | | AndreasenHugeVolatilityInterpl::InterpolationType interpolationType) |
64 | 0 | : marketNPVs_(std::move(marketNPVs)), marketVegas_(std::move(marketVegas)), |
65 | 0 | lnMarketStrikes_(std::move(lnMarketStrikes)), previousNPVs_(std::move(previousNPVs)), |
66 | 0 | mesher_(mesher), nGridPoints_(mesher->layout()->size()), dT_(dT), |
67 | 0 | interpolationType_((lnMarketStrikes_.size() > 1) ? |
68 | 0 | interpolationType : |
69 | 0 | AndreasenHugeVolatilityInterpl::PiecewiseConstant), |
70 | 0 | dxMap_(FirstDerivativeOp(0, mesher_)), dxxMap_(SecondDerivativeOp(0, mesher_)), |
71 | 0 | d2CdK2_(dxMap_.mult(Array(mesher->layout()->size(), -1.0)).add(dxxMap_)), |
72 | 0 | mapT_(0, mesher_) {} |
73 | | |
74 | 0 | Array d2CdK2(const Array& c) const { |
75 | 0 | return d2CdK2_.apply(c); |
76 | 0 | } |
77 | | |
78 | 0 | Array solveFor(Time dT, const Array& sig, const Array& b) const { |
79 | |
|
80 | 0 | Array x(lnMarketStrikes_.size()); |
81 | 0 | Interpolation sigInterpl; |
82 | |
|
83 | 0 | switch (interpolationType_) { |
84 | 0 | case AndreasenHugeVolatilityInterpl::CubicSpline: |
85 | 0 | sigInterpl = CubicNaturalSpline( |
86 | 0 | lnMarketStrikes_.begin(), lnMarketStrikes_.end(), |
87 | 0 | sig.begin()); |
88 | 0 | break; |
89 | 0 | case AndreasenHugeVolatilityInterpl::Linear: |
90 | 0 | sigInterpl = LinearInterpolation( |
91 | 0 | lnMarketStrikes_.begin(), lnMarketStrikes_.end(), |
92 | 0 | sig.begin()); |
93 | 0 | break; |
94 | 0 | case AndreasenHugeVolatilityInterpl::PiecewiseConstant: |
95 | 0 | for (Size i=0; i < x.size()-1; ++i) |
96 | 0 | x[i] = 0.5*(lnMarketStrikes_[i] + lnMarketStrikes_[i+1]); |
97 | 0 | x.back() = lnMarketStrikes_.back(); |
98 | |
|
99 | 0 | sigInterpl = BackwardFlatInterpolation( |
100 | 0 | x.begin(), x.end(), sig.begin()); |
101 | 0 | break; |
102 | 0 | default: |
103 | 0 | QL_FAIL("unknown interpolation type"); |
104 | 0 | } |
105 | | |
106 | 0 | Array z(mesher_->layout()->size()); |
107 | 0 | for (const auto& iter : *mesher_->layout()) { |
108 | 0 | const Size i = iter.index(); |
109 | 0 | const Real lnStrike = mesher_->location(iter, 0); |
110 | |
|
111 | 0 | const Real vol = sigInterpl( |
112 | 0 | std::min(std::max(lnStrike, lnMarketStrikes_.front()), |
113 | 0 | lnMarketStrikes_.back()), true); |
114 | |
|
115 | 0 | z[i] = 0.5*vol*vol; |
116 | 0 | } |
117 | |
|
118 | 0 | mapT_.axpyb(z, dxMap_, dxxMap_.mult(-z), Array()); |
119 | 0 | return mapT_.mult(Array(z.size(), dT)).solve_splitting(b, 1.0); |
120 | |
|
121 | 0 | } |
122 | | |
123 | 0 | Array apply(const Array& c) const { |
124 | 0 | return -mapT_.apply(c); |
125 | 0 | } |
126 | | |
127 | 0 | Array values(const Array& sig) const override { |
128 | 0 | Array newNPVs = solveFor(dT_, sig, previousNPVs_); |
129 | |
|
130 | 0 | const std::vector<Real>& gridPoints = |
131 | 0 | mesher_->getFdm1dMeshers().front()->locations(); |
132 | |
|
133 | 0 | const MonotonicCubicNaturalSpline interpl( |
134 | 0 | gridPoints.begin(), gridPoints.end(), newNPVs.begin()); |
135 | |
|
136 | 0 | Array retVal(lnMarketStrikes_.size()); |
137 | 0 | for (Size i=0; i < retVal.size(); ++i) { |
138 | 0 | const Real strike = lnMarketStrikes_[i]; |
139 | 0 | retVal[i] = interpl(strike) - marketNPVs_[i]; |
140 | 0 | } |
141 | 0 | return retVal; |
142 | 0 | } |
143 | | |
144 | 0 | Array vegaCalibrationError(const Array& sig) const { |
145 | 0 | return values(sig)/marketVegas_; |
146 | 0 | } |
147 | | |
148 | 0 | Array initialValues() const { |
149 | 0 | return Array(lnMarketStrikes_.size(), 0.25); |
150 | 0 | } |
151 | | |
152 | | |
153 | | private: |
154 | | const Array marketNPVs_, marketVegas_; |
155 | | const Array lnMarketStrikes_, previousNPVs_; |
156 | | const ext::shared_ptr<FdmMesherComposite> mesher_; |
157 | | const Size nGridPoints_; |
158 | | const Time dT_; |
159 | | const AndreasenHugeVolatilityInterpl::InterpolationType |
160 | | interpolationType_; |
161 | | |
162 | | const FirstDerivativeOp dxMap_; |
163 | | const TripleBandLinearOp dxxMap_; |
164 | | const TripleBandLinearOp d2CdK2_; |
165 | | mutable TripleBandLinearOp mapT_; |
166 | | }; |
167 | | |
168 | | class CombinedCostFunction : public CostFunction { |
169 | | public: |
170 | | CombinedCostFunction(ext::shared_ptr<AndreasenHugeCostFunction> putCostFct, |
171 | | ext::shared_ptr<AndreasenHugeCostFunction> callCostFct) |
172 | 0 | : putCostFct_(std::move(putCostFct)), callCostFct_(std::move(callCostFct)) {} |
173 | | |
174 | 0 | Array values(const Array& sig) const override { |
175 | 0 | if ((putCostFct_ != nullptr) && (callCostFct_ != nullptr)) { |
176 | 0 | const Array pv = putCostFct_->values(sig); |
177 | 0 | const Array cv = callCostFct_->values(sig); |
178 | |
|
179 | 0 | Array retVal(pv.size() + cv.size()); |
180 | 0 | std::copy(pv.begin(), pv.end(), retVal.begin()); |
181 | 0 | std::copy(cv.begin(), cv.end(), retVal.begin() + cv.size()); |
182 | |
|
183 | 0 | return retVal; |
184 | 0 | } else if (putCostFct_ != nullptr) |
185 | 0 | return putCostFct_->values(sig); |
186 | 0 | else if (callCostFct_ != nullptr) |
187 | 0 | return callCostFct_->values(sig); |
188 | 0 | else |
189 | 0 | QL_FAIL("internal error: cost function not set"); |
190 | 0 | } |
191 | | |
192 | 0 | Array initialValues() const { |
193 | 0 | if ((putCostFct_ != nullptr) && (callCostFct_ != nullptr)) |
194 | 0 | return 0.5*( putCostFct_->initialValues() |
195 | 0 | + callCostFct_->initialValues()); |
196 | 0 | else if (putCostFct_ != nullptr) |
197 | 0 | return putCostFct_->initialValues(); |
198 | 0 | else if (callCostFct_ != nullptr) |
199 | 0 | return callCostFct_->initialValues(); |
200 | 0 | else |
201 | 0 | QL_FAIL("internal error: cost function not set"); |
202 | 0 | } |
203 | | |
204 | | private: |
205 | | const ext::shared_ptr<AndreasenHugeCostFunction> putCostFct_; |
206 | | const ext::shared_ptr<AndreasenHugeCostFunction> callCostFct_; |
207 | | }; |
208 | | |
209 | | |
210 | | AndreasenHugeVolatilityInterpl::AndreasenHugeVolatilityInterpl( |
211 | | const CalibrationSet& calibrationSet, |
212 | | Handle<Quote> spot, |
213 | | Handle<YieldTermStructure> rTS, |
214 | | Handle<YieldTermStructure> qTS, |
215 | | InterpolationType interplationType, |
216 | | CalibrationType calibrationType, |
217 | | Size nGridPoints, |
218 | | Real _minStrike, |
219 | | Real _maxStrike, |
220 | | ext::shared_ptr<OptimizationMethod> optimizationMethod, |
221 | | const EndCriteria& endCriteria) |
222 | 0 | : spot_(std::move(spot)), rTS_(std::move(rTS)), qTS_(std::move(qTS)), |
223 | 0 | interpolationType_(interplationType), calibrationType_(calibrationType), |
224 | 0 | nGridPoints_(nGridPoints), minStrike_(_minStrike), maxStrike_(_maxStrike), |
225 | 0 | optimizationMethod_(std::move(optimizationMethod)), endCriteria_(endCriteria) { |
226 | 0 | QL_REQUIRE(nGridPoints > 2 && !calibrationSet.empty(), "undefined grid or calibration set"); |
227 | | |
228 | 0 | std::set<Real> strikes; |
229 | 0 | std::set<Date> expiries; |
230 | |
|
231 | 0 | calibrationSet_.reserve(calibrationSet.size()); |
232 | 0 | for (const auto& i : calibrationSet) { |
233 | |
|
234 | 0 | const ext::shared_ptr<Exercise> exercise = i.first->exercise(); |
235 | |
|
236 | 0 | QL_REQUIRE(exercise->type() == Exercise::European, |
237 | 0 | "European option required"); |
238 | | |
239 | 0 | const Date expiry = exercise->lastDate(); |
240 | 0 | expiries.insert(expiry); |
241 | |
|
242 | 0 | const ext::shared_ptr<PlainVanillaPayoff> payoff = |
243 | 0 | ext::dynamic_pointer_cast<PlainVanillaPayoff>(i.first->payoff()); |
244 | |
|
245 | 0 | QL_REQUIRE(payoff, "plain vanilla payoff required"); |
246 | | |
247 | 0 | const Real strike = payoff->strike(); |
248 | 0 | strikes.insert(strike); |
249 | |
|
250 | 0 | calibrationSet_.emplace_back(ext::make_shared<VanillaOption>(payoff, exercise), i.second); |
251 | |
|
252 | 0 | registerWith(i.second); |
253 | 0 | } |
254 | | |
255 | 0 | strikes_.assign(strikes.begin(), strikes.end()); |
256 | 0 | expiries_.assign(expiries.begin(), expiries.end()); |
257 | |
|
258 | 0 | dT_.resize(expiries_.size()); |
259 | 0 | expiryTimes_.resize(expiries_.size()); |
260 | |
|
261 | 0 | calibrationMatrix_ = std::vector< std::vector<Size> >( |
262 | 0 | expiries.size(), std::vector<Size>(strikes.size(), Null<Size>())); |
263 | |
|
264 | 0 | for (Size i=0; i < calibrationSet.size(); ++i) { |
265 | 0 | const Date expiry = |
266 | 0 | calibrationSet[i].first->exercise()->lastDate(); |
267 | |
|
268 | 0 | const Size l = std::distance(expiries.begin(), expiries.lower_bound(expiry)); |
269 | |
|
270 | 0 | const Real strike = |
271 | 0 | ext::dynamic_pointer_cast<PlainVanillaPayoff>( |
272 | 0 | calibrationSet[i].first->payoff())->strike(); |
273 | |
|
274 | 0 | const Size k = std::distance(strikes_.begin(), |
275 | 0 | std::find_if(strikes_.begin(), strikes_.end(), |
276 | 0 | close_enough_to(strike))); |
277 | |
|
278 | 0 | calibrationMatrix_[l][k] = i; |
279 | 0 | } |
280 | |
|
281 | 0 | registerWith(spot_); |
282 | 0 | registerWith(rTS_); |
283 | 0 | registerWith(qTS_); |
284 | 0 | } Unexecuted instantiation: QuantLib::AndreasenHugeVolatilityInterpl::AndreasenHugeVolatilityInterpl(std::__1::vector<std::__1::pair<boost::shared_ptr<QuantLib::VanillaOption>, boost::shared_ptr<QuantLib::Quote> >, std::__1::allocator<std::__1::pair<boost::shared_ptr<QuantLib::VanillaOption>, boost::shared_ptr<QuantLib::Quote> > > > const&, QuantLib::Handle<QuantLib::Quote>, QuantLib::Handle<QuantLib::YieldTermStructure>, QuantLib::Handle<QuantLib::YieldTermStructure>, QuantLib::AndreasenHugeVolatilityInterpl::InterpolationType, QuantLib::AndreasenHugeVolatilityInterpl::CalibrationType, unsigned long, double, double, boost::shared_ptr<QuantLib::OptimizationMethod>, QuantLib::EndCriteria const&) Unexecuted instantiation: QuantLib::AndreasenHugeVolatilityInterpl::AndreasenHugeVolatilityInterpl(std::__1::vector<std::__1::pair<boost::shared_ptr<QuantLib::VanillaOption>, boost::shared_ptr<QuantLib::Quote> >, std::__1::allocator<std::__1::pair<boost::shared_ptr<QuantLib::VanillaOption>, boost::shared_ptr<QuantLib::Quote> > > > const&, QuantLib::Handle<QuantLib::Quote>, QuantLib::Handle<QuantLib::YieldTermStructure>, QuantLib::Handle<QuantLib::YieldTermStructure>, QuantLib::AndreasenHugeVolatilityInterpl::InterpolationType, QuantLib::AndreasenHugeVolatilityInterpl::CalibrationType, unsigned long, double, double, boost::shared_ptr<QuantLib::OptimizationMethod>, QuantLib::EndCriteria const&) |
285 | | |
286 | | ext::shared_ptr<AndreasenHugeCostFunction> |
287 | | AndreasenHugeVolatilityInterpl::buildCostFunction( |
288 | | Size iExpiry, Option::Type optionType, |
289 | 0 | const Array& previousNPVs) const { |
290 | |
|
291 | 0 | if (calibrationType_ != CallPut |
292 | 0 | && ( (calibrationType_ == Call && optionType ==Option::Put) |
293 | 0 | || (calibrationType_ == Put && optionType ==Option::Call))) |
294 | 0 | return ext::shared_ptr<AndreasenHugeCostFunction>(); |
295 | | |
296 | 0 | const Time expiryTime = expiryTimes_[iExpiry]; |
297 | |
|
298 | 0 | const DiscountFactor discount = rTS_->discount(expiryTime); |
299 | 0 | const Real fwd = spot_->value()*qTS_->discount(expiryTime)/discount; |
300 | |
|
301 | 0 | Size null = Null<Size>(); |
302 | 0 | const Size nOptions = std::count_if( |
303 | 0 | calibrationMatrix_[iExpiry].begin(), |
304 | 0 | calibrationMatrix_[iExpiry].end(), |
305 | 0 | [=](Size n){ return n != null; }); |
306 | |
|
307 | 0 | Array lnMarketStrikes(nOptions), |
308 | 0 | marketNPVs(nOptions), marketVegas(nOptions); |
309 | | |
310 | | // calculate undiscounted market prices |
311 | 0 | for (Size j=0, k=0; j < strikes_.size(); ++j) { |
312 | 0 | const Size idx = calibrationMatrix_[iExpiry][j]; |
313 | |
|
314 | 0 | if (idx != null) { |
315 | |
|
316 | 0 | const Volatility vol = calibrationSet_[idx].second->value(); |
317 | 0 | const Real stdDev = vol*std::sqrt(expiryTime); |
318 | |
|
319 | 0 | const BlackCalculator calculator( |
320 | 0 | optionType, strikes_[j], fwd, stdDev, discount); |
321 | |
|
322 | 0 | const Real npv = calculator.value(); |
323 | 0 | const Real vega = calculator.vega(expiryTime); |
324 | |
|
325 | 0 | marketNPVs[k] = npv/(discount*fwd); |
326 | 0 | marketVegas[k] = vega/(discount*fwd); |
327 | 0 | lnMarketStrikes[k++] = std::log(strikes_[j]/fwd); |
328 | 0 | } |
329 | 0 | } |
330 | |
|
331 | 0 | return ext::make_shared<AndreasenHugeCostFunction>( |
332 | 0 | marketNPVs, |
333 | 0 | marketVegas, |
334 | 0 | lnMarketStrikes, |
335 | 0 | previousNPVs, |
336 | 0 | mesher_, |
337 | 0 | dT_[iExpiry], |
338 | 0 | interpolationType_); |
339 | 0 | } |
340 | | |
341 | | |
342 | 0 | void AndreasenHugeVolatilityInterpl::performCalculations() const { |
343 | 0 | QL_REQUIRE(maxStrike() > minStrike(), |
344 | 0 | "max strike must be greater than min strike"); |
345 | | |
346 | 0 | const DayCounter dc = rTS_->dayCounter(); |
347 | 0 | for (Size i=0; i < expiryTimes_.size(); ++i) { |
348 | 0 | expiryTimes_[i] = |
349 | 0 | dc.yearFraction(rTS_->referenceDate(), expiries_[i]); |
350 | 0 | dT_[i] = expiryTimes_[i] - ( (i==0)? 0.0 : expiryTimes_[i-1]); |
351 | 0 | } |
352 | |
|
353 | 0 | mesher_ = |
354 | 0 | ext::make_shared<FdmMesherComposite>( |
355 | 0 | ext::make_shared<Concentrating1dMesher>( |
356 | 0 | std::log(minStrike()/spot_->value()), |
357 | 0 | std::log(maxStrike()/spot_->value()), |
358 | 0 | nGridPoints_, |
359 | 0 | std::pair<Real, Real>(0.0, 0.025))); |
360 | |
|
361 | 0 | gridPoints_ = mesher_->locations(0); |
362 | 0 | gridInFwd_ = Exp(gridPoints_)*spot_->value(); |
363 | |
|
364 | 0 | localVolCache_.clear(); |
365 | 0 | calibrationResults_.clear(); |
366 | |
|
367 | 0 | avgError_ = 0.0; |
368 | 0 | minError_ = std::numeric_limits<Real>::max(); |
369 | 0 | maxError_ = 0.0; |
370 | |
|
371 | 0 | calibrationResults_.reserve(expiries_.size()); |
372 | |
|
373 | 0 | Array npvPuts(nGridPoints_); |
374 | 0 | Array npvCalls(nGridPoints_); |
375 | |
|
376 | 0 | for (Size i=0; i < nGridPoints_; ++i) { |
377 | 0 | const Real strike = std::exp(gridPoints_[i]); |
378 | 0 | npvPuts[i] = PlainVanillaPayoff(Option::Put, strike)(1.0); |
379 | 0 | npvCalls[i]= PlainVanillaPayoff(Option::Call, strike)(1.0); |
380 | 0 | } |
381 | |
|
382 | 0 | for (Size i=0; i < expiries_.size(); ++i) { |
383 | 0 | const ext::shared_ptr<AndreasenHugeCostFunction> putCostFct = |
384 | 0 | buildCostFunction(i, Option::Put, npvPuts); |
385 | 0 | const ext::shared_ptr<AndreasenHugeCostFunction> callCostFct = |
386 | 0 | buildCostFunction(i, Option::Call, npvCalls); |
387 | |
|
388 | 0 | CombinedCostFunction costFunction(putCostFct, callCostFct); |
389 | |
|
390 | 0 | PositiveConstraint positiveConstraint; |
391 | 0 | Problem problem(costFunction, |
392 | 0 | positiveConstraint, costFunction.initialValues()); |
393 | |
|
394 | 0 | optimizationMethod_->minimize(problem, endCriteria_); |
395 | |
|
396 | 0 | const Array& sig = problem.currentValue(); |
397 | |
|
398 | 0 | const SingleStepCalibrationResult calibrationResult = { |
399 | 0 | npvPuts, npvCalls, sig, |
400 | 0 | (calibrationType_ == Call)? callCostFct : putCostFct |
401 | 0 | }; |
402 | |
|
403 | 0 | calibrationResults_.push_back(calibrationResult); |
404 | |
|
405 | 0 | Array vegaDiffs(sig.size()); |
406 | 0 | switch (calibrationType_) { |
407 | 0 | case CallPut: { |
408 | 0 | const Array vegaPutDiffs = |
409 | 0 | putCostFct->vegaCalibrationError(sig); |
410 | 0 | const Array vegaCallDiffs = |
411 | 0 | callCostFct->vegaCalibrationError(sig); |
412 | |
|
413 | 0 | const Real fwd = spot_->value()* |
414 | 0 | qTS_->discount(expiryTimes_[i])/rTS_->discount(expiryTimes_[i]); |
415 | |
|
416 | 0 | for (Size j=0; j < vegaDiffs.size(); ++j) |
417 | 0 | vegaDiffs[j] = std::fabs( |
418 | 0 | (fwd > gridInFwd_[j])? vegaPutDiffs[j] : vegaCallDiffs[j]); |
419 | 0 | } |
420 | 0 | break; |
421 | 0 | case Put: |
422 | 0 | vegaDiffs = Abs(putCostFct->vegaCalibrationError(sig)); |
423 | 0 | break; |
424 | 0 | case Call: |
425 | 0 | vegaDiffs = Abs(callCostFct->vegaCalibrationError(sig)); |
426 | 0 | break; |
427 | 0 | default: |
428 | 0 | QL_FAIL("unknown calibration type"); |
429 | 0 | } |
430 | | |
431 | 0 | avgError_ += |
432 | 0 | std::accumulate(vegaDiffs.begin(), vegaDiffs.end(), Real(0.0)); |
433 | 0 | minError_ = std::min(minError_, |
434 | 0 | *std::min_element(vegaDiffs.begin(), vegaDiffs.end())); |
435 | 0 | maxError_ = std::max(maxError_, |
436 | 0 | *std::max_element(vegaDiffs.begin(), vegaDiffs.end())); |
437 | |
|
438 | 0 | if (putCostFct != nullptr) |
439 | 0 | npvPuts = putCostFct->solveFor(dT_[i], sig, npvPuts); |
440 | 0 | if (callCostFct != nullptr) |
441 | 0 | npvCalls= callCostFct->solveFor(dT_[i], sig, npvCalls); |
442 | 0 | } |
443 | | |
444 | 0 | avgError_ /= calibrationSet_.size(); |
445 | 0 | } |
446 | | |
447 | 0 | Date AndreasenHugeVolatilityInterpl::maxDate() const { |
448 | 0 | return expiries_.back(); |
449 | 0 | } |
450 | | |
451 | 0 | Real AndreasenHugeVolatilityInterpl::minStrike() const { |
452 | 0 | return (minStrike_ == Null<Real>()) |
453 | 0 | ? 1/8.0*strikes_.front() : minStrike_; |
454 | 0 | } |
455 | | |
456 | 0 | Real AndreasenHugeVolatilityInterpl::maxStrike() const { |
457 | 0 | return (maxStrike_ == Null<Real>()) |
458 | 0 | ? 8.0*strikes_.back() : maxStrike_; |
459 | 0 | } |
460 | | |
461 | 0 | Real AndreasenHugeVolatilityInterpl::fwd(Time t) const { |
462 | 0 | return spot_->value()*qTS_->discount(t)/rTS_->discount(t); |
463 | 0 | } |
464 | | |
465 | | const Handle<YieldTermStructure>& |
466 | 0 | AndreasenHugeVolatilityInterpl::riskFreeRate() const { |
467 | 0 | return rTS_; |
468 | 0 | } |
469 | | |
470 | | std::tuple<Real, Real, Real> |
471 | 0 | AndreasenHugeVolatilityInterpl::calibrationError() const { |
472 | 0 | calculate(); |
473 | |
|
474 | 0 | return std::make_tuple(minError_, maxError_, avgError_); |
475 | 0 | } |
476 | | |
477 | 0 | Size AndreasenHugeVolatilityInterpl::getExerciseTimeIdx(Time t) const { |
478 | 0 | return std::min<Size>(expiryTimes_.size()-1, |
479 | 0 | std::distance(expiryTimes_.begin(), |
480 | 0 | std::upper_bound( |
481 | 0 | expiryTimes_.begin(), expiryTimes_.end(), t))); |
482 | 0 | } |
483 | | |
484 | | Real AndreasenHugeVolatilityInterpl::getCacheValue( |
485 | 0 | Real strike, const TimeValueCacheType::const_iterator& f) const { |
486 | |
|
487 | 0 | const Real fwd = std::get<0>(f->second); |
488 | 0 | const Real k = std::log(strike / fwd); |
489 | |
|
490 | 0 | const Real s = std::max(gridPoints_[1], |
491 | 0 | std::min(*(gridPoints_.end()-2), k)); |
492 | |
|
493 | 0 | return (*(std::get<2>(f->second)))(s); |
494 | 0 | } |
495 | | |
496 | | Array AndreasenHugeVolatilityInterpl::getPriceSlice( |
497 | 0 | Time t, Option::Type optionType) const { |
498 | |
|
499 | 0 | const Size iu = getExerciseTimeIdx(t); |
500 | |
|
501 | 0 | return calibrationResults_[iu].costFunction->solveFor( |
502 | 0 | (iu == 0) ? t : t-expiryTimes_[iu-1], |
503 | 0 | calibrationResults_[iu].sigmas, |
504 | 0 | (optionType == Option::Call)? calibrationResults_[iu].callNPVs |
505 | 0 | : calibrationResults_[iu].putNPVs); |
506 | 0 | } |
507 | | |
508 | | Real AndreasenHugeVolatilityInterpl::optionPrice( |
509 | 0 | Time t, Real strike, Option::Type optionType) const { |
510 | |
|
511 | 0 | auto f = priceCache_.find(t); |
512 | |
|
513 | 0 | const DiscountFactor df = rTS_->discount(t); |
514 | |
|
515 | 0 | if (f != priceCache_.end()) { |
516 | 0 | const Real fwd = std::get<0>(f->second); |
517 | |
|
518 | 0 | Real price = getCacheValue(strike, f); |
519 | |
|
520 | 0 | if (optionType == Option::Put |
521 | 0 | && (calibrationType_ == Call || calibrationType_ == CallPut)) |
522 | 0 | price = price + strike/fwd - 1.0; |
523 | 0 | else if (optionType == Option::Call && calibrationType_ == Put) |
524 | 0 | price = 1.0 - strike/fwd + price; |
525 | |
|
526 | 0 | return price*df*fwd; |
527 | 0 | } |
528 | | |
529 | 0 | calculate(); |
530 | | |
531 | |
|
532 | 0 | ext::shared_ptr<Array> prices( |
533 | 0 | ext::make_shared<Array>(gridPoints_)); |
534 | |
|
535 | 0 | switch (calibrationType_) { |
536 | 0 | case Put: |
537 | 0 | (*prices) = getPriceSlice(t, Option::Put); |
538 | 0 | break; |
539 | 0 | case Call: |
540 | 0 | case CallPut: |
541 | 0 | (*prices) = getPriceSlice(t, Option::Call); |
542 | 0 | break; |
543 | 0 | default: |
544 | 0 | QL_FAIL("unknown calibration type"); |
545 | 0 | } |
546 | | |
547 | 0 | Real fwd = spot_->value()*qTS_->discount(t)/df; |
548 | |
|
549 | 0 | priceCache_[t] = std::make_tuple( |
550 | 0 | fwd, prices, |
551 | 0 | ext::make_shared<CubicNaturalSpline>( |
552 | 0 | gridPoints_.begin()+1, gridPoints_.end()-1, |
553 | 0 | prices->begin()+1)); |
554 | |
|
555 | 0 | return this->optionPrice(t, strike, optionType); |
556 | 0 | } |
557 | | |
558 | | Array AndreasenHugeVolatilityInterpl::getLocalVolSlice( |
559 | 0 | Time t, Option::Type optionType) const { |
560 | |
|
561 | 0 | const Size iu = getExerciseTimeIdx(t); |
562 | |
|
563 | 0 | const Array& previousNPVs = |
564 | 0 | (optionType == Option::Call)? calibrationResults_[iu].callNPVs |
565 | 0 | : calibrationResults_[iu].putNPVs; |
566 | |
|
567 | 0 | const ext::shared_ptr<AndreasenHugeCostFunction> costFunction |
568 | 0 | = calibrationResults_[iu].costFunction; |
569 | |
|
570 | 0 | const Time dt = (iu == 0) ? t : t-expiryTimes_[iu-1]; |
571 | 0 | const Array& sig = calibrationResults_[iu].sigmas; |
572 | |
|
573 | 0 | const Array cAtJ = costFunction->solveFor(dt, sig, previousNPVs); |
574 | |
|
575 | 0 | const Array dCdT = |
576 | 0 | costFunction->solveFor(dt, sig, |
577 | 0 | costFunction->apply( |
578 | 0 | costFunction->solveFor(dt, sig, previousNPVs))); |
579 | |
|
580 | 0 | const Array d2CdK2 = costFunction->d2CdK2(cAtJ); |
581 | |
|
582 | 0 | Array localVol = Sqrt(2*dCdT/d2CdK2); |
583 | |
|
584 | 0 | for (Size i=1; i < localVol.size()-1; ++i) |
585 | 0 | if (!std::isfinite(localVol[i]) || localVol[i] < 0.0) |
586 | 0 | localVol[i] = 0.25; |
587 | |
|
588 | 0 | return localVol; |
589 | 0 | } |
590 | | |
591 | | Volatility AndreasenHugeVolatilityInterpl::localVol(Time t, Real strike) |
592 | 0 | const { |
593 | 0 | auto f = localVolCache_.find(t); |
594 | |
|
595 | 0 | if (f != localVolCache_.end()) |
596 | 0 | return getCacheValue(strike, f); |
597 | | |
598 | 0 | calculate(); |
599 | |
|
600 | 0 | ext::shared_ptr<Array> localVol( |
601 | 0 | ext::make_shared<Array>(gridPoints_.size())); |
602 | |
|
603 | 0 | switch (calibrationType_) { |
604 | 0 | case CallPut: { |
605 | 0 | const Array putLocalVol = getLocalVolSlice(t, Option::Put); |
606 | 0 | const Array callLocalVol = getLocalVolSlice(t, Option::Call); |
607 | |
|
608 | 0 | for (Size i=0, n=localVol->size(); i < n; ++i) |
609 | 0 | (*localVol)[i] = |
610 | 0 | (gridPoints_[i] > 0.0)? callLocalVol[i] : putLocalVol[i]; |
611 | 0 | } |
612 | 0 | break; |
613 | 0 | case Put: |
614 | 0 | (*localVol) = getLocalVolSlice(t, Option::Put); |
615 | 0 | break; |
616 | 0 | case Call: |
617 | 0 | (*localVol) = getLocalVolSlice(t, Option::Call); |
618 | 0 | break; |
619 | 0 | default: |
620 | 0 | QL_FAIL("unknown calibration type"); |
621 | 0 | } |
622 | | |
623 | 0 | Real fwd = spot_->value()*qTS_->discount(t)/rTS_->discount(t); |
624 | |
|
625 | 0 | localVolCache_[t] = std::make_tuple( |
626 | 0 | fwd, localVol, |
627 | 0 | ext::make_shared<LinearInterpolation>( |
628 | 0 | gridPoints_.begin()+1, gridPoints_.end()-1, |
629 | 0 | localVol->begin()+1)); |
630 | |
|
631 | 0 | return this->localVol(t, strike); |
632 | 0 | } |
633 | | } |