/src/quantlib/ql/pricingengines/barrier/analyticbinarybarrierengine.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) 2014 Thema Consulting SA |
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/exercise.hpp> |
21 | | #include <ql/instruments/vanillaoption.hpp> |
22 | | #include <ql/math/distributions/normaldistribution.hpp> |
23 | | #include <ql/pricingengines/barrier/analyticbinarybarrierengine.hpp> |
24 | | #include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp> |
25 | | #include <utility> |
26 | | |
27 | | namespace QuantLib { |
28 | | |
29 | | // calc helper object |
30 | | class AnalyticBinaryBarrierEngine_helper |
31 | | { |
32 | | |
33 | | public: |
34 | | AnalyticBinaryBarrierEngine_helper( |
35 | | const ext::shared_ptr<GeneralizedBlackScholesProcess>& process, |
36 | | const ext::shared_ptr<StrikedTypePayoff> &payoff, |
37 | | const ext::shared_ptr<AmericanExercise> &exercise, |
38 | | const BarrierOption::arguments &arguments): |
39 | 0 | process_(process), |
40 | 0 | payoff_(payoff), |
41 | 0 | exercise_(exercise), |
42 | 0 | arguments_(arguments) |
43 | 0 | { |
44 | 0 | } |
45 | | |
46 | | Real payoffAtExpiry(Real spot, Real variance, Real discount); |
47 | | private: |
48 | | const ext::shared_ptr<GeneralizedBlackScholesProcess>& process_; |
49 | | const ext::shared_ptr<StrikedTypePayoff> &payoff_; |
50 | | const ext::shared_ptr<AmericanExercise> &exercise_; |
51 | | const BarrierOption::arguments &arguments_; |
52 | | }; |
53 | | |
54 | | |
55 | | AnalyticBinaryBarrierEngine::AnalyticBinaryBarrierEngine( |
56 | | ext::shared_ptr<GeneralizedBlackScholesProcess> process) |
57 | 0 | : process_(std::move(process)) { |
58 | 0 | registerWith(process_); |
59 | 0 | } |
60 | | |
61 | 0 | void AnalyticBinaryBarrierEngine::calculate() const { |
62 | |
|
63 | 0 | ext::shared_ptr<AmericanExercise> ex = |
64 | 0 | ext::dynamic_pointer_cast<AmericanExercise>(arguments_.exercise); |
65 | 0 | QL_REQUIRE(ex, "non-American exercise given"); |
66 | 0 | QL_REQUIRE(ex->payoffAtExpiry(), "payoff must be at expiry"); |
67 | 0 | QL_REQUIRE(ex->dates()[0] <= |
68 | 0 | process_->blackVolatility()->referenceDate(), |
69 | 0 | "American option with window exercise not handled yet"); |
70 | | |
71 | 0 | ext::shared_ptr<StrikedTypePayoff> payoff = |
72 | 0 | ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff); |
73 | 0 | QL_REQUIRE(payoff, "non-striked payoff given"); |
74 | | |
75 | 0 | Real spot = process_->stateVariable()->value(); |
76 | 0 | QL_REQUIRE(spot > 0.0, "negative or null underlying given"); |
77 | | |
78 | 0 | Real variance = |
79 | 0 | process_->blackVolatility()->blackVariance(ex->lastDate(), |
80 | 0 | payoff->strike()); |
81 | 0 | Real barrier = arguments_.barrier; |
82 | 0 | QL_REQUIRE(barrier>0.0, |
83 | 0 | "positive barrier value required"); |
84 | 0 | Barrier::Type barrierType = arguments_.barrierType; |
85 | | |
86 | | // KO degenerate cases |
87 | 0 | if ( (barrierType == Barrier::DownOut && spot <= barrier) || |
88 | 0 | (barrierType == Barrier::UpOut && spot >= barrier)) |
89 | 0 | { |
90 | | // knocked out, no value |
91 | 0 | results_.value = 0; |
92 | 0 | results_.delta = 0; |
93 | 0 | results_.gamma = 0; |
94 | 0 | results_.vega = 0; |
95 | 0 | results_.theta = 0; |
96 | 0 | results_.rho = 0; |
97 | 0 | results_.dividendRho = 0; |
98 | 0 | return; |
99 | 0 | } |
100 | | |
101 | | // KI degenerate cases |
102 | 0 | if ((barrierType == Barrier::DownIn && spot <= barrier) || |
103 | 0 | (barrierType == Barrier::UpIn && spot >= barrier)) { |
104 | | // knocked in - is a digital european |
105 | 0 | ext::shared_ptr<Exercise> exercise(new EuropeanExercise( |
106 | 0 | arguments_.exercise->lastDate())); |
107 | |
|
108 | 0 | ext::shared_ptr<PricingEngine> engine( |
109 | 0 | new AnalyticEuropeanEngine(process_)); |
110 | |
|
111 | 0 | VanillaOption opt(payoff, exercise); |
112 | 0 | opt.setPricingEngine(engine); |
113 | 0 | results_.value = opt.NPV(); |
114 | 0 | results_.delta = opt.delta(); |
115 | 0 | results_.gamma = opt.gamma(); |
116 | 0 | results_.vega = opt.vega(); |
117 | 0 | results_.theta = opt.theta(); |
118 | 0 | results_.rho = opt.rho(); |
119 | 0 | results_.dividendRho = opt.dividendRho(); |
120 | 0 | return; |
121 | 0 | } |
122 | | |
123 | 0 | Rate riskFreeDiscount = |
124 | 0 | process_->riskFreeRate()->discount(ex->lastDate()); |
125 | |
|
126 | 0 | AnalyticBinaryBarrierEngine_helper helper(process_, |
127 | 0 | payoff, ex, arguments_); |
128 | 0 | results_.value = helper.payoffAtExpiry(spot, variance, riskFreeDiscount); |
129 | 0 | } |
130 | | |
131 | | Real AnalyticBinaryBarrierEngine_helper::payoffAtExpiry( |
132 | | Real spot, Real variance, Real discount) |
133 | 0 | { |
134 | 0 | Rate dividendDiscount = |
135 | 0 | process_->dividendYield()->discount(exercise_->lastDate()); |
136 | |
|
137 | 0 | QL_REQUIRE(spot>0.0, |
138 | 0 | "positive spot value required"); |
139 | | |
140 | 0 | QL_REQUIRE(discount>0.0, |
141 | 0 | "positive discount required"); |
142 | | |
143 | 0 | QL_REQUIRE(dividendDiscount>0.0, |
144 | 0 | "positive dividend discount required"); |
145 | | |
146 | 0 | QL_REQUIRE(variance>=0.0, |
147 | 0 | "negative variance not allowed"); |
148 | | |
149 | 0 | Option::Type type = payoff_->optionType(); |
150 | 0 | Real strike = payoff_->strike(); |
151 | 0 | Real barrier = arguments_.barrier; |
152 | 0 | QL_REQUIRE(barrier>0.0, |
153 | 0 | "positive barrier value required"); |
154 | 0 | Barrier::Type barrierType = arguments_.barrierType; |
155 | |
|
156 | 0 | Real stdDev = std::sqrt(variance); |
157 | 0 | Real mu = std::log(dividendDiscount/discount)/variance - 0.5; |
158 | 0 | Real K = 0; |
159 | | |
160 | | // binary cash-or-nothing payoff? |
161 | 0 | ext::shared_ptr<CashOrNothingPayoff> coo = |
162 | 0 | ext::dynamic_pointer_cast<CashOrNothingPayoff>(payoff_); |
163 | 0 | if (coo != nullptr) { |
164 | 0 | K = coo->cashPayoff(); |
165 | 0 | } |
166 | | |
167 | | // binary asset-or-nothing payoff? |
168 | 0 | ext::shared_ptr<AssetOrNothingPayoff> aoo = |
169 | 0 | ext::dynamic_pointer_cast<AssetOrNothingPayoff>(payoff_); |
170 | 0 | if (aoo != nullptr) { |
171 | 0 | mu += 1.0; |
172 | 0 | K = spot * dividendDiscount / discount; // forward |
173 | 0 | } |
174 | |
|
175 | 0 | Real log_S_X = std::log(spot/strike); |
176 | 0 | Real log_S_H = std::log(spot/barrier); |
177 | 0 | Real log_H_S = std::log(barrier/spot); |
178 | 0 | Real log_H2_SX = std::log(barrier*barrier/(spot*strike)); |
179 | 0 | Real H_S_2mu = std::pow(barrier/spot, 2*mu); |
180 | |
|
181 | 0 | Real eta = (barrierType == Barrier::DownIn || |
182 | 0 | barrierType == Barrier::DownOut ? 1.0 : -1.0); |
183 | 0 | Real phi = (type == Option::Call ? 1.0 : -1.0); |
184 | |
|
185 | 0 | Real x1, x2, y1, y2; |
186 | 0 | Real cum_x1, cum_x2, cum_y1, cum_y2; |
187 | 0 | if (variance>=QL_EPSILON) { |
188 | | |
189 | | // we calculate using mu*stddev instead of (mu+1)*stddev |
190 | | // because cash-or-nothing don't need it. asset-or-nothing |
191 | | // mu is really mu+1 |
192 | 0 | x1 = phi*(log_S_X/stdDev + mu*stdDev); |
193 | 0 | x2 = phi*(log_S_H/stdDev + mu*stdDev); |
194 | 0 | y1 = eta*(log_H2_SX/stdDev + mu*stdDev); |
195 | 0 | y2 = eta*(log_H_S/stdDev + mu*stdDev); |
196 | |
|
197 | 0 | CumulativeNormalDistribution f; |
198 | 0 | cum_x1 = f(x1); |
199 | 0 | cum_x2 = f(x2); |
200 | 0 | cum_y1 = f(y1); |
201 | 0 | cum_y2 = f(y2); |
202 | 0 | } else { |
203 | 0 | if (log_S_X>0) |
204 | 0 | cum_x1= 1.0; |
205 | 0 | else |
206 | 0 | cum_x1= 0.0; |
207 | 0 | if (log_S_H>0) |
208 | 0 | cum_x2= 1.0; |
209 | 0 | else |
210 | 0 | cum_x2= 0.0; |
211 | 0 | if (log_H2_SX>0) |
212 | 0 | cum_y1= 1.0; |
213 | 0 | else |
214 | 0 | cum_y1= 0.0; |
215 | 0 | if (log_H_S>0) |
216 | 0 | cum_y2= 1.0; |
217 | 0 | else |
218 | 0 | cum_y2= 0.0; |
219 | 0 | } |
220 | |
|
221 | 0 | Real alpha = 0; |
222 | |
|
223 | 0 | switch (barrierType) { |
224 | 0 | case Barrier::DownIn: |
225 | 0 | if (type == Option::Call) { |
226 | | // down-in and call |
227 | 0 | if (strike >= barrier) { |
228 | | // B3 (eta=1, phi=1) |
229 | 0 | alpha = H_S_2mu * cum_y1; |
230 | 0 | } else { |
231 | | // B1-B2+B4 (eta=1, phi=1) |
232 | 0 | alpha = cum_x1 - cum_x2 + H_S_2mu * cum_y2; |
233 | 0 | } |
234 | 0 | } |
235 | 0 | else { |
236 | | // down-in and put |
237 | 0 | if (strike >= barrier) { |
238 | | // B2-B3+B4 (eta=1, phi=-1) |
239 | 0 | alpha = cum_x2 + H_S_2mu*(-cum_y1 + cum_y2); |
240 | 0 | } else { |
241 | | // B1 (eta=1, phi=-1) |
242 | 0 | alpha = cum_x1; |
243 | 0 | } |
244 | 0 | } |
245 | 0 | break; |
246 | | |
247 | 0 | case Barrier::UpIn: |
248 | 0 | if (type == Option::Call) { |
249 | | // up-in and call |
250 | 0 | if (strike >= barrier) { |
251 | | // B1 (eta=-1, phi=1) |
252 | 0 | alpha = cum_x1; |
253 | 0 | } else { |
254 | | // B2-B3+B4 (eta=-1, phi=1) |
255 | 0 | alpha = cum_x2 + H_S_2mu * (-cum_y1 + cum_y2); |
256 | 0 | } |
257 | 0 | } |
258 | 0 | else { |
259 | | // up-in and put |
260 | 0 | if (strike >= barrier) { |
261 | | // B1-B2+B4 (eta=-1, phi=-1) |
262 | 0 | alpha = cum_x1 - cum_x2 + H_S_2mu * cum_y2; |
263 | 0 | } else { |
264 | | // B3 (eta=-1, phi=-1) |
265 | 0 | alpha = H_S_2mu * cum_y1; |
266 | 0 | } |
267 | 0 | } |
268 | 0 | break; |
269 | | |
270 | 0 | case Barrier::DownOut: |
271 | 0 | if (type == Option::Call) { |
272 | | // down-out and call |
273 | 0 | if (strike >= barrier) { |
274 | | // B1-B3 (eta=1, phi=1) |
275 | 0 | alpha = cum_x1 - H_S_2mu * cum_y1; |
276 | 0 | } else { |
277 | | // B2-B4 (eta=1, phi=1) |
278 | 0 | alpha = cum_x2 - H_S_2mu * cum_y2; |
279 | 0 | } |
280 | 0 | } |
281 | 0 | else { |
282 | | // down-out and put |
283 | 0 | if (strike >= barrier) { |
284 | | // B1-B2+B3-B4 (eta=1, phi=-1) |
285 | 0 | alpha = cum_x1 - cum_x2 + H_S_2mu * (cum_y1-cum_y2); |
286 | 0 | } else { |
287 | | // always 0 |
288 | 0 | alpha = 0; |
289 | 0 | } |
290 | 0 | } |
291 | 0 | break; |
292 | 0 | case Barrier::UpOut: |
293 | 0 | if (type == Option::Call) { |
294 | | // up-out and call |
295 | 0 | if (strike >= barrier) { |
296 | | // always 0 |
297 | 0 | alpha = 0; |
298 | 0 | } else { |
299 | | // B1-B2+B3-B4 (eta=-1, phi=1) |
300 | 0 | alpha = cum_x1 - cum_x2 + H_S_2mu * (cum_y1-cum_y2); |
301 | 0 | } |
302 | 0 | } |
303 | 0 | else { |
304 | | // up-out and put |
305 | 0 | if (strike >= barrier) { |
306 | | // B2-B4 (eta=-1, phi=-1) |
307 | 0 | alpha = cum_x2 - H_S_2mu * cum_y2; |
308 | 0 | } else { |
309 | | // B1-B3 (eta=-1, phi=-1) |
310 | 0 | alpha = cum_x1 - H_S_2mu * cum_y1; |
311 | 0 | } |
312 | 0 | } |
313 | 0 | break; |
314 | 0 | default: |
315 | 0 | QL_FAIL("invalid barrier type"); |
316 | 0 | } |
317 | | |
318 | 0 | return discount * K * alpha; |
319 | 0 | } |
320 | | |
321 | | |
322 | | |
323 | | } |
324 | | |