Coverage Report

Created: 2025-08-28 06:30

/src/quantlib/ql/experimental/volatility/sabrvolsurface.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2007 Ferdinando Ametrano
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
#include <ql/experimental/volatility/sabrvolsurface.hpp>
21
#include <ql/math/interpolations/linearinterpolation.hpp>
22
#include <ql/math/interpolations/sabrinterpolation.hpp>
23
#include <ql/quotes/simplequote.hpp>
24
#include <ql/termstructures/volatility/smilesection.hpp>
25
#include <ql/utilities/dataformatters.hpp>
26
#include <utility>
27
28
namespace QuantLib {
29
30
    SabrVolSurface::SabrVolSurface(const ext::shared_ptr<InterestRateIndex>& index,
31
                                   Handle<BlackAtmVolCurve> atmCurve,
32
                                   const std::vector<Period>& optionTenors,
33
                                   std::vector<Spread> atmRateSpreads,
34
                                   std::vector<std::vector<Handle<Quote> > > volSpreads)
35
0
    : InterestRateVolSurface(index), atmCurve_(std::move(atmCurve)), optionTenors_(optionTenors),
36
0
      optionTimes_(optionTenors.size()), optionDates_(optionTenors.size()),
37
0
      atmRateSpreads_(std::move(atmRateSpreads)), volSpreads_(std::move(volSpreads)) {
38
39
0
        checkInputs();
40
41
        // Creation of reference smile sections
42
43
        // Hard coded
44
0
        isAlphaFixed_ = false;
45
0
        isBetaFixed_ = false;
46
0
        isNuFixed_ = false;
47
0
        isRhoFixed_ = false;
48
0
        vegaWeighted_ = true;
49
50
0
        sabrGuesses_.resize(optionTenors_.size());
51
52
0
        for (Size i=0; i<optionTenors_.size(); ++i) {
53
54
0
            optionDates_[i] = optionDateFromTenor(optionTenors_[i]);
55
0
            optionTimes_[i] = timeFromReference(optionDates_[i]);
56
57
            // Hard coded
58
0
            sabrGuesses_[i][0] = 0.025; // alpha
59
0
            sabrGuesses_[i][1] = 0.5;   // beta
60
0
            sabrGuesses_[i][2] = 0.3;   // rho
61
0
            sabrGuesses_[i][3] = 0.0;   // nu
62
0
        }
63
0
        registerWithMarketData();
64
0
    }
Unexecuted instantiation: QuantLib::SabrVolSurface::SabrVolSurface(boost::shared_ptr<QuantLib::InterestRateIndex> const&, QuantLib::Handle<QuantLib::BlackAtmVolCurve>, std::__1::vector<QuantLib::Period, std::__1::allocator<QuantLib::Period> > const&, std::__1::vector<double, std::__1::allocator<double> >, std::__1::vector<std::__1::vector<QuantLib::Handle<QuantLib::Quote>, std::__1::allocator<QuantLib::Handle<QuantLib::Quote> > >, std::__1::allocator<std::__1::vector<QuantLib::Handle<QuantLib::Quote>, std::__1::allocator<QuantLib::Handle<QuantLib::Quote> > > > >)
Unexecuted instantiation: QuantLib::SabrVolSurface::SabrVolSurface(boost::shared_ptr<QuantLib::InterestRateIndex> const&, QuantLib::Handle<QuantLib::BlackAtmVolCurve>, std::__1::vector<QuantLib::Period, std::__1::allocator<QuantLib::Period> > const&, std::__1::vector<double, std::__1::allocator<double> >, std::__1::vector<std::__1::vector<QuantLib::Handle<QuantLib::Quote>, std::__1::allocator<QuantLib::Handle<QuantLib::Quote> > >, std::__1::allocator<std::__1::vector<QuantLib::Handle<QuantLib::Quote>, std::__1::allocator<QuantLib::Handle<QuantLib::Quote> > > > >)
65
66
0
    std::array<Real, 4> SabrVolSurface::sabrGuesses(const Date& d) const {
67
68
        // the guesses for sabr parameters are assumed to be piecewise constant
69
0
        if (d<=optionDates_[0]) return sabrGuesses_[0];
70
0
        Size i=0;
71
0
        while (i<optionDates_.size()-1 && d<optionDates_[i])
72
0
            ++i;
73
0
        return sabrGuesses_[i];
74
0
    }
75
76
0
    void SabrVolSurface::updateSabrGuesses(const Date& d, std::array<Real, 4> newGuesses) const {
77
78
0
        Size i=0;
79
0
        while (i<optionDates_.size() && d<=optionDates_[i])
80
0
            ++i;
81
0
        sabrGuesses_[i][0] = newGuesses[0];
82
0
        sabrGuesses_[i][1] = newGuesses[1];
83
0
        sabrGuesses_[i][2] = newGuesses[2];
84
0
        sabrGuesses_[i][3] = newGuesses[3];
85
86
0
    }
87
88
0
    std::vector<Volatility> SabrVolSurface::volatilitySpreads(const Date& d) const {
89
90
0
        Size nOptionsTimes = optionTimes_.size();
91
0
        Size nAtmRateSpreads = atmRateSpreads_.size();
92
0
        std::vector<Volatility> interpolatedVols(nAtmRateSpreads);
93
94
0
        std::vector<Volatility> vols(nOptionsTimes); // the volspread at a given strike
95
0
        for (Size i=0; i<nAtmRateSpreads; ++i) {
96
0
            for (Size j=0; j<nOptionsTimes; ++j) {
97
0
                vols[j] = (**volSpreads_[j][i]).value();
98
0
            }
99
0
            LinearInterpolation interpolator(optionTimes_.begin(), optionTimes_.end(),
100
0
                                             vols.begin());
101
0
            interpolatedVols[i] = interpolator(timeFromReference(d),true);
102
0
        }
103
0
        return interpolatedVols;
104
0
    }
105
106
107
0
    void SabrVolSurface::update() {
108
0
        TermStructure::update();
109
0
        for (Size i=0; i<optionTenors_.size(); ++i) {
110
0
            optionDates_[i] = optionDateFromTenor(optionTenors_[i]);
111
0
            optionTimes_[i] = timeFromReference(optionDates_[i]);
112
0
        }
113
0
        notifyObservers();
114
115
0
    }
116
117
    ext::shared_ptr<SmileSection>
118
0
    SabrVolSurface::smileSectionImpl(Time t) const {
119
120
0
        auto n = BigInteger(t * 365.0);
121
0
        Date d = referenceDate()+n*Days;
122
        // interpolating on ref smile sections
123
0
        std::vector<Volatility> volSpreads = volatilitySpreads(d);
124
125
        // calculate sabr fit
126
0
        std::array<Real, 4> sabrParameters1 = sabrGuesses(d);
127
128
0
        ext::shared_ptr<SabrInterpolatedSmileSection> tmp(new
129
0
            SabrInterpolatedSmileSection(d,
130
0
                                         index_->fixing(d,true), atmRateSpreads_, true,
131
0
                                            atmCurve_->atmVol(d), volSpreads,
132
0
                                            sabrParameters1[0], sabrParameters1[1],
133
0
                                            sabrParameters1[2], sabrParameters1[3],
134
0
                                            isAlphaFixed_, isBetaFixed_,
135
0
                                            isNuFixed_, isRhoFixed_,
136
0
                                            vegaWeighted_/*,
137
                                            const ext::shared_ptr<EndCriteria>& endCriteria,
138
                                            const ext::shared_ptr<OptimizationMethod>& method,
139
0
                                            const DayCounter& dc*/));
140
141
        // update guess
142
143
0
        return tmp;
144
145
0
    }
146
147
0
    void SabrVolSurface::registerWithMarketData() {
148
149
0
        for (Size i=0; i<optionTenors_.size(); ++i) {
150
0
            for (Size j=0; j<atmRateSpreads_.size(); ++j) {
151
0
                registerWith(volSpreads_[i][j]);
152
0
            }
153
0
        }
154
0
    }
155
156
0
    void SabrVolSurface::checkInputs() const {
157
158
0
        Size nStrikes = atmRateSpreads_.size();
159
0
        QL_REQUIRE(nStrikes>1, "too few strikes (" << nStrikes << ")");
160
0
        for (Size i=1; i<nStrikes; ++i)
161
0
            QL_REQUIRE(atmRateSpreads_[i-1]<atmRateSpreads_[i],
162
0
                       "non increasing strike spreads: " <<
163
0
                       io::ordinal(i) << " is " << atmRateSpreads_[i-1] << ", " <<
164
0
                       io::ordinal(i+1) << " is " << atmRateSpreads_[i]);
165
0
        for (Size i=0; i<volSpreads_.size(); i++)
166
0
            QL_REQUIRE(atmRateSpreads_.size()==volSpreads_[i].size(),
167
0
                       "mismatch between number of strikes (" << atmRateSpreads_.size() <<
168
0
                       ") and number of columns (" << volSpreads_[i].size() <<
169
0
                       ") in the " << io::ordinal(i+1) << " row");
170
0
    }
171
172
0
    void SabrVolSurface::accept(AcyclicVisitor& v) {
173
0
        auto* v1 = dynamic_cast<Visitor<SabrVolSurface>*>(&v);
174
0
        if (v1 != nullptr)
175
0
            v1->visit(*this);
176
0
        else
177
0
            InterestRateVolSurface::accept(v);
178
0
    }
179
180
}