File: continuousarithmeticasianvecerengine.cpp

package info (click to toggle)
quantlib 1.41-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 41,480 kB
  • sloc: cpp: 400,885; makefile: 6,547; python: 214; sh: 150; lisp: 86
file content (241 lines) | stat: -rw-r--r-- 10,032 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
  Copyright (C) 2014 Bernd Lewerenz

  This file is part of QuantLib, a free-software/open-source library
  for financial quantitative analysts and developers - http://quantlib.org/

  QuantLib is free software: you can redistribute it and/or modify it
  under the terms of the QuantLib license.  You should have received a
  copy of the license along with this program; if not, please email
  <quantlib-dev@lists.sf.net>. The license is also available online at
  <https://www.quantlib.org/license.shtml>.

  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

#include <ql/exercise.hpp>
#include <ql/experimental/exoticoptions/continuousarithmeticasianvecerengine.hpp>
#include <ql/instruments/vanillaoption.hpp>
#include <ql/math/distributions/normaldistribution.hpp>
#include <ql/math/rounding.hpp>
#include <ql/methods/finitedifferences/dminus.hpp>
#include <ql/methods/finitedifferences/dplus.hpp>
#include <ql/methods/finitedifferences/dplusdminus.hpp>
#include <ql/methods/finitedifferences/tridiagonaloperator.hpp>
#include <ql/pricingengines/blackcalculator.hpp>
#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
#include <utility>

namespace QuantLib {

    ContinuousArithmeticAsianVecerEngine::ContinuousArithmeticAsianVecerEngine(
        ext::shared_ptr<GeneralizedBlackScholesProcess> process,
        Handle<Quote> currentAverage,
        Date startDate,
        Size timeSteps,
        Size assetSteps,
        Real z_min,
        Real z_max)
    : process_(std::move(process)), currentAverage_(std::move(currentAverage)),
      startDate_(startDate), z_min_(z_min), z_max_(z_max), timeSteps_(timeSteps),
      assetSteps_(assetSteps) {
        registerWith(process_);
        registerWith(currentAverage_);
    }

    void ContinuousArithmeticAsianVecerEngine::calculate() const {
        Real expectedAverage;

        QL_REQUIRE(arguments_.averageType == Average::Arithmetic,
                   "not an Arithmetic average option");
        QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
                   "not an European Option");

        DayCounter rfdc  = process_->riskFreeRate()->dayCounter();
        DayCounter divdc = process_->dividendYield()->dayCounter();
        DayCounter voldc = process_->blackVolatility()->dayCounter();
        Real S_0 = process_->stateVariable()->value();

        // payoff
        ext::shared_ptr<StrikedTypePayoff> payoff =
            ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
        QL_REQUIRE(payoff, "non-plain payoff given");

        // original time to maturity
        Date maturity = arguments_.exercise->lastDate();

        Real X = payoff->strike();
        QL_REQUIRE(z_min_<=0 && z_max_>=0,
                   "strike (0 for vecer fixed strike asian)  not on Grid");

        Volatility sigma =
            process_->blackVolatility()->blackVol(maturity, X);

        Rate r = process_->riskFreeRate()->
            zeroRate(maturity, rfdc, Continuous, NoFrequency);
        Rate q = process_->dividendYield()->
            zeroRate(maturity, divdc, Continuous, NoFrequency);

        Date today(Settings::instance().evaluationDate());

        QL_REQUIRE(startDate_>=today,
                   "Seasoned Asian not yet implemented");

        // Expiry in Years
        Time T = rfdc.yearFraction(today,
                                   arguments_.exercise->lastDate());
        Time T1 = rfdc.yearFraction(today,
                                    startDate_ );            // Average Begin
        Time T2 = T;            // Average End (In this version only Maturity...)

        if ((T2 - T1) < 0.001) {
            // its a vanilla option. Use vanilla engine
            VanillaOption europeanOption(payoff, arguments_.exercise);
            europeanOption.setPricingEngine(
                        ext::make_shared<AnalyticEuropeanEngine>(process_));
            results_.value = europeanOption.NPV();

        } else {
            Real Theta = 0.5;        // Mixed Scheme: 0.5 = Crank Nicolson
            Real Z_0 = cont_strategy(0,T1,T2,q,r) - std::exp(-r*T) * X /S_0;

            QL_REQUIRE(Z_0>=z_min_ && Z_0<=z_max_,
                       "spot not on grid");

            Real h = (z_max_ - z_min_) / assetSteps_; // Space step size
            Real k = T / timeSteps_;         // Time Step size

            Real sigma2 = sigma * sigma, vecerTerm;

            Array SVec(assetSteps_+1),u_initial(assetSteps_+1),
                  u(assetSteps_+1),rhs(assetSteps_+1);

            for (Natural i= 0; i<= SVec.size()-1;i++) {
                SVec[i] = z_min_ + i * h;     // Value of Underlying on the grid
            }

            // Begin gamma construction
            TridiagonalOperator gammaOp = DPlusDMinus(assetSteps_+1,h);

            Array upperD = gammaOp.upperDiagonal();
            Array lowerD = gammaOp.lowerDiagonal();
            Array Dia    = gammaOp.diagonal();

            // Construct Vecer operator
            TridiagonalOperator explicit_part(gammaOp.size());
            TridiagonalOperator implicit_part(gammaOp.size());

            for (Natural i= 0; i<= SVec.size()-1;i++) {
                u_initial[i] = std::max<Real>(SVec[i] , 0.0); // Call Payoff
            }

            u = u_initial;

            // Start Time Loop

            for (Natural j = 1; j<=timeSteps_;j++) {
                if (Theta != 1.0) { // Explicit Part
                    for (Natural i = 1; i<= SVec.size()-2;i++) {
                        vecerTerm = SVec[i] - std::exp(-q * (T-(j-1)*k))
                                  * cont_strategy(T-(j-1)*k,T1,T2,q,r);
                        gammaOp.setMidRow(i,
                            0.5 * sigma2 * vecerTerm * vecerTerm  * lowerD[i-1],
                            0.5 * sigma2 * vecerTerm * vecerTerm  * Dia[i],
                            0.5 * sigma2 *  vecerTerm * vecerTerm * upperD[i]);
                    }
                    explicit_part = TridiagonalOperator::identity(gammaOp.size()) +
                                    (1 - Theta) * k * gammaOp;
                    explicit_part.setFirstRow(1.0,0.0); // Apply before applying
                    explicit_part.setLastRow(-1.0,1.0); // Neumann BC

                    u = explicit_part.applyTo(u);

                    // Apply after applying (Neumann BC)
                    u[assetSteps_] = u[assetSteps_-1] + h;
                    u[0] = 0;
                } // End Explicit Part

                if (Theta != 0.0) {  // Implicit Part
                    for (Natural i = 1; i<= SVec.size()-2;i++) {
                        vecerTerm = SVec[i] - std::exp(-q * (T-j*k)) *
                                    cont_strategy(T-j*k,T1,T2,q,r);
                        gammaOp.setMidRow(i,
                            0.5 * sigma2 * vecerTerm * vecerTerm * lowerD[i-1],
                            0.5 * sigma2 * vecerTerm * vecerTerm  * Dia[i],
                            0.5 * sigma2 * vecerTerm * vecerTerm * upperD[i]);
                    }

                    implicit_part = TridiagonalOperator::identity(gammaOp.size()) -
                                    Theta * k * gammaOp;

                    // Apply before solving
                    implicit_part.setFirstRow(1.0,0.0);
                    implicit_part.setLastRow(-1.0,1.0);
                    rhs = u;
                    rhs[0] = 0; // Lower BC
                    rhs[assetSteps_] = h; // Upper BC (Neumann) Delta=1
                    u = implicit_part.solveFor(rhs);
                } // End implicit Part
            } // End Time Loop

            DownRounding Rounding(0);
            auto lowerI = Integer(Rounding((Z_0 - z_min_) / h));
            // Interpolate solution
            Real pv;

            pv = u[lowerI] + (u[lowerI+1] - u[lowerI]) * (Z_0 - SVec[lowerI])/h;
            results_.value = S_0 * pv;

            if (payoff->optionType()==Option::Put) {
                // Apply Call Put Parity for Asians
                if (r == q) {
                    expectedAverage = S_0;
                } else {
                    expectedAverage =
                        S_0 * (std::exp( (r-q) * T2) -
                               std::exp( (r-q) * T1)) / ((r-q) * (T2-T1));
                }

                Real asianForward = std::exp(-r * T2) * (expectedAverage -  X);
                results_.value = results_.value - asianForward;
            }
        }
    }

    // Replication of Average by holding this amount in Assets
    Real ContinuousArithmeticAsianVecerEngine::cont_strategy(Time t,
                                                             Time T1,
                                                             Time T2,
                                                             Real v,
                                                             Real r) const {
        Real const eps= 0.00001;

        QL_REQUIRE(T1 <= T2, "Average Start must be before Average End");
        if (std::fabs(t-T2) < eps) {
            return 0.0;
        } else {
            if (t<T1) {
                if (std::fabs(r-v) >= eps) {
                    return (std::exp(v * (t-T2)) *
                           (1 - std::exp((v-r) * (T2-T1) ))  /
                           (( r - v) * (T2 - T1) ));
                } else {
                    return std::exp(v*(t-T2));
                } // end else v-r ==0
            } else { // t<T1
                if (std::fabs(r-v) >= eps) {
                    return std::exp(v * (t-T2)) *
                           (1 - std::exp( (v - r) * (T2-t) )) /
                           (( r - v) * (T2 - T1)  );
                } else {
                    return std::exp(v * (t-T2)) * (T2 - t) / (T2 - T1);
                }
            }
        }
    }

}