Coverage Report

Created: 2025-12-08 06:13

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