/src/quantlib/ql/experimental/exoticoptions/continuousarithmeticasianvecerengine.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2014 Bernd Lewerenz |
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/experimental/exoticoptions/continuousarithmeticasianvecerengine.hpp> |
22 | | #include <ql/instruments/vanillaoption.hpp> |
23 | | #include <ql/math/distributions/normaldistribution.hpp> |
24 | | #include <ql/math/rounding.hpp> |
25 | | #include <ql/methods/finitedifferences/tridiagonaloperator.hpp> |
26 | | #include <ql/pricingengines/blackcalculator.hpp> |
27 | | #include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp> |
28 | | #include <utility> |
29 | | |
30 | | namespace QuantLib { |
31 | | |
32 | | ContinuousArithmeticAsianVecerEngine::ContinuousArithmeticAsianVecerEngine( |
33 | | ext::shared_ptr<GeneralizedBlackScholesProcess> process, |
34 | | Handle<Quote> currentAverage, |
35 | | Date startDate, |
36 | | Size timeSteps, |
37 | | Size assetSteps, |
38 | | Real z_min, |
39 | | Real z_max) |
40 | 0 | : process_(std::move(process)), currentAverage_(std::move(currentAverage)), |
41 | 0 | startDate_(startDate), z_min_(z_min), z_max_(z_max), timeSteps_(timeSteps), |
42 | 0 | assetSteps_(assetSteps) { |
43 | 0 | registerWith(process_); |
44 | 0 | registerWith(currentAverage_); |
45 | 0 | } |
46 | | |
47 | 0 | void ContinuousArithmeticAsianVecerEngine::calculate() const { |
48 | 0 | Real expectedAverage; |
49 | |
|
50 | 0 | QL_REQUIRE(arguments_.averageType == Average::Arithmetic, |
51 | 0 | "not an Arithmetic average option"); |
52 | 0 | QL_REQUIRE(arguments_.exercise->type() == Exercise::European, |
53 | 0 | "not an European Option"); |
54 | | |
55 | 0 | DayCounter rfdc = process_->riskFreeRate()->dayCounter(); |
56 | 0 | DayCounter divdc = process_->dividendYield()->dayCounter(); |
57 | 0 | DayCounter voldc = process_->blackVolatility()->dayCounter(); |
58 | 0 | Real S_0 = process_->stateVariable()->value(); |
59 | | |
60 | | // payoff |
61 | 0 | ext::shared_ptr<StrikedTypePayoff> payoff = |
62 | 0 | ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff); |
63 | 0 | QL_REQUIRE(payoff, "non-plain payoff given"); |
64 | | |
65 | | // original time to maturity |
66 | 0 | Date maturity = arguments_.exercise->lastDate(); |
67 | |
|
68 | 0 | Real X = payoff->strike(); |
69 | 0 | QL_REQUIRE(z_min_<=0 && z_max_>=0, |
70 | 0 | "strike (0 for vecer fixed strike asian) not on Grid"); |
71 | | |
72 | 0 | Volatility sigma = |
73 | 0 | process_->blackVolatility()->blackVol(maturity, X); |
74 | |
|
75 | 0 | Rate r = process_->riskFreeRate()-> |
76 | 0 | zeroRate(maturity, rfdc, Continuous, NoFrequency); |
77 | 0 | Rate q = process_->dividendYield()-> |
78 | 0 | zeroRate(maturity, divdc, Continuous, NoFrequency); |
79 | |
|
80 | 0 | Date today(Settings::instance().evaluationDate()); |
81 | |
|
82 | 0 | QL_REQUIRE(startDate_>=today, |
83 | 0 | "Seasoned Asian not yet implemented"); |
84 | | |
85 | | // Expiry in Years |
86 | 0 | Time T = rfdc.yearFraction(today, |
87 | 0 | arguments_.exercise->lastDate()); |
88 | 0 | Time T1 = rfdc.yearFraction(today, |
89 | 0 | startDate_ ); // Average Begin |
90 | 0 | Time T2 = T; // Average End (In this version only Maturity...) |
91 | |
|
92 | 0 | if ((T2 - T1) < 0.001) { |
93 | | // its a vanilla option. Use vanilla engine |
94 | 0 | VanillaOption europeanOption(payoff, arguments_.exercise); |
95 | 0 | europeanOption.setPricingEngine( |
96 | 0 | ext::make_shared<AnalyticEuropeanEngine>(process_)); |
97 | 0 | results_.value = europeanOption.NPV(); |
98 | |
|
99 | 0 | } else { |
100 | 0 | Real Theta = 0.5; // Mixed Scheme: 0.5 = Crank Nicolson |
101 | 0 | Real Z_0 = cont_strategy(0,T1,T2,q,r) - std::exp(-r*T) * X /S_0; |
102 | |
|
103 | 0 | QL_REQUIRE(Z_0>=z_min_ && Z_0<=z_max_, |
104 | 0 | "spot not on grid"); |
105 | | |
106 | 0 | Real h = (z_max_ - z_min_) / assetSteps_; // Space step size |
107 | 0 | Real k = T / timeSteps_; // Time Step size |
108 | |
|
109 | 0 | Real sigma2 = sigma * sigma, vecerTerm; |
110 | |
|
111 | 0 | Array SVec(assetSteps_+1),u_initial(assetSteps_+1), |
112 | 0 | u(assetSteps_+1),rhs(assetSteps_+1); |
113 | |
|
114 | 0 | for (Natural i= 0; i<= SVec.size()-1;i++) { |
115 | 0 | SVec[i] = z_min_ + i * h; // Value of Underlying on the grid |
116 | 0 | } |
117 | | |
118 | | // Begin gamma construction |
119 | 0 | TridiagonalOperator gammaOp(assetSteps_+1); |
120 | 0 | gammaOp.setFirstRow(0.0,0.0); |
121 | 0 | gammaOp.setMidRows(1/(h*h),-2/(h*h),1/(h*h)); |
122 | 0 | gammaOp.setLastRow(0.0,0.0); |
123 | |
|
124 | 0 | Array upperD = gammaOp.upperDiagonal(); |
125 | 0 | Array lowerD = gammaOp.lowerDiagonal(); |
126 | 0 | Array Dia = gammaOp.diagonal(); |
127 | | |
128 | | // Construct Vecer operator |
129 | 0 | TridiagonalOperator explicit_part(gammaOp.size()); |
130 | 0 | TridiagonalOperator implicit_part(gammaOp.size()); |
131 | |
|
132 | 0 | for (Natural i= 0; i<= SVec.size()-1;i++) { |
133 | 0 | u_initial[i] = std::max<Real>(SVec[i] , 0.0); // Call Payoff |
134 | 0 | } |
135 | |
|
136 | 0 | u = u_initial; |
137 | | |
138 | | // Start Time Loop |
139 | |
|
140 | 0 | for (Natural j = 1; j<=timeSteps_;j++) { |
141 | 0 | if (Theta != 1.0) { // Explicit Part |
142 | 0 | for (Natural i = 1; i<= SVec.size()-2;i++) { |
143 | 0 | vecerTerm = SVec[i] - std::exp(-q * (T-(j-1)*k)) |
144 | 0 | * cont_strategy(T-(j-1)*k,T1,T2,q,r); |
145 | 0 | gammaOp.setMidRow(i, |
146 | 0 | 0.5 * sigma2 * vecerTerm * vecerTerm * lowerD[i-1], |
147 | 0 | 0.5 * sigma2 * vecerTerm * vecerTerm * Dia[i], |
148 | 0 | 0.5 * sigma2 * vecerTerm * vecerTerm * upperD[i]); |
149 | 0 | } |
150 | 0 | explicit_part = TridiagonalOperator::identity(gammaOp.size()) + |
151 | 0 | (1 - Theta) * k * gammaOp; |
152 | 0 | explicit_part.setFirstRow(1.0,0.0); // Apply before applying |
153 | 0 | explicit_part.setLastRow(-1.0,1.0); // Neumann BC |
154 | |
|
155 | 0 | u = explicit_part.applyTo(u); |
156 | | |
157 | | // Apply after applying (Neumann BC) |
158 | 0 | u[assetSteps_] = u[assetSteps_-1] + h; |
159 | 0 | u[0] = 0; |
160 | 0 | } // End Explicit Part |
161 | |
|
162 | 0 | if (Theta != 0.0) { // Implicit Part |
163 | 0 | for (Natural i = 1; i<= SVec.size()-2;i++) { |
164 | 0 | vecerTerm = SVec[i] - std::exp(-q * (T-j*k)) * |
165 | 0 | cont_strategy(T-j*k,T1,T2,q,r); |
166 | 0 | gammaOp.setMidRow(i, |
167 | 0 | 0.5 * sigma2 * vecerTerm * vecerTerm * lowerD[i-1], |
168 | 0 | 0.5 * sigma2 * vecerTerm * vecerTerm * Dia[i], |
169 | 0 | 0.5 * sigma2 * vecerTerm * vecerTerm * upperD[i]); |
170 | 0 | } |
171 | |
|
172 | 0 | implicit_part = TridiagonalOperator::identity(gammaOp.size()) - |
173 | 0 | Theta * k * gammaOp; |
174 | | |
175 | | // Apply before solving |
176 | 0 | implicit_part.setFirstRow(1.0,0.0); |
177 | 0 | implicit_part.setLastRow(-1.0,1.0); |
178 | 0 | rhs = u; |
179 | 0 | rhs[0] = 0; // Lower BC |
180 | 0 | rhs[assetSteps_] = h; // Upper BC (Neumann) Delta=1 |
181 | 0 | u = implicit_part.solveFor(rhs); |
182 | 0 | } // End implicit Part |
183 | 0 | } // End Time Loop |
184 | |
|
185 | 0 | DownRounding Rounding(0); |
186 | 0 | auto lowerI = Integer(Rounding((Z_0 - z_min_) / h)); |
187 | | // Interpolate solution |
188 | 0 | Real pv; |
189 | |
|
190 | 0 | pv = u[lowerI] + (u[lowerI+1] - u[lowerI]) * (Z_0 - SVec[lowerI])/h; |
191 | 0 | results_.value = S_0 * pv; |
192 | |
|
193 | 0 | if (payoff->optionType()==Option::Put) { |
194 | | // Apply Call Put Parity for Asians |
195 | 0 | if (r == q) { |
196 | 0 | expectedAverage = S_0; |
197 | 0 | } else { |
198 | 0 | expectedAverage = |
199 | 0 | S_0 * (std::exp( (r-q) * T2) - |
200 | 0 | std::exp( (r-q) * T1)) / ((r-q) * (T2-T1)); |
201 | 0 | } |
202 | |
|
203 | 0 | Real asianForward = std::exp(-r * T2) * (expectedAverage - X); |
204 | 0 | results_.value = results_.value - asianForward; |
205 | 0 | } |
206 | 0 | } |
207 | 0 | } |
208 | | |
209 | | // Replication of Average by holding this amount in Assets |
210 | | Real ContinuousArithmeticAsianVecerEngine::cont_strategy(Time t, |
211 | | Time T1, |
212 | | Time T2, |
213 | | Real v, |
214 | 0 | Real r) const { |
215 | 0 | Real const eps= 0.00001; |
216 | |
|
217 | 0 | QL_REQUIRE(T1 <= T2, "Average Start must be before Average End"); |
218 | 0 | if (std::fabs(t-T2) < eps) { |
219 | 0 | return 0.0; |
220 | 0 | } else { |
221 | 0 | if (t<T1) { |
222 | 0 | if (std::fabs(r-v) >= eps) { |
223 | 0 | return (std::exp(v * (t-T2)) * |
224 | 0 | (1 - std::exp((v-r) * (T2-T1) )) / |
225 | 0 | (( r - v) * (T2 - T1) )); |
226 | 0 | } else { |
227 | 0 | return std::exp(v*(t-T2)); |
228 | 0 | } // end else v-r ==0 |
229 | 0 | } else { // t<T1 |
230 | 0 | if (std::fabs(r-v) >= eps) { |
231 | 0 | return std::exp(v * (t-T2)) * |
232 | 0 | (1 - std::exp( (v - r) * (T2-t) )) / |
233 | 0 | (( r - v) * (T2 - T1) ); |
234 | 0 | } else { |
235 | 0 | return std::exp(v * (t-T2)) * (T2 - t) / (T2 - T1); |
236 | 0 | } |
237 | 0 | } |
238 | 0 | } |
239 | 0 | } |
240 | | |
241 | | } |