/src/quantlib/ql/pricingengines/blackdeltacalculator.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2010 Dimitri Reiswich |
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 | | <https://www.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/pricingengines/blackdeltacalculator.hpp> |
21 | | |
22 | | namespace QuantLib { |
23 | | |
24 | | QL_DEPRECATED_DISABLE_WARNING |
25 | | |
26 | | BlackDeltaCalculator::BlackDeltaCalculator( |
27 | | Option::Type ot, |
28 | | DeltaVolQuote::DeltaType dt, |
29 | | Real spot, |
30 | | DiscountFactor dDiscount, // domestic discount |
31 | | DiscountFactor fDiscount, // foreign discount |
32 | | Real stdDev): |
33 | 0 | dt_(dt), ot_(ot), |
34 | 0 | dDiscount_(dDiscount), fDiscount_(fDiscount), |
35 | 0 | stdDev_(stdDev), spot_(spot), |
36 | 0 | forward_(spot*fDiscount/dDiscount), phi_(Integer(ot)) { |
37 | |
|
38 | 0 | QL_REQUIRE(spot_>0.0, |
39 | 0 | "positive spot value required: " << |
40 | 0 | spot_ << " not allowed"); |
41 | 0 | QL_REQUIRE(dDiscount_>0.0, |
42 | 0 | "positive domestic discount factor required: " << |
43 | 0 | dDiscount_ << " not allowed"); |
44 | 0 | QL_REQUIRE(fDiscount_>0.0, |
45 | 0 | "positive foreign discount factor required: " << |
46 | 0 | fDiscount_ << " not allowed"); |
47 | 0 | QL_REQUIRE(stdDev_>=0.0, |
48 | 0 | "non-negative standard deviation required: " |
49 | 0 | << stdDev_ << " not allowed"); |
50 | | |
51 | 0 | fExpPos_ = forward_ * std::exp(0.5 * stdDev_ * stdDev_); |
52 | 0 | fExpNeg_ = forward_ * std::exp(-0.5 * stdDev_ * stdDev_); |
53 | 0 | } |
54 | | |
55 | | |
56 | 0 | Real BlackDeltaCalculator::deltaFromStrike(Real strike) const { |
57 | |
|
58 | 0 | QL_REQUIRE(strike >=0.0, |
59 | 0 | "positive strike value required: " << |
60 | 0 | strike << " not allowed"); |
61 | | |
62 | 0 | Real res = 0.0; |
63 | |
|
64 | 0 | switch(dt_){ |
65 | 0 | case DeltaVolQuote::Spot: |
66 | 0 | res = phi_ * fDiscount_ * cumD1(strike); |
67 | 0 | break; |
68 | | |
69 | 0 | case DeltaVolQuote::Fwd: |
70 | 0 | res = phi_ * cumD1(strike); |
71 | 0 | break; |
72 | | |
73 | 0 | case DeltaVolQuote::PaSpot: |
74 | 0 | res = phi_ * fDiscount_ * cumD2(strike) * strike / forward_; |
75 | 0 | break; |
76 | | |
77 | 0 | case DeltaVolQuote::PaFwd: |
78 | 0 | res = phi_ * cumD2(strike) * strike/ forward_; |
79 | 0 | break; |
80 | | |
81 | 0 | default: |
82 | 0 | QL_FAIL("invalid delta type"); |
83 | 0 | } |
84 | 0 | return res; |
85 | 0 | } |
86 | | |
87 | 0 | Real BlackDeltaCalculator::strikeFromDelta(Real delta) const { |
88 | 0 | return(strikeFromDelta(delta, dt_)); |
89 | 0 | } |
90 | | |
91 | | Real BlackDeltaCalculator::strikeFromDelta(Real delta, |
92 | | DeltaVolQuote::DeltaType dt) |
93 | 0 | const{ |
94 | 0 | Real res = 0.0; |
95 | 0 | Real arg = 0.0; |
96 | 0 | InverseCumulativeNormal invNorm; |
97 | |
|
98 | 0 | QL_REQUIRE(delta*phi_ >= 0.0, "Option type and delta are incoherent."); |
99 | | |
100 | 0 | switch (dt) { |
101 | 0 | case DeltaVolQuote::Spot: |
102 | 0 | QL_REQUIRE(std::fabs(delta)<=fDiscount_, |
103 | 0 | "Spot delta out of range."); |
104 | | |
105 | 0 | arg = -phi_ * invNorm(phi_ * delta / fDiscount_) * stdDev_ + 0.5 * stdDev_ * stdDev_; |
106 | 0 | res = forward_ * std::exp(arg); |
107 | 0 | break; |
108 | | |
109 | 0 | case DeltaVolQuote::Fwd: |
110 | 0 | QL_REQUIRE(std::fabs(delta)<=1.0, |
111 | 0 | "Forward delta out of range."); |
112 | | |
113 | 0 | arg = -phi_ * invNorm(phi_ * delta) * stdDev_ + 0.5 * stdDev_ * stdDev_; |
114 | 0 | res = forward_ * std::exp(arg); |
115 | 0 | break; |
116 | | |
117 | 0 | case DeltaVolQuote::PaSpot: |
118 | 0 | case DeltaVolQuote::PaFwd: { |
119 | | // This has to be solved numerically. One of the |
120 | | // problems is that the premium adjusted call delta is |
121 | | // not monotonic in strike, such that two solutions |
122 | | // might occur. The one right to the max of the delta is |
123 | | // considered to be the correct strike. Some proper |
124 | | // interval bounds for the strike need to be chosen, the |
125 | | // numerics can otherwise be very unreliable and |
126 | | // unstable. I've chosen Brent over Newton, since the |
127 | | // interval can be specified explicitly and we can not |
128 | | // run into the area on the left of the maximum. The |
129 | | // put delta doesn't have this property and can be |
130 | | // solved without any problems, but also numerically. |
131 | 0 | auto f = [&](Real strike) { |
132 | 0 | return deltaFromStrike(strike) - delta; |
133 | 0 | }; |
134 | |
|
135 | 0 | Brent solver; |
136 | 0 | solver.setMaxEvaluations(1000); |
137 | 0 | Real accuracy = 1.0e-10; |
138 | |
|
139 | 0 | Real rightLimit = 0.0; |
140 | 0 | Real leftLimit = 0.0; |
141 | | |
142 | | // Strike of not premium adjusted is always to the right of premium adjusted |
143 | 0 | if (dt == DeltaVolQuote::PaSpot) { |
144 | 0 | rightLimit = strikeFromDelta(delta, DeltaVolQuote::Spot); |
145 | 0 | } else { |
146 | 0 | rightLimit = strikeFromDelta(delta, DeltaVolQuote::Fwd); |
147 | 0 | } |
148 | |
|
149 | 0 | if (phi_ < 0) { // if put |
150 | 0 | res = solver.solve(f, accuracy, rightLimit, 0.0, spot_*100.0); |
151 | 0 | break; |
152 | 0 | } else { |
153 | | |
154 | | // find out the left limit which is the strike |
155 | | // corresponding to the value where premium adjusted |
156 | | // deltas have their maximum |
157 | 0 | auto g = [&](Real strike) { |
158 | 0 | return cumD2(strike) * stdDev_ - nD2(strike); |
159 | 0 | }; |
160 | |
|
161 | 0 | leftLimit = solver.solve(g, accuracy, rightLimit * 0.5, |
162 | 0 | 0.0, rightLimit); |
163 | |
|
164 | 0 | Real guess = leftLimit+(rightLimit - leftLimit) * 0.5; |
165 | |
|
166 | 0 | res = solver.solve(f, accuracy, guess, leftLimit, rightLimit); |
167 | 0 | } // end if phi<0 else |
168 | | |
169 | 0 | break; |
170 | 0 | } |
171 | | |
172 | 0 | default: |
173 | 0 | QL_FAIL("invalid delta type"); |
174 | 0 | } |
175 | | |
176 | 0 | return res; |
177 | 0 | } |
178 | | |
179 | 0 | Real BlackDeltaCalculator::atmStrike(DeltaVolQuote::AtmType atmT) const { |
180 | |
|
181 | 0 | Real res = 0.0; |
182 | |
|
183 | 0 | switch(atmT) { |
184 | 0 | case DeltaVolQuote::AtmSpot: |
185 | 0 | res = spot_; |
186 | 0 | break; |
187 | | |
188 | 0 | case DeltaVolQuote::AtmDeltaNeutral: |
189 | 0 | if(dt_ == DeltaVolQuote::Spot || dt_ == DeltaVolQuote::Fwd){ |
190 | 0 | res = fExpPos_; |
191 | 0 | } else { |
192 | 0 | res = fExpNeg_; |
193 | 0 | } |
194 | 0 | break; |
195 | | |
196 | 0 | case DeltaVolQuote::AtmFwd: |
197 | 0 | res = forward_; |
198 | 0 | break; |
199 | | |
200 | 0 | case DeltaVolQuote::AtmGammaMax: case DeltaVolQuote::AtmVegaMax: |
201 | 0 | res = fExpPos_; |
202 | 0 | break; |
203 | | |
204 | 0 | case DeltaVolQuote::AtmPutCall50: |
205 | 0 | QL_REQUIRE(dt_ == DeltaVolQuote::Fwd, |
206 | 0 | "|PutDelta|=CallDelta=0.50 only possible for forward delta."); |
207 | 0 | res = fExpPos_; |
208 | 0 | break; |
209 | | |
210 | 0 | default: |
211 | 0 | QL_FAIL("invalid atm type"); |
212 | 0 | } |
213 | | |
214 | 0 | return res; |
215 | 0 | } |
216 | | |
217 | | |
218 | 0 | Real BlackDeltaCalculator::cumD1(Real strike) const { |
219 | |
|
220 | 0 | Real d1_=0.0; |
221 | 0 | Real cum_d1_pos_ = 1.0; // N(d1) |
222 | 0 | Real cum_d1_neg_ = 0.0; // N(-d1) |
223 | |
|
224 | 0 | CumulativeNormalDistribution f; |
225 | |
|
226 | 0 | if (stdDev_>=QL_EPSILON) { |
227 | 0 | if(strike>0) { |
228 | 0 | d1_ = std::log(forward_/strike)/stdDev_ + 0.5*stdDev_; |
229 | 0 | return f(phi_*d1_); |
230 | 0 | } |
231 | 0 | } else { |
232 | 0 | if (forward_<strike) { |
233 | 0 | cum_d1_pos_ = 0.0; |
234 | 0 | cum_d1_neg_ = 1.0; |
235 | 0 | } else if(forward_==strike){ |
236 | 0 | d1_ = 0.5*stdDev_; |
237 | 0 | return f(phi_*d1_); |
238 | 0 | } |
239 | 0 | } |
240 | | |
241 | 0 | if (phi_>0) { // if Call |
242 | 0 | return cum_d1_pos_; |
243 | 0 | } else { |
244 | 0 | return cum_d1_neg_; |
245 | 0 | } |
246 | 0 | } |
247 | | |
248 | | |
249 | 0 | Real BlackDeltaCalculator::nD1(Real strike) const { |
250 | |
|
251 | 0 | Real d1_=0.0; |
252 | 0 | Real n_d1_ = 0.0; // n(d1) |
253 | |
|
254 | 0 | if (stdDev_>=QL_EPSILON){ |
255 | 0 | if(strike>0){ |
256 | 0 | d1_ = std::log(forward_/strike)/stdDev_ + 0.5*stdDev_; |
257 | 0 | CumulativeNormalDistribution f; |
258 | 0 | n_d1_ = f.derivative(d1_); |
259 | 0 | } |
260 | 0 | } |
261 | |
|
262 | 0 | return n_d1_; |
263 | 0 | } |
264 | | |
265 | | |
266 | 0 | Real BlackDeltaCalculator::cumD2(Real strike) const { |
267 | |
|
268 | 0 | Real d2_=0.0; |
269 | 0 | Real cum_d2_pos_= 1.0; // N(d2) |
270 | 0 | Real cum_d2_neg_= 0.0; // N(-d2) |
271 | |
|
272 | 0 | CumulativeNormalDistribution f; |
273 | |
|
274 | 0 | if (stdDev_>=QL_EPSILON){ |
275 | |
|
276 | 0 | if(strike>0){ |
277 | 0 | d2_ = std::log(forward_/strike)/stdDev_ - 0.5*stdDev_; |
278 | 0 | return f(phi_*d2_); |
279 | 0 | } |
280 | |
|
281 | 0 | } else { |
282 | |
|
283 | 0 | if (forward_<strike) { |
284 | 0 | cum_d2_pos_= 0.0; |
285 | 0 | cum_d2_neg_= 1.0; |
286 | 0 | } else if (forward_==strike) { |
287 | 0 | d2_ = -0.5*stdDev_; |
288 | 0 | return(f(phi_*d2_)); |
289 | 0 | } |
290 | |
|
291 | 0 | } |
292 | | |
293 | 0 | if (phi_>0) { // if Call |
294 | 0 | return cum_d2_pos_; |
295 | 0 | } else { |
296 | 0 | return cum_d2_neg_; |
297 | 0 | } |
298 | 0 | } |
299 | | |
300 | | |
301 | 0 | Real BlackDeltaCalculator::nD2(Real strike) const { |
302 | |
|
303 | 0 | Real d2_=0.0; |
304 | 0 | Real n_d2_= 0.0; // n(d2) |
305 | |
|
306 | 0 | if (stdDev_>=QL_EPSILON){ |
307 | 0 | if(strike>0){ |
308 | 0 | d2_ = std::log(forward_/strike)/stdDev_ - 0.5*stdDev_; |
309 | 0 | CumulativeNormalDistribution f; |
310 | 0 | n_d2_ = f.derivative(d2_); |
311 | 0 | } |
312 | 0 | } |
313 | |
|
314 | 0 | return n_d2_; |
315 | 0 | } |
316 | | |
317 | | |
318 | 0 | void BlackDeltaCalculator::setDeltaType(DeltaVolQuote::DeltaType dt){ |
319 | 0 | dt_=dt; |
320 | 0 | } |
321 | | |
322 | 0 | void BlackDeltaCalculator::setOptionType(Option::Type ot){ |
323 | 0 | ot_=ot; |
324 | 0 | phi_=Integer(ot_); |
325 | 0 | } |
326 | | |
327 | | |
328 | | // helper classes |
329 | | |
330 | | BlackDeltaPremiumAdjustedSolverClass::BlackDeltaPremiumAdjustedSolverClass( |
331 | | Option::Type ot, |
332 | | DeltaVolQuote::DeltaType dt, |
333 | | Real spot, |
334 | | DiscountFactor dDiscount, // domestic discount |
335 | | DiscountFactor fDiscount, // foreign discount |
336 | | Real stdDev, |
337 | | Real delta): |
338 | 0 | bdc_(ot,dt,spot,dDiscount,fDiscount,stdDev), delta_(delta) {} |
339 | | |
340 | | |
341 | 0 | Real BlackDeltaPremiumAdjustedSolverClass::operator()(Real strike) const { |
342 | 0 | return bdc_.deltaFromStrike(strike)-delta_; |
343 | 0 | } |
344 | | |
345 | | |
346 | | BlackDeltaPremiumAdjustedMaxStrikeClass::BlackDeltaPremiumAdjustedMaxStrikeClass( |
347 | | Option::Type ot, |
348 | | DeltaVolQuote::DeltaType dt, |
349 | | Real spot, |
350 | | DiscountFactor dDiscount, // domestic discount |
351 | | DiscountFactor fDiscount, // foreign discount |
352 | | Real stdDev): |
353 | 0 | bdc_(ot,dt,spot,dDiscount,fDiscount,stdDev), stdDev_(stdDev) {} |
354 | | |
355 | 0 | Real BlackDeltaPremiumAdjustedMaxStrikeClass::operator()(Real strike) const { |
356 | 0 | return bdc_.cumD2(strike)*stdDev_ - bdc_.nD2(strike); |
357 | 0 | } |
358 | | |
359 | | QL_DEPRECATED_ENABLE_WARNING |
360 | | } |