/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 | | } |