/src/quantlib/ql/pricingengines/barrier/analyticpartialtimebarrieroptionengine.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2014 Master IMAFA - Polytech'Nice Sophia - Université de Nice Sophia Antipolis |
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/exercise.hpp> |
21 | | #include <ql/math/functional.hpp> |
22 | | #include <ql/pricingengines/barrier/analyticpartialtimebarrieroptionengine.hpp> |
23 | | #include <ql/math/distributions/bivariatenormaldistribution.hpp> |
24 | | #include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp> |
25 | | #include <utility> |
26 | | |
27 | | namespace QuantLib { |
28 | | |
29 | | AnalyticPartialTimeBarrierOptionEngine::AnalyticPartialTimeBarrierOptionEngine( |
30 | | ext::shared_ptr<GeneralizedBlackScholesProcess> process) |
31 | 0 | : process_(std::move(process)) { |
32 | 0 | registerWith(process_); |
33 | 0 | } |
34 | | |
35 | | Real AnalyticPartialTimeBarrierOptionEngine::calculate(PartialTimeBarrierOption::arguments& arguments, |
36 | | const ext::shared_ptr<PlainVanillaPayoff>& payoff, |
37 | 0 | const ext::shared_ptr<GeneralizedBlackScholesProcess>& process) const { |
38 | 0 | Barrier::Type barrierType = arguments.barrierType; |
39 | 0 | PartialBarrier::Range barrierRange = arguments.barrierRange; |
40 | 0 | Rate r = process->riskFreeRate()->zeroRate(residualTime(), Continuous, |
41 | 0 | NoFrequency); |
42 | 0 | Rate q = process->dividendYield()->zeroRate(residualTime(), Continuous, |
43 | 0 | NoFrequency); |
44 | 0 | Real barrier = arguments.barrier; |
45 | 0 | Real strike = payoff->strike(); |
46 | | |
47 | 0 | switch (barrierType) { |
48 | 0 | case Barrier::DownOut: |
49 | 0 | switch (barrierRange) { |
50 | 0 | case PartialBarrier::Start: |
51 | 0 | return CA(1, barrier, strike, r, q); |
52 | 0 | case PartialBarrier::EndB1: |
53 | 0 | return CoB1(barrier, strike, r, q); |
54 | 0 | case PartialBarrier::EndB2: |
55 | 0 | return CoB2(Barrier::DownOut, barrier, strike, r, q); |
56 | 0 | default: |
57 | 0 | QL_FAIL("invalid barrier range"); |
58 | 0 | } |
59 | 0 | break; |
60 | | |
61 | 0 | case Barrier::DownIn: |
62 | 0 | switch (barrierRange) { |
63 | 0 | case PartialBarrier::Start: |
64 | 0 | return CIA(1, barrier, strike, r, q, payoff, process); |
65 | 0 | case PartialBarrier::EndB1: |
66 | 0 | case PartialBarrier::EndB2: |
67 | 0 | QL_FAIL("Down-and-in partial-time end barrier is not implemented"); |
68 | 0 | default: |
69 | 0 | QL_FAIL("invalid barrier range"); |
70 | 0 | } |
71 | 0 | break; |
72 | | |
73 | 0 | case Barrier::UpOut: |
74 | 0 | switch (barrierRange) { |
75 | 0 | case PartialBarrier::Start: |
76 | 0 | return CA(-1, barrier, strike, r, q); |
77 | 0 | case PartialBarrier::EndB1: |
78 | 0 | return CoB1(barrier, strike, r, q); |
79 | 0 | case PartialBarrier::EndB2: |
80 | 0 | return CoB2(Barrier::UpOut, barrier, strike, r, q); |
81 | 0 | default: |
82 | 0 | QL_FAIL("invalid barrier range"); |
83 | 0 | } |
84 | 0 | break; |
85 | | |
86 | 0 | case Barrier::UpIn: |
87 | 0 | switch (barrierRange) { |
88 | 0 | case PartialBarrier::Start: |
89 | 0 | return CIA(-1, barrier, strike, r, q, payoff, process); |
90 | 0 | case PartialBarrier::EndB1: |
91 | 0 | case PartialBarrier::EndB2: |
92 | 0 | QL_FAIL("Up-and-in partial-time end barrier is not implemented"); |
93 | 0 | default: |
94 | 0 | QL_FAIL("invalid barrier range"); |
95 | 0 | } |
96 | 0 | break; |
97 | 0 | default: |
98 | 0 | QL_FAIL("unknown barrier type"); |
99 | 0 | } |
100 | 0 | } |
101 | | |
102 | 0 | void AnalyticPartialTimeBarrierOptionEngine::calculate() const { |
103 | 0 | ext::shared_ptr<PlainVanillaPayoff> payoff = |
104 | 0 | ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff); |
105 | 0 | QL_REQUIRE(payoff, "non-plain payoff given"); |
106 | 0 | QL_REQUIRE(payoff->strike()>0.0, |
107 | 0 | "strike must be positive"); |
108 | | |
109 | 0 | Real spot = process_->x0(); |
110 | 0 | QL_REQUIRE(spot > 0.0, "negative or null underlying given"); |
111 | | |
112 | 0 | auto getSymmetricBarrierType = [](Barrier::Type barrierType) -> Barrier::Type { |
113 | 0 | if (barrierType == Barrier::UpIn) return Barrier::DownIn; |
114 | 0 | if (barrierType == Barrier::DownIn) return Barrier::UpIn; |
115 | 0 | if (barrierType == Barrier::UpOut) return Barrier::DownOut; |
116 | 0 | return Barrier::UpOut; |
117 | 0 | }; |
118 | |
|
119 | 0 | auto tmp_arguments_ = arguments_; |
120 | 0 | if (payoff->optionType() == Option::Put) |
121 | 0 | { |
122 | 0 | Real spotSq = spot * spot; |
123 | 0 | Real callStrike = spotSq / payoff->strike(); |
124 | 0 | ext::shared_ptr<PlainVanillaPayoff> callPayoff = |
125 | 0 | ext::make_shared<PlainVanillaPayoff>(Option::Call, callStrike); |
126 | 0 | tmp_arguments_.barrierType = getSymmetricBarrierType(arguments_.barrierType); |
127 | 0 | tmp_arguments_.barrier = spotSq / arguments_.barrier; |
128 | 0 | tmp_arguments_.payoff = callPayoff; |
129 | 0 | auto callProcess = ext::make_shared<GeneralizedBlackScholesProcess>( |
130 | 0 | process_->stateVariable(), |
131 | 0 | process_->riskFreeRate(), |
132 | 0 | process_->dividendYield(), |
133 | 0 | process_->blackVolatility() |
134 | 0 | ); |
135 | |
|
136 | 0 | results_.value = payoff->strike() / spot * calculate(tmp_arguments_, callPayoff, callProcess); |
137 | 0 | } else |
138 | 0 | results_.value = calculate(tmp_arguments_, payoff, process_); |
139 | 0 | } |
140 | | |
141 | | Real AnalyticPartialTimeBarrierOptionEngine::CoB2( |
142 | | Barrier::Type barrierType, |
143 | 0 | Real barrier, Real strike, Rate r, Rate q) const { |
144 | 0 | Real result = 0.0; |
145 | 0 | Real b = r - q; |
146 | 0 | Real T = residualTime(); |
147 | 0 | Real S = underlying(); |
148 | 0 | Real mu_ = mu(strike, b); |
149 | 0 | Real g1_ = g1(barrier, strike, b); |
150 | 0 | Real g2_ = g2(barrier, strike, b); |
151 | 0 | Real g3_ = g3(barrier, strike, b); |
152 | 0 | Real g4_ = g4(barrier, strike, b); |
153 | 0 | Real e1_ = e1(barrier, strike, b); |
154 | 0 | Real e2_ = e2(barrier, strike, b); |
155 | 0 | Real e3_ = e3(barrier, strike, b); |
156 | 0 | Real e4_ = e4(barrier, strike, b); |
157 | 0 | Real rho_ = rho(); |
158 | 0 | Real HSMu = HS(S, barrier, 2 * mu_); |
159 | 0 | Real HSMu1 = HS(S, barrier, 2 * (mu_ + 1)); |
160 | 0 | Real X1 = strike * std::exp(-r * T); |
161 | |
|
162 | 0 | if (strike < barrier){ |
163 | 0 | switch (barrierType) { |
164 | 0 | case Barrier::DownOut: |
165 | 0 | result = S * std::exp((b - r) * T); |
166 | 0 | result *= (M(g1_, e1_, rho_) - HSMu1 * M(g3_, -e3_, -rho_)); |
167 | 0 | result -= X1 * (M(g2_, e2_, rho_)-HSMu*M(g4_, -e4_, -rho_)); |
168 | 0 | return result; |
169 | | |
170 | 0 | case Barrier::UpOut: |
171 | 0 | result = S * std::exp((b - r) * T); |
172 | 0 | result *= (M(-g1_, -e1_, rho_) - HSMu1 * M(-g3_, e3_, -rho_)); |
173 | 0 | result -= X1 * (M(-g2_, -e2_, rho_) - HSMu * M(-g4_, e4_, -rho_)); |
174 | 0 | result -= S * std::exp((b - r) * T) * |
175 | 0 | (M(-d1(strike, b), -e1_, rho_) - HSMu1 * |
176 | 0 | M(e3_, -f1(barrier, strike, b),-rho_)); |
177 | 0 | result += X1 * (M(-d2(strike, b), -e2_, rho_) - HSMu * |
178 | 0 | M(e4_, -f2(barrier, strike, b), -rho_)); |
179 | 0 | return result; |
180 | | |
181 | 0 | default: |
182 | 0 | QL_FAIL("invalid barrier type"); |
183 | 0 | } |
184 | 0 | } else { |
185 | 0 | QL_FAIL("case of strike>barrier is not implemented for OutEnd B2 type"); |
186 | 0 | } |
187 | 0 | } |
188 | | |
189 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::CoB1(Real barrier, Real strike, Rate r, Rate q) const { |
190 | 0 | Real result = 0.0; |
191 | 0 | Rate b = r - q; |
192 | 0 | Real T = residualTime(); |
193 | 0 | Real S = underlying(); |
194 | 0 | Real mu_ = mu(strike, b); |
195 | 0 | Real g1_ = g1(barrier, strike, b); |
196 | 0 | Real g2_ = g2(barrier, strike, b); |
197 | 0 | Real g3_ = g3(barrier, strike, b); |
198 | 0 | Real g4_ = g4(barrier, strike, b); |
199 | 0 | Real e1_ = e1(barrier, strike, b); |
200 | 0 | Real e2_ = e2(barrier, strike, b); |
201 | 0 | Real e3_ = e3(barrier, strike, b); |
202 | 0 | Real e4_ = e4(barrier, strike, b); |
203 | 0 | Real rho_ = rho(); |
204 | 0 | Real HSMu = HS(S, barrier, 2 * mu_); |
205 | 0 | Real HSMu1 = HS(S, barrier, 2 * (mu_ + 1)); |
206 | 0 | Real X1 = strike * std::exp(-r * T); |
207 | |
|
208 | 0 | if (strike > barrier) { |
209 | 0 | result = S * std::exp((b - r) * T); |
210 | 0 | result *= (M(d1(strike, b), e1_, rho_) - HSMu1 * M(f1(barrier, strike, b), -e3_, -rho_)); |
211 | 0 | result -= X1 * (M(d2(strike, b), e2_, rho_) - HSMu * M(f2(barrier, strike, b), -e4_, -rho_)); |
212 | 0 | return result; |
213 | 0 | } else { |
214 | 0 | Real S1 = S * std::exp((b - r) * T); |
215 | 0 | result = S1; |
216 | 0 | result *= (M(-g1_, -e1_, rho_) - HSMu1 * M(-g3_,e3_,-rho_)); |
217 | 0 | result -= X1 * (M(-g2_, -e2_, rho_) - HSMu * M(-g4_, e4_, -rho_)); |
218 | 0 | result -= S1 * (M(-d1(strike, b), -e1_, rho_) - HSMu1 * M(-f1(barrier, strike, b), e3_, -rho_)); |
219 | 0 | result += X1 * (M(-d2(strike, b), -e2_, rho_) - HSMu * M(-f2(barrier, strike, b), e4_, -rho_)); |
220 | 0 | result += S1 * (M(g1_, e1_, rho_) - HSMu1 * M(g3_, -e3_, -rho_)); |
221 | 0 | result -= X1 * (M(g2_, e2_, rho_) - HSMu * M(g4_, -e4_, -rho_)); |
222 | 0 | return result; |
223 | 0 | } |
224 | 0 | } |
225 | | |
226 | | // eta = -1: Up-and-In Call |
227 | | // eta = 1: Down-and-In Call |
228 | | Real AnalyticPartialTimeBarrierOptionEngine::CIA( |
229 | | Integer eta, Real barrier, Real strike, Rate r, Rate q, |
230 | | const ext::shared_ptr<PlainVanillaPayoff>& payoff, |
231 | 0 | const ext::shared_ptr<GeneralizedBlackScholesProcess>& process) const { |
232 | 0 | ext::shared_ptr<EuropeanExercise> exercise = |
233 | 0 | ext::dynamic_pointer_cast<EuropeanExercise>(arguments_.exercise); |
234 | |
|
235 | 0 | VanillaOption europeanOption(payoff, exercise); |
236 | |
|
237 | 0 | europeanOption.setPricingEngine( |
238 | 0 | ext::make_shared<AnalyticEuropeanEngine>(process)); |
239 | |
|
240 | 0 | return europeanOption.NPV() - CA(eta, barrier, strike, r, q); |
241 | 0 | } |
242 | | |
243 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::CA(Integer eta, Real barrier, Real strike, Rate r, Rate q) const { |
244 | | //Partial-Time-Start- OUT Call Option calculation |
245 | 0 | Real b = r - q; |
246 | 0 | Real rho_ = rho(); |
247 | 0 | Real T = residualTime(); |
248 | 0 | Real S = underlying(); |
249 | 0 | Real mu_ = mu(strike, b); |
250 | 0 | Real e1_ = e1(barrier, strike, b); |
251 | 0 | Real e2_ = e2(barrier, strike, b); |
252 | 0 | Real e3_ = e3(barrier, strike, b); |
253 | 0 | Real e4_ = e4(barrier, strike, b); |
254 | 0 | Real HSMu = HS(S, barrier,2 * mu_); |
255 | 0 | Real HSMu1 = HS(S, barrier, 2 * (mu_ + 1)); |
256 | |
|
257 | 0 | Real result; |
258 | 0 | result = S * std::exp((b - r) * T); |
259 | 0 | result *= (M(d1(strike, b), eta * e1_, eta * rho_)-HSMu1 * |
260 | 0 | M(f1(barrier, strike, b), eta * e3_, eta * rho_)); |
261 | 0 | result -= (strike * std::exp(-r * T) * |
262 | 0 | (M(d2(strike, b),eta * e2_, eta * rho_) - HSMu * |
263 | 0 | M(f2(barrier, strike, b), eta * e4_, eta * rho_))); |
264 | 0 | return result; |
265 | 0 | } |
266 | | |
267 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::underlying() const { |
268 | 0 | return process_->x0(); |
269 | 0 | } |
270 | | |
271 | 0 | Time AnalyticPartialTimeBarrierOptionEngine::residualTime() const { |
272 | 0 | return process_->time(arguments_.exercise->lastDate()); |
273 | 0 | } |
274 | | |
275 | 0 | Time AnalyticPartialTimeBarrierOptionEngine::coverEventTime() const { |
276 | 0 | return process_->time(arguments_.coverEventDate); |
277 | 0 | } |
278 | | |
279 | 0 | Volatility AnalyticPartialTimeBarrierOptionEngine::volatility(Time t, Real strike) const { |
280 | 0 | return process_->blackVolatility()->blackVol(t, strike); |
281 | 0 | } |
282 | | |
283 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::f1(Real barrier, Real strike, Rate b) const { |
284 | 0 | Real S = underlying(); |
285 | 0 | Real T = residualTime(); |
286 | 0 | Real sigma = volatility(T, strike); |
287 | 0 | return (std::log(S / strike) + 2 * std::log(barrier / S) + |
288 | 0 | (b + (squared(sigma) / 2))*T) / (sigma*std::sqrt(T)); |
289 | 0 | } |
290 | | |
291 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::f2(Real barrier, Real strike, Rate b) const { |
292 | 0 | Time T = residualTime(); |
293 | 0 | return f1(barrier, strike, b) - volatility(T, strike) * std::sqrt(T); |
294 | 0 | } |
295 | | |
296 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::M(Real a, Real b, Real rho) const { |
297 | 0 | BivariateCumulativeNormalDistributionDr78 CmlNormDist(rho); |
298 | 0 | return CmlNormDist(a,b); |
299 | 0 | } |
300 | | |
301 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::rho() const { |
302 | 0 | return std::sqrt(coverEventTime() / residualTime()); |
303 | 0 | } |
304 | | |
305 | 0 | Rate AnalyticPartialTimeBarrierOptionEngine::mu(Real strike, Rate b) const { |
306 | 0 | Volatility vol = volatility(coverEventTime(), strike); |
307 | 0 | return (b - (vol * vol) / 2) / (vol * vol); |
308 | 0 | } |
309 | | |
310 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::d1(Real strike, Rate b) const { |
311 | 0 | Time T2 = residualTime(); |
312 | 0 | Volatility vol = volatility(T2, strike); |
313 | 0 | return (std::log(underlying() / strike) + (b + vol * vol / 2) * T2) / (std::sqrt(T2) * vol); |
314 | 0 | } |
315 | | |
316 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::d2(Real strike, Rate b) const { |
317 | 0 | Time T2 = residualTime(); |
318 | 0 | Volatility vol = volatility(T2, strike); |
319 | 0 | return d1(strike, b) - vol * std::sqrt(T2); |
320 | 0 | } |
321 | | |
322 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::e1(Real barrier, Real strike, Rate b) const { |
323 | 0 | Time T1 = coverEventTime(); |
324 | 0 | Volatility vol = volatility(T1, strike); |
325 | 0 | return (std::log(underlying() / barrier) + (b + vol * vol / 2) * T1) / (std::sqrt(T1) * vol); |
326 | 0 | } |
327 | | |
328 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::e2(Real barrier, Real strike, Rate b) const { |
329 | 0 | Time T1 = coverEventTime(); |
330 | 0 | Volatility vol = volatility(T1, strike); |
331 | 0 | return e1(barrier, strike, b) - vol * std::sqrt(T1); |
332 | 0 | } |
333 | | |
334 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::e3(Real barrier, Real strike, Rate b) const { |
335 | 0 | Time T1 = coverEventTime(); |
336 | 0 | Real vol = volatility(T1, strike); |
337 | 0 | return e1(barrier, strike, b) + (2 * std::log(barrier / underlying()) / (vol * std::sqrt(T1))); |
338 | 0 | } |
339 | | |
340 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::e4(Real barrier, Real strike, Rate b) const { |
341 | 0 | Time t = coverEventTime(); |
342 | 0 | return e3(barrier, strike, b) - volatility(t, strike) * std::sqrt(t); |
343 | 0 | } |
344 | | |
345 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::g1(Real barrier, Real strike, Rate b) const { |
346 | 0 | Time T2 = residualTime(); |
347 | 0 | Volatility vol = volatility(T2, strike); |
348 | 0 | return (std::log(underlying() / barrier) + (b + vol * vol / 2) * T2) / (std::sqrt(T2) * vol); |
349 | 0 | } |
350 | | |
351 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::g2(Real barrier, Real strike, Rate b) const { |
352 | 0 | Time T2 = residualTime(); |
353 | 0 | Volatility vol = volatility(T2, strike); |
354 | 0 | return g1(barrier, strike, b) - vol * std::sqrt(T2); |
355 | 0 | } |
356 | | |
357 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::g3(Real barrier, Real strike, Rate b) const { |
358 | 0 | Time T2 = residualTime(); |
359 | 0 | Real vol = volatility(T2, strike); |
360 | 0 | return g1(barrier, strike, b) + (2 * std::log(barrier / underlying()) /(vol * std::sqrt(T2))); |
361 | 0 | } |
362 | | |
363 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::g4(Real barrier, Real strike, Rate b) const { |
364 | 0 | Time T2 = residualTime(); |
365 | 0 | Real vol = volatility(T2, strike); |
366 | 0 | return g3(barrier, strike, b) - vol * std::sqrt(T2); |
367 | 0 | } |
368 | | |
369 | 0 | Real AnalyticPartialTimeBarrierOptionEngine::HS(Real S, Real H, Real power) const { |
370 | 0 | return std::pow((H / S), power); |
371 | 0 | } |
372 | | |
373 | | } |
374 | | |