Coverage Report

Created: 2026-06-23 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/termstructures/volatility/equityfx/piecewiseblackvariancesurface.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2026 Richard Amaya
5
 Copyright (C) 2026 Yassine Idyiahia
6
7
 This file is part of QuantLib, a free-software/open-source library
8
 for financial quantitative analysts and developers - http://quantlib.org/
9
10
 QuantLib is free software: you can redistribute it and/or modify it
11
 under the terms of the QuantLib license.  You should have received a
12
 copy of the license along with this program; if not, please email
13
 <quantlib-dev@lists.sf.net>. The license is also available online at
14
 <https://www.quantlib.org/license.shtml>.
15
16
 This program is distributed in the hope that it will be useful, but WITHOUT
17
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 FOR A PARTICULAR PURPOSE.  See the license for more details.
19
*/
20
21
#include <ql/termstructures/volatility/equityfx/piecewiseblackvariancesurface.hpp>
22
#include <ql/math/comparison.hpp>
23
#include <ql/math/interpolations/linearinterpolation.hpp>
24
#include <ql/termstructures/volatility/interpolatedsmilesection.hpp>
25
#include <ql/utilities/null.hpp>
26
#include <algorithm>
27
#include <cmath>
28
#include <utility>
29
30
namespace QuantLib {
31
32
    PiecewiseBlackVarianceSurface::PiecewiseBlackVarianceSurface(
33
            const Date& referenceDate,
34
            const std::vector<Date>& dates,
35
            std::vector<ext::shared_ptr<SmileSection>> smileSections,
36
            DayCounter dayCounter)
37
0
    : BlackVarianceTermStructure(referenceDate),
38
0
      dayCounter_(std::move(dayCounter)),
39
0
      smileSections_(std::move(smileSections)) {
40
41
0
        QL_REQUIRE(!dates.empty(),
42
0
                   "at least one date is required");
43
0
        QL_REQUIRE(dates.size() == smileSections_.size(),
44
0
                   "mismatch between " << dates.size() << " dates and "
45
0
                   << smileSections_.size() << " smile sections");
46
47
0
        maxDate_ = dates.back();
48
0
        times_.resize(dates.size());
49
50
0
        times_[0] = timeFromReference(dates[0]);
51
0
        QL_REQUIRE(times_[0] > 0.0,
52
0
                   "first date (" << dates[0]
53
0
                   << ") must be after reference date ("
54
0
                   << referenceDate << ")");
55
56
0
        for (Size i = 1; i < dates.size(); ++i) {
57
0
            times_[i] = timeFromReference(dates[i]);
58
0
            QL_REQUIRE(times_[i] > times_[i-1],
59
0
                       "dates must be sorted and unique, but date "
60
0
                       << dates[i] << " (t=" << times_[i]
61
0
                       << ") is not after date " << dates[i-1]
62
0
                       << " (t=" << times_[i-1] << ")");
63
0
        }
64
65
0
        for (Size i = 0; i < smileSections_.size(); ++i) {
66
0
            QL_REQUIRE(smileSections_[i],
67
0
                       "null smile section at index " << i);
68
0
            registerWith(smileSections_[i]);
69
0
        }
70
0
    }
Unexecuted instantiation: QuantLib::PiecewiseBlackVarianceSurface::PiecewiseBlackVarianceSurface(QuantLib::Date const&, std::__1::vector<QuantLib::Date, std::__1::allocator<QuantLib::Date> > const&, std::__1::vector<boost::shared_ptr<QuantLib::SmileSection>, std::__1::allocator<boost::shared_ptr<QuantLib::SmileSection> > >, QuantLib::DayCounter)
Unexecuted instantiation: QuantLib::PiecewiseBlackVarianceSurface::PiecewiseBlackVarianceSurface(QuantLib::Date const&, std::__1::vector<QuantLib::Date, std::__1::allocator<QuantLib::Date> > const&, std::__1::vector<boost::shared_ptr<QuantLib::SmileSection>, std::__1::allocator<boost::shared_ptr<QuantLib::SmileSection> > >, QuantLib::DayCounter)
71
72
    PiecewiseBlackVarianceSurface::PiecewiseBlackVarianceSurface(
73
            const Date& referenceDate,
74
            const Date& date,
75
            ext::shared_ptr<SmileSection> smileSection,
76
            DayCounter dayCounter)
77
0
    : PiecewiseBlackVarianceSurface(
78
0
          referenceDate,
79
0
          std::vector<Date>{date},
80
0
          std::vector<ext::shared_ptr<SmileSection>>{std::move(smileSection)},
81
0
          std::move(dayCounter)) {}
Unexecuted instantiation: QuantLib::PiecewiseBlackVarianceSurface::PiecewiseBlackVarianceSurface(QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::SmileSection>, QuantLib::DayCounter)
Unexecuted instantiation: QuantLib::PiecewiseBlackVarianceSurface::PiecewiseBlackVarianceSurface(QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::SmileSection>, QuantLib::DayCounter)
82
83
    Real PiecewiseBlackVarianceSurface::sectionVariance(
84
0
            Size i, Real strike) const {
85
0
        const auto& s = smileSections_[i];
86
0
        QL_REQUIRE(allowsExtrapolation() ||
87
0
                   (strike >= s->minStrike() && strike <= s->maxStrike()),
88
0
                   "strike (" << strike
89
0
                   << ") is outside the range of smile section "
90
0
                   << i << " [" << s->minStrike() << ", "
91
0
                   << s->maxStrike() << "]");
92
0
        return s->variance(strike);
93
0
    }
94
95
    Real PiecewiseBlackVarianceSurface::blackVarianceImpl(
96
0
            Time t, Real strike) const {
97
98
0
        if (t == 0.0)
99
0
            return 0.0;
100
101
0
        if (t <= times_.front()) {
102
            // linear interpolation from (0, 0) to first tenor
103
0
            Real var1 = sectionVariance(0, strike);
104
0
            return var1 * t / times_.front();
105
0
        }
106
107
0
        if (t >= times_.back()) {
108
            // flat vol extrapolation beyond last tenor
109
0
            Real varN = sectionVariance(smileSections_.size() - 1, strike);
110
0
            return varN * t / times_.back();
111
0
        }
112
113
        // find enclosing interval
114
0
        auto it = std::upper_bound(times_.begin(), times_.end(), t);
115
0
        Size hi = std::distance(times_.begin(), it);
116
0
        Size lo = hi - 1;
117
118
0
        Real varLo = sectionVariance(lo, strike);
119
0
        Real varHi = sectionVariance(hi, strike);
120
0
        Real alpha = (t - times_[lo]) / (times_[hi] - times_[lo]);
121
122
0
        return varLo + (varHi - varLo) * alpha;
123
0
    }
124
125
    ext::shared_ptr<PiecewiseBlackVarianceSurface>
126
    PiecewiseBlackVarianceSurface::makeFromGrid(
127
            const Date& referenceDate,
128
            const std::vector<Date>& dates,
129
            const std::vector<Real>& strikes,
130
            const Matrix& blackVols,
131
0
            const DayCounter& dc) {
132
133
0
        QL_REQUIRE(blackVols.rows() == strikes.size(),
134
0
                   "mismatch between " << strikes.size() << " strikes and "
135
0
                   << blackVols.rows() << " matrix rows");
136
0
        QL_REQUIRE(blackVols.columns() == dates.size(),
137
0
                   "mismatch between " << dates.size() << " dates and "
138
0
                   << blackVols.columns() << " matrix columns");
139
140
0
        std::vector<ext::shared_ptr<SmileSection>> sections(dates.size());
141
142
0
        for (Size j = 0; j < dates.size(); ++j) {
143
0
            std::vector<Real> stdDevs(strikes.size());
144
0
            Time t = dc.yearFraction(referenceDate, dates[j]);
145
0
            QL_REQUIRE(t > 0.0,
146
0
                       "date " << dates[j]
147
0
                       << " must be after reference date "
148
0
                       << referenceDate);
149
0
            Real sqrtT = std::sqrt(t);
150
0
            for (Size i = 0; i < strikes.size(); ++i)
151
0
                stdDevs[i] = blackVols[i][j] * sqrtT;
152
153
0
            sections[j] = ext::make_shared<InterpolatedSmileSection<Linear>>(
154
0
                dates[j], strikes, stdDevs, Null<Real>(),
155
0
                dc, Linear(), referenceDate);
156
0
        }
157
158
0
        return ext::make_shared<PiecewiseBlackVarianceSurface>(
159
0
            referenceDate, dates, std::move(sections), dc);
160
0
    }
161
162
    ext::shared_ptr<SmileSection>
163
0
    PiecewiseBlackVarianceSurface::smileSectionImpl(Time t) const {
164
0
        auto it = std::lower_bound(times_.begin(), times_.end(), t);
165
0
        if (it != times_.end() && close_enough(t, *it))
166
0
            return smileSections_[std::distance(times_.begin(), it)];
167
0
        return BlackVarianceTermStructure::smileSectionImpl(t);
168
0
    }
169
170
0
    void PiecewiseBlackVarianceSurface::accept(AcyclicVisitor& v) {
171
0
        auto* v1 = dynamic_cast<Visitor<PiecewiseBlackVarianceSurface>*>(&v);
172
0
        if (v1 != nullptr)
173
0
            v1->visit(*this);
174
0
        else
175
0
            BlackVarianceTermStructure::accept(v);
176
0
    }
177
178
}