Coverage Report

Created: 2025-08-05 06:45

/src/quantlib/ql/pricingengines/vanilla/juquadraticengine.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2004 Neil Firth
5
 Copyright (C) 2007 StatPro Italia srl
6
 Copyright (C) 2013 Fabien Le Floc'h
7
8
 This file is part of QuantLib, a free-software/open-source library
9
 for financial quantitative analysts and developers - http://quantlib.org/
10
11
 QuantLib is free software: you can redistribute it and/or modify it
12
 under the terms of the QuantLib license.  You should have received a
13
 copy of the license along with this program; if not, please email
14
 <quantlib-dev@lists.sf.net>. The license is also available online at
15
 <http://quantlib.org/license.shtml>.
16
17
 This program is distributed in the hope that it will be useful, but WITHOUT
18
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 FOR A PARTICULAR PURPOSE.  See the license for more details.
20
*/
21
22
#include <ql/exercise.hpp>
23
#include <ql/math/distributions/normaldistribution.hpp>
24
#include <ql/pricingengines/blackcalculator.hpp>
25
#include <ql/pricingengines/blackformula.hpp>
26
#include <ql/pricingengines/vanilla/baroneadesiwhaleyengine.hpp>
27
#include <ql/pricingengines/vanilla/juquadraticengine.hpp>
28
#include <utility>
29
30
namespace QuantLib {
31
32
    /*  An Approximate Formula for Pricing American Options
33
        Journal of Derivatives Winter 1999
34
        Ju, N.
35
    */
36
37
38
    JuQuadraticApproximationEngine::JuQuadraticApproximationEngine(
39
        ext::shared_ptr<GeneralizedBlackScholesProcess> process)
40
0
    : process_(std::move(process)) {
41
0
        registerWith(process_);
42
0
    }
43
44
0
    void JuQuadraticApproximationEngine::calculate() const {
45
46
0
        QL_REQUIRE(arguments_.exercise->type() == Exercise::American,
47
0
                   "not an American Option");
48
49
0
        ext::shared_ptr<AmericanExercise> ex =
50
0
            ext::dynamic_pointer_cast<AmericanExercise>(arguments_.exercise);
51
0
        QL_REQUIRE(ex, "non-American exercise given");
52
0
        QL_REQUIRE(!ex->payoffAtExpiry(),
53
0
                   "payoff at expiry not handled");
54
55
0
        ext::shared_ptr<StrikedTypePayoff> payoff =
56
0
            ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
57
0
        QL_REQUIRE(payoff, "non-striked payoff given");
58
59
0
        Real variance = process_->blackVolatility()->blackVariance(
60
0
            ex->lastDate(), payoff->strike());
61
0
        DiscountFactor dividendDiscount = process_->dividendYield()->discount(
62
0
            ex->lastDate());
63
0
        DiscountFactor riskFreeDiscount = process_->riskFreeRate()->discount(
64
0
            ex->lastDate());
65
0
        Real spot = process_->stateVariable()->value();
66
0
        QL_REQUIRE(spot > 0.0, "negative or null underlying given");
67
0
        Real forwardPrice = spot * dividendDiscount / riskFreeDiscount;
68
0
        BlackCalculator black(payoff, forwardPrice,
69
0
                              std::sqrt(variance), riskFreeDiscount);
70
71
0
        if (dividendDiscount>=1.0 && payoff->optionType()==Option::Call) {
72
            // early exercise never optimal
73
0
            results_.value        = black.value();
74
0
            results_.delta        = black.delta(spot);
75
0
            results_.deltaForward = black.deltaForward();
76
0
            results_.elasticity   = black.elasticity(spot);
77
0
            results_.gamma        = black.gamma(spot);
78
79
0
            DayCounter rfdc  = process_->riskFreeRate()->dayCounter();
80
0
            DayCounter divdc = process_->dividendYield()->dayCounter();
81
0
            DayCounter voldc = process_->blackVolatility()->dayCounter();
82
0
            Time t =
83
0
                rfdc.yearFraction(process_->riskFreeRate()->referenceDate(),
84
0
                                  arguments_.exercise->lastDate());
85
0
            results_.rho = black.rho(t);
86
87
0
            t = divdc.yearFraction(process_->dividendYield()->referenceDate(),
88
0
                                   arguments_.exercise->lastDate());
89
0
            results_.dividendRho = black.dividendRho(t);
90
91
0
            t = voldc.yearFraction(process_->blackVolatility()->referenceDate(),
92
0
                                   arguments_.exercise->lastDate());
93
0
            results_.vega        = black.vega(t);
94
0
            results_.theta       = black.theta(spot, t);
95
0
            results_.thetaPerDay = black.thetaPerDay(spot, t);
96
97
0
            results_.strikeSensitivity  = black.strikeSensitivity();
98
0
            results_.itmCashProbability = black.itmCashProbability();
99
0
        } else {
100
            // early exercise can be optimal
101
0
            CumulativeNormalDistribution cumNormalDist;
102
0
            NormalDistribution normalDist;
103
104
0
            Real tolerance = 1e-6;
105
0
            Real Sk = BaroneAdesiWhaleyApproximationEngine::criticalPrice(
106
0
                payoff, riskFreeDiscount, dividendDiscount, variance,
107
0
                tolerance);
108
109
0
            Real forwardSk = Sk * dividendDiscount / riskFreeDiscount;
110
111
0
            Real alpha = -2.0*std::log(riskFreeDiscount)/(variance);
112
0
            Real beta = 2.0*std::log(dividendDiscount/riskFreeDiscount)/
113
0
                                                (variance);
114
0
            Real h = 1 - riskFreeDiscount;
115
0
            Real phi;
116
0
            switch (payoff->optionType()) {
117
0
                case Option::Call:
118
0
                    phi = 1;
119
0
                    break;
120
0
                case Option::Put:
121
0
                    phi = -1;
122
0
                    break;
123
0
                default:
124
0
                  QL_FAIL("unknown option type");
125
0
            }
126
            //it can throw: to be fixed
127
0
            Real temp_root = std::sqrt ((beta-1)*(beta-1) + (4*alpha)/h);
128
0
            Real lambda = (-(beta-1) + phi * temp_root) / 2;
129
0
            Real lambda_prime = - phi * alpha / (h*h * temp_root);
130
131
0
            Real black_Sk = blackFormula(payoff->optionType(), payoff->strike(),
132
0
                                         forwardSk, std::sqrt(variance)) * riskFreeDiscount;
133
0
            Real hA = phi * (Sk - payoff->strike()) - black_Sk;
134
135
0
            Real d1_Sk = (std::log(forwardSk/payoff->strike()) + 0.5*variance)
136
0
                /std::sqrt(variance);
137
0
            Real d2_Sk = d1_Sk - std::sqrt(variance);
138
0
            Real part1 = forwardSk * normalDist(d1_Sk) /
139
0
                                        (alpha * std::sqrt(variance));
140
0
            Real part2 = - phi * forwardSk * cumNormalDist(phi * d1_Sk) *
141
0
                      std::log(dividendDiscount) / std::log(riskFreeDiscount);
142
0
            Real part3 = + phi * payoff->strike() * cumNormalDist(phi * d2_Sk);
143
0
            Real V_E_h = part1 + part2 + part3;
144
145
0
            Real b = (1-h) * alpha * lambda_prime / (2*(2*lambda + beta - 1));
146
0
            Real c = - ((1 - h) * alpha / (2 * lambda + beta - 1)) *
147
0
                (V_E_h / (hA) + 1 / h + lambda_prime / (2*lambda + beta - 1));
148
0
            Real temp_spot_ratio = std::log(spot / Sk);
149
0
            Real chi = temp_spot_ratio * (b * temp_spot_ratio + c);
150
151
0
            if (phi*(Sk-spot) > 0) {
152
0
                results_.value = black.value() +
153
0
                    hA * std::pow((spot/Sk), lambda) / (1 - chi);
154
0
                Real temp_chi_prime = (2 * b / spot) * std::log(spot/Sk);
155
0
                Real chi_prime = temp_chi_prime + c / spot;
156
0
                Real chi_double_prime = 2*b/(spot*spot)
157
0
                    - temp_chi_prime / spot - c / (spot*spot);
158
0
                Real d1_S = (std::log(forwardPrice/payoff->strike()) + 0.5*variance)
159
0
                    / std::sqrt(variance);
160
                //There is a typo in the original paper from Ju-Zhong
161
                //the first term is the Black-Scholes delta/gamma.    
162
0
                results_.delta = phi * dividendDiscount * cumNormalDist (phi * d1_S)
163
0
                    + (lambda / (spot * (1 - chi)) + chi_prime / ((1 - chi)*(1 - chi))) *
164
0
                    (phi * (Sk - payoff->strike()) - black_Sk) * std::pow((spot/Sk), lambda);
165
166
0
                results_.gamma = dividendDiscount * normalDist (phi*d1_S) 
167
0
                    / (spot * std::sqrt(variance))
168
0
                    + (2 * lambda * chi_prime / (spot * (1 - chi) * (1 - chi))
169
0
                        + 2 * chi_prime * chi_prime / ((1 - chi) * (1 - chi) * (1 - chi))
170
0
                        + chi_double_prime / ((1 - chi) * (1 - chi))
171
0
                        + lambda * (lambda - 1) / (spot * spot * (1 - chi)))
172
0
                    * (phi * (Sk - payoff->strike()) - black_Sk)
173
0
                    * std::pow((spot/Sk), lambda);
174
0
            } else {
175
0
                results_.value = phi * (spot - payoff->strike());
176
0
                results_.delta = phi;
177
0
                results_.gamma = 0;
178
0
            }
179
180
0
        } // end of "early exercise can be optimal"
181
0
    }
182
183
}