/src/quantlib/ql/pricingengines/asian/turnbullwakemanasianengine.cpp
Line | Count | Source |
1 | | /* |
2 | | Copyright (C) 2021 Skandinaviska Enskilda Banken AB (publ) |
3 | | |
4 | | This file is part of QuantLib, a free-software/open-source library |
5 | | for financial quantitative analysts and developers - http://quantlib.org/ |
6 | | |
7 | | QuantLib is free software: you can redistribute it and/or modify it |
8 | | under the terms of the QuantLib license. You should have received a |
9 | | copy of the license along with this program; if not, please email |
10 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
11 | | <https://www.quantlib.org/license.shtml>. |
12 | | |
13 | | This program is distributed in the hope that it will be useful, but WITHOUT |
14 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
15 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
16 | | */ |
17 | | |
18 | | #include <ql/exercise.hpp> |
19 | | #include <ql/pricingengines/asian/turnbullwakemanasianengine.hpp> |
20 | | #include <ql/pricingengines/blackcalculator.hpp> |
21 | | |
22 | | using namespace QuantLib; |
23 | | |
24 | 0 | void TurnbullWakemanAsianEngine::calculate() const { |
25 | | |
26 | | // Enforce a few required things |
27 | 0 | QL_REQUIRE(arguments_.exercise->type() == Exercise::European, "not a European Option"); |
28 | 0 | QL_REQUIRE(arguments_.averageType == Average::Type::Arithmetic, |
29 | 0 | "must be Arithmetic Average::Type"); |
30 | | |
31 | | // Calculate the accrued portion |
32 | 0 | Size pastFixings = arguments_.pastFixings; |
33 | 0 | Size futureFixings = arguments_.fixingDates.size(); |
34 | 0 | Real accruedAverage = 0; |
35 | 0 | if (pastFixings != 0) { |
36 | 0 | accruedAverage = arguments_.runningAccumulator / (pastFixings + futureFixings); |
37 | 0 | } |
38 | 0 | results_.additionalResults["accrued"] = accruedAverage; |
39 | |
|
40 | 0 | Real discount = process_->riskFreeRate()->discount(arguments_.exercise->lastDate()); |
41 | 0 | results_.additionalResults["discount"] = discount; |
42 | |
|
43 | 0 | ext::shared_ptr<PlainVanillaPayoff> payoff = |
44 | 0 | ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff); |
45 | 0 | QL_REQUIRE(payoff, "non-plain payoff given"); |
46 | | |
47 | | // We will read the volatility off the surface at the effective strike |
48 | 0 | Real effectiveStrike = payoff->strike() - accruedAverage; |
49 | 0 | results_.additionalResults["strike"] = payoff->strike(); |
50 | 0 | results_.additionalResults["effective_strike"] = effectiveStrike; |
51 | | |
52 | | // If the effective strike is negative, exercise resp. permanent OTM is guaranteed and the |
53 | | // valuation is made easy |
54 | 0 | Size m = futureFixings + pastFixings; |
55 | 0 | if (effectiveStrike <= 0.0) { |
56 | | // For a reference, see "Option Pricing Formulas", Haug, 2nd ed, p. 193 |
57 | 0 | if (payoff->optionType() == Option::Type::Call) { |
58 | 0 | Real spot = process_->stateVariable()->value(); |
59 | 0 | Real S_A_hat = accruedAverage; |
60 | 0 | for (const auto& fd : arguments_.fixingDates) { |
61 | 0 | S_A_hat += (spot * process_->dividendYield()->discount(fd) / |
62 | 0 | process_->riskFreeRate()->discount(fd)) / |
63 | 0 | m; |
64 | 0 | } |
65 | 0 | results_.value = discount * (S_A_hat - payoff->strike()); |
66 | 0 | results_.delta = discount * (S_A_hat - accruedAverage) / spot; |
67 | 0 | } else if (payoff->optionType() == Option::Type::Put) { |
68 | 0 | results_.value = 0; |
69 | 0 | results_.delta = 0; |
70 | 0 | } |
71 | 0 | results_.gamma = 0; |
72 | 0 | return; |
73 | 0 | } |
74 | | |
75 | | // We should only get this far when the effectiveStrike > 0 but will check anyway |
76 | 0 | QL_REQUIRE(effectiveStrike > 0.0, "expected effectiveStrike to be positive"); |
77 | | |
78 | | // Expected value of the non-accrued portion of the average prices |
79 | | // In general, m will equal n below if there is no accrued. If accrued, m > n. |
80 | 0 | Real EA = 0.0; |
81 | 0 | std::vector<Real> forwards; |
82 | 0 | std::vector<Time> times; |
83 | 0 | std::vector<Real> spotVars; |
84 | 0 | std::vector<Real> spotVolsVec; // additional results only |
85 | 0 | Real spot = process_->stateVariable()->value(); |
86 | |
|
87 | 0 | for (const auto& fd : arguments_.fixingDates) { |
88 | 0 | DiscountFactor dividendDiscount = process_->dividendYield()->discount(fd); |
89 | 0 | DiscountFactor riskFreeDiscountForFwdEstimation = process_->riskFreeRate()->discount(fd); |
90 | |
|
91 | 0 | forwards.push_back(spot * dividendDiscount / riskFreeDiscountForFwdEstimation); |
92 | 0 | times.push_back(process_->blackVolatility()->timeFromReference(fd)); |
93 | |
|
94 | 0 | spotVars.push_back( |
95 | 0 | process_->blackVolatility()->blackVariance(times.back(), effectiveStrike)); |
96 | 0 | spotVolsVec.push_back(std::sqrt(spotVars.back() / times.back())); |
97 | |
|
98 | 0 | EA += forwards.back(); |
99 | 0 | } |
100 | 0 | EA /= m; |
101 | | |
102 | | // Expected value of A^2. |
103 | 0 | Real EA2 = 0.0; |
104 | 0 | Size n = forwards.size(); |
105 | |
|
106 | 0 | for (Size i = 0; i < n; ++i) { |
107 | 0 | EA2 += forwards[i] * forwards[i] * exp(spotVars[i]); |
108 | 0 | for (Size j = 0; j < i; ++j) { |
109 | 0 | EA2 += 2 * forwards[i] * forwards[j] * exp(spotVars[j]); |
110 | 0 | } |
111 | 0 | } |
112 | |
|
113 | 0 | EA2 /= m * m; |
114 | | |
115 | | // Calculate value |
116 | 0 | Real tn = times.back(); |
117 | 0 | Real sigma = sqrt(log(EA2 / (EA * EA)) / tn); |
118 | | |
119 | | // Populate results |
120 | 0 | BlackCalculator black(payoff->optionType(), effectiveStrike, EA, sigma * sqrt(tn), discount); |
121 | |
|
122 | 0 | results_.value = black.value(); |
123 | 0 | results_.delta = black.delta(spot); |
124 | 0 | results_.gamma = black.gamma(spot); |
125 | |
|
126 | 0 | results_.additionalResults["forward"] = EA; |
127 | 0 | results_.additionalResults["exp_A_2"] = EA2; |
128 | 0 | results_.additionalResults["tte"] = tn; |
129 | 0 | results_.additionalResults["sigma"] = sigma; |
130 | 0 | results_.additionalResults["times"] = times; |
131 | 0 | results_.additionalResults["spotVols"] = spotVolsVec; |
132 | 0 | results_.additionalResults["forwards"] = forwards; |
133 | 0 | } |