QuantLib: a free/open-source library for quantitative finance
Fully annotated sources - version 1.32
Loading...
Searching...
No Matches
smilesectionutils.cpp
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
24namespace QuantLib {
25
27 const std::vector<Real> &moneynessGrid,
28 const Real atm,
29 const bool deleteArbitragePoints) {
30
31 if (!moneynessGrid.empty()) {
32 QL_REQUIRE(
33 section.volatilityType() == Normal || moneynessGrid[0] >= 0.0,
34 "moneyness grid should only contain non negative values ("
35 << moneynessGrid[0] << ")");
36 for (Size i = 0; i < moneynessGrid.size() - 1; i++) {
37 QL_REQUIRE(moneynessGrid[i] < moneynessGrid[i + 1],
38 "moneyness grid should contain strictly increasing "
39 "values ("
40 << moneynessGrid[i] << ","
41 << moneynessGrid[i + 1] << " at indices " << i
42 << ", " << i + 1 << ")");
43 }
44 }
45
46 if (atm == Null<Real>()) {
47 f_ = section.atmLevel();
48 QL_REQUIRE(f_ != Null<Real>(),
49 "atm level must be provided by source section or given "
50 "in the constructor");
51 } else {
52 f_ = atm;
53 }
54
55 std::vector<Real> tmp;
56
57 static const Real defaultMoney[] = { 0.0, 0.01, 0.05, 0.10, 0.25, 0.40,
58 0.50, 0.60, 0.70, 0.80, 0.90, 1.0,
59 1.25, 1.5, 1.75, 2.0, 5.0, 7.5,
60 10.0, 15.0, 20.0 };
61 static const Real defaultMoneyNormal[] = {
62 -0.20, -0.15, -0.10, -0.075, -0.05, -0.04, -0.03,
63 -0.02, -0.015, -0.01, -0.0075, -0.0050, -0.0025, 0.0,
64 0.0025, 0.0050, 0.0075, 0.01, 0.015, 0.02, 0.03,
65 0.04, 0.05, 0.075, 0.10, 0.15, 0.20
66 };
67
68 if (moneynessGrid.empty()) {
69 tmp = section.volatilityType() == Normal
70 ? std::vector<Real>(defaultMoneyNormal,
71 defaultMoneyNormal + 27)
72 : std::vector<Real>(defaultMoney, defaultMoney + 21);
73 }
74 else
75 tmp = std::vector<Real>(moneynessGrid);
76
77 Real shift = section.shift();
78
79 if (section.volatilityType() == ShiftedLognormal && tmp[0] > QL_EPSILON) {
80 m_.push_back(0.0);
81 k_.push_back(-shift);
82 }
83
84 bool minStrikeAdded = false, maxStrikeAdded = false;
85 for (Real& i : tmp) {
86 Real k = section.volatilityType() == Normal ? Real(f_ + i) : Real(i * (f_ + shift) - shift);
87 if ((section.volatilityType() == ShiftedLognormal && i <= QL_EPSILON) ||
88 (k >= section.minStrike() && k <= section.maxStrike())) {
89 if (!minStrikeAdded || !close(k, section.minStrike())) {
90 m_.push_back(i);
91 k_.push_back(k);
92 }
93 if (close(k, section.maxStrike()))
94 maxStrikeAdded = true;
95 } 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 if (k < section.minStrike() && !minStrikeAdded) {
99 m_.push_back(section.volatilityType() == Normal
100 ? Real(section.minStrike() - f_)
101 : Real((section.minStrike() + shift) / f_));
102 k_.push_back(section.minStrike());
103 minStrikeAdded = true;
104 }
105 if (k > section.maxStrike() && !maxStrikeAdded) {
106 m_.push_back(section.volatilityType() == Normal
107 ? Real(section.maxStrike() - f_)
108 : Real((section.maxStrike() + shift) / f_));
109 k_.push_back(section.maxStrike());
110 maxStrikeAdded = true;
111 }
112 }
113 }
114
115 // only known for shifted lognormal vols, otherwise we include
116 // the lower strike in the loop below
117 if(section.volatilityType() == ShiftedLognormal)
118 c_.push_back(f_ + shift);
119
120 for (Size i = (section.volatilityType() == Normal ? 0 : 1);
121 i < k_.size(); i++) {
122 c_.push_back(section.optionPrice(k_[i], Option::Call, 1.0));
123 }
124
125 Size centralIndex =
126 std::upper_bound(m_.begin(), m_.end(),
127 (section.volatilityType() == Normal ? 0.0 : 1.0) -
128 QL_EPSILON) -
129 m_.begin();
130 QL_REQUIRE(centralIndex < k_.size() - 1 && centralIndex > 1,
131 "Atm point in moneyness grid ("
132 << 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 while (!af(centralIndex, centralIndex, centralIndex + 1) &&
138 centralIndex < k_.size() - 1)
139 centralIndex++;
140
141 QL_REQUIRE(centralIndex < k_.size(),
142 "central index is at right boundary");
143
144 leftIndex_ = centralIndex;
145 rightIndex_ = centralIndex;
146
147 bool done = false;
148 while (!done) {
149
150 bool isAf = true;
151 done = true;
152
153 while (isAf && rightIndex_ < k_.size() - 1) {
154 rightIndex_++;
157 }
158 if (!isAf)
159 rightIndex_--;
160
161 isAf = true;
162 while (isAf && leftIndex_ > 1) {
163 leftIndex_--;
166 }
167 if (!isAf)
168 leftIndex_++;
169
172
173 if (deleteArbitragePoints && leftIndex_ > 1) {
174 m_.erase(m_.begin() + leftIndex_ - 1);
175 k_.erase(k_.begin() + leftIndex_ - 1);
176 c_.erase(c_.begin() + leftIndex_ - 1);
177 leftIndex_--;
178 rightIndex_--;
179 done = false;
180 }
181 if (deleteArbitragePoints && rightIndex_ < k_.size() - 1) {
182 m_.erase(m_.begin() + rightIndex_ + 1);
183 k_.erase(k_.begin() + rightIndex_ + 1);
184 c_.erase(c_.begin() + rightIndex_ + 1);
185 rightIndex_--;
186 done = false;
187 }
188 }
189
190 QL_REQUIRE(rightIndex_ > leftIndex_,
191 "arbitrage free region must at least contain two "
192 "points (only index is "
193 << leftIndex_ << ")");
194
195 }
196
197 std::pair<Real, Real> SmileSectionUtils::arbitragefreeRegion() const {
198 return {k_[leftIndex_], k_[rightIndex_]};
199 }
200
201 std::pair<Size, Size> SmileSectionUtils::arbitragefreeIndices() const {
202 return {leftIndex_, rightIndex_};
203 }
204
205 bool SmileSectionUtils::af(const Size i0, const Size i,
206 const Size i1) const {
207 if (i == 0)
208 return true;
209 Size im = i - 1 >= i0 ? i - 1 : 0;
210 Real q1 = (c_[i] - c_[im]) / (k_[i] - k_[im]);
211 if (q1 < -1.0 || q1 > 0.0)
212 return false;
213 if (i >= i1)
214 return true;
215 Real q2 = (c_[i + 1] - c_[i]) / (k_[i + 1] - k_[i]);
216 return q1 <= q2 && q2 <= 0.0;
217 }
218}
template class providing a null value for a given type.
Definition: null.hpp:76
interest rate volatility smile section
virtual Real minStrike() const =0
virtual VolatilityType volatilityType() const
virtual Real atmLevel() const =0
virtual Rate shift() const
virtual Real optionPrice(Rate strike, Option::Type type=Option::Call, Real discount=1.0) const
virtual Real maxStrike() const =0
std::pair< Size, Size > arbitragefreeIndices() const
std::pair< Real, Real > arbitragefreeRegion() const
SmileSectionUtils(const SmileSection &section, const std::vector< Real > &moneynessGrid=std::vector< Real >(), Real atm=Null< Real >(), bool deleteArbitragePoints=false)
bool af(Size i0, Size i, Size i1) const
#define QL_EPSILON
Definition: qldefines.hpp:178
QL_REAL Real
real number
Definition: types.hpp:50
std::size_t Size
size of a container
Definition: types.hpp:58
Definition: any.hpp:35
bool close(const Quantity &m1, const Quantity &m2, Size n)
Definition: quantity.cpp:163