Coverage Report

Created: 2025-10-14 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}