/src/quantlib/ql/termstructures/volatility/smilesectionutils.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) 2013, 2015, 2018 Peter Caspers |
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/termstructures/volatility/smilesectionutils.hpp> |
21 | | #include <ql/math/comparison.hpp> |
22 | | #include <algorithm> |
23 | | |
24 | | namespace QuantLib { |
25 | | |
26 | | SmileSectionUtils::SmileSectionUtils(const SmileSection §ion, |
27 | | const std::vector<Real> &moneynessGrid, |
28 | | const Real atm, |
29 | 0 | const bool deleteArbitragePoints) { |
30 | |
|
31 | 0 | if (!moneynessGrid.empty()) { |
32 | 0 | QL_REQUIRE( |
33 | 0 | section.volatilityType() == Normal || moneynessGrid[0] >= 0.0, |
34 | 0 | "moneyness grid should only contain non negative values (" |
35 | 0 | << moneynessGrid[0] << ")"); |
36 | 0 | for (Size i = 0; i < moneynessGrid.size() - 1; i++) { |
37 | 0 | QL_REQUIRE(moneynessGrid[i] < moneynessGrid[i + 1], |
38 | 0 | "moneyness grid should contain strictly increasing " |
39 | 0 | "values (" |
40 | 0 | << moneynessGrid[i] << "," |
41 | 0 | << moneynessGrid[i + 1] << " at indices " << i |
42 | 0 | << ", " << i + 1 << ")"); |
43 | 0 | } |
44 | 0 | } |
45 | | |
46 | 0 | if (atm == Null<Real>()) { |
47 | 0 | f_ = section.atmLevel(); |
48 | 0 | QL_REQUIRE(f_ != Null<Real>(), |
49 | 0 | "atm level must be provided by source section or given " |
50 | 0 | "in the constructor"); |
51 | 0 | } else { |
52 | 0 | f_ = atm; |
53 | 0 | } |
54 | | |
55 | 0 | std::vector<Real> tmp; |
56 | |
|
57 | 0 | static const Real defaultMoney[] = { 0.0, 0.01, 0.05, 0.10, 0.25, 0.40, |
58 | 0 | 0.50, 0.60, 0.70, 0.80, 0.90, 1.0, |
59 | 0 | 1.25, 1.5, 1.75, 2.0, 5.0, 7.5, |
60 | 0 | 10.0, 15.0, 20.0 }; |
61 | 0 | static const Real defaultMoneyNormal[] = { |
62 | 0 | -0.20, -0.15, -0.10, -0.075, -0.05, -0.04, -0.03, |
63 | 0 | -0.02, -0.015, -0.01, -0.0075, -0.0050, -0.0025, 0.0, |
64 | 0 | 0.0025, 0.0050, 0.0075, 0.01, 0.015, 0.02, 0.03, |
65 | 0 | 0.04, 0.05, 0.075, 0.10, 0.15, 0.20 |
66 | 0 | }; |
67 | |
|
68 | 0 | if (moneynessGrid.empty()) { |
69 | 0 | tmp = section.volatilityType() == Normal |
70 | 0 | ? std::vector<Real>(defaultMoneyNormal, |
71 | 0 | defaultMoneyNormal + 27) |
72 | 0 | : std::vector<Real>(defaultMoney, defaultMoney + 21); |
73 | 0 | } |
74 | 0 | else |
75 | 0 | tmp = std::vector<Real>(moneynessGrid); |
76 | |
|
77 | 0 | Real shift = section.shift(); |
78 | |
|
79 | 0 | if (section.volatilityType() == ShiftedLognormal && tmp[0] > QL_EPSILON) { |
80 | 0 | m_.push_back(0.0); |
81 | 0 | k_.push_back(-shift); |
82 | 0 | } |
83 | |
|
84 | 0 | bool minStrikeAdded = false, maxStrikeAdded = false; |
85 | 0 | for (Real& i : tmp) { |
86 | 0 | Real k = section.volatilityType() == Normal ? Real(f_ + i) : Real(i * (f_ + shift) - shift); |
87 | 0 | if ((section.volatilityType() == ShiftedLognormal && i <= QL_EPSILON) || |
88 | 0 | (k >= section.minStrike() && k <= section.maxStrike())) { |
89 | 0 | if (!minStrikeAdded || !close(k, section.minStrike())) { |
90 | 0 | m_.push_back(i); |
91 | 0 | k_.push_back(k); |
92 | 0 | } |
93 | 0 | if (close(k, section.maxStrike())) |
94 | 0 | maxStrikeAdded = true; |
95 | 0 | } else { // if the section provides a limited strike range |
96 | | // we put the respective endpoint in our grid |
97 | | // in order to not loose too much information |
98 | 0 | if (k < section.minStrike() && !minStrikeAdded) { |
99 | 0 | m_.push_back(section.volatilityType() == Normal |
100 | 0 | ? Real(section.minStrike() - f_) |
101 | 0 | : Real((section.minStrike() + shift) / f_)); |
102 | 0 | k_.push_back(section.minStrike()); |
103 | 0 | minStrikeAdded = true; |
104 | 0 | } |
105 | 0 | if (k > section.maxStrike() && !maxStrikeAdded) { |
106 | 0 | m_.push_back(section.volatilityType() == Normal |
107 | 0 | ? Real(section.maxStrike() - f_) |
108 | 0 | : Real((section.maxStrike() + shift) / f_)); |
109 | 0 | k_.push_back(section.maxStrike()); |
110 | 0 | maxStrikeAdded = true; |
111 | 0 | } |
112 | 0 | } |
113 | 0 | } |
114 | | |
115 | | // only known for shifted lognormal vols, otherwise we include |
116 | | // the lower strike in the loop below |
117 | 0 | if(section.volatilityType() == ShiftedLognormal) |
118 | 0 | c_.push_back(f_ + shift); |
119 | |
|
120 | 0 | for (Size i = (section.volatilityType() == Normal ? 0 : 1); |
121 | 0 | i < k_.size(); i++) { |
122 | 0 | c_.push_back(section.optionPrice(k_[i], Option::Call, 1.0)); |
123 | 0 | } |
124 | |
|
125 | 0 | Size centralIndex = |
126 | 0 | std::upper_bound(m_.begin(), m_.end(), |
127 | 0 | (section.volatilityType() == Normal ? 0.0 : 1.0) - |
128 | 0 | QL_EPSILON) - |
129 | 0 | m_.begin(); |
130 | 0 | QL_REQUIRE(centralIndex < k_.size() - 1 && centralIndex > 1, |
131 | 0 | "Atm point in moneyness grid (" |
132 | 0 | << centralIndex << ") too close to boundary."); |
133 | | |
134 | | // shift central index to the right if necessary |
135 | | // (sometimes even the atm point lies in an arbitrageable area) |
136 | | |
137 | 0 | while (!af(centralIndex, centralIndex, centralIndex + 1) && |
138 | 0 | centralIndex < k_.size() - 1) |
139 | 0 | centralIndex++; |
140 | |
|
141 | 0 | QL_REQUIRE(centralIndex < k_.size(), |
142 | 0 | "central index is at right boundary"); |
143 | | |
144 | 0 | leftIndex_ = centralIndex; |
145 | 0 | rightIndex_ = centralIndex; |
146 | |
|
147 | 0 | bool done = false; |
148 | 0 | while (!done) { |
149 | |
|
150 | 0 | bool isAf = true; |
151 | 0 | done = true; |
152 | |
|
153 | 0 | while (isAf && rightIndex_ < k_.size() - 1) { |
154 | 0 | rightIndex_++; |
155 | 0 | isAf = af(leftIndex_, rightIndex_, rightIndex_) && |
156 | 0 | af(leftIndex_, rightIndex_ - 1, rightIndex_); |
157 | 0 | } |
158 | 0 | if (!isAf) |
159 | 0 | rightIndex_--; |
160 | |
|
161 | 0 | isAf = true; |
162 | 0 | while (isAf && leftIndex_ > 1) { |
163 | 0 | leftIndex_--; |
164 | 0 | isAf = af(leftIndex_, leftIndex_, rightIndex_) && |
165 | 0 | af(leftIndex_, leftIndex_ + 1, rightIndex_); |
166 | 0 | } |
167 | 0 | if (!isAf) |
168 | 0 | leftIndex_++; |
169 | |
|
170 | 0 | if (rightIndex_ < leftIndex_) |
171 | 0 | rightIndex_ = leftIndex_; |
172 | |
|
173 | 0 | if (deleteArbitragePoints && leftIndex_ > 1) { |
174 | 0 | m_.erase(m_.begin() + leftIndex_ - 1); |
175 | 0 | k_.erase(k_.begin() + leftIndex_ - 1); |
176 | 0 | c_.erase(c_.begin() + leftIndex_ - 1); |
177 | 0 | leftIndex_--; |
178 | 0 | rightIndex_--; |
179 | 0 | done = false; |
180 | 0 | } |
181 | 0 | if (deleteArbitragePoints && rightIndex_ < k_.size() - 1) { |
182 | 0 | m_.erase(m_.begin() + rightIndex_ + 1); |
183 | 0 | k_.erase(k_.begin() + rightIndex_ + 1); |
184 | 0 | c_.erase(c_.begin() + rightIndex_ + 1); |
185 | 0 | rightIndex_--; |
186 | 0 | done = false; |
187 | 0 | } |
188 | 0 | } |
189 | |
|
190 | 0 | QL_REQUIRE(rightIndex_ > leftIndex_, |
191 | 0 | "arbitrage free region must at least contain two " |
192 | 0 | "points (only index is " |
193 | 0 | << leftIndex_ << ")"); |
194 | |
|
195 | 0 | } |
196 | | |
197 | 0 | std::pair<Real, Real> SmileSectionUtils::arbitragefreeRegion() const { |
198 | 0 | return {k_[leftIndex_], k_[rightIndex_]}; |
199 | 0 | } |
200 | | |
201 | 0 | std::pair<Size, Size> SmileSectionUtils::arbitragefreeIndices() const { |
202 | 0 | return {leftIndex_, rightIndex_}; |
203 | 0 | } |
204 | | |
205 | | bool SmileSectionUtils::af(const Size i0, const Size i, |
206 | 0 | const Size i1) const { |
207 | 0 | if (i == 0) |
208 | 0 | return true; |
209 | 0 | Size im = i - 1 >= i0 ? i - 1 : 0; |
210 | 0 | Real q1 = (c_[i] - c_[im]) / (k_[i] - k_[im]); |
211 | 0 | if (q1 < -1.0 || q1 > 0.0) |
212 | 0 | return false; |
213 | 0 | if (i >= i1) |
214 | 0 | return true; |
215 | 0 | Real q2 = (c_[i + 1] - c_[i]) / (k_[i + 1] - k_[i]); |
216 | 0 | return q1 <= q2 && q2 <= 0.0; |
217 | 0 | } |
218 | | } |