Coverage Report

Created: 2026-03-11 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}