/src/quantlib/ql/pricingengines/basket/pearsonspreadengine.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2026 Yassine Idyiahia |
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/pricingengines/basket/pearsonspreadengine.hpp> |
21 | | #include <ql/pricingengines/blackcalculator.hpp> |
22 | | #include <ql/math/distributions/normaldistribution.hpp> |
23 | | #include <ql/math/integrals/gausslobattointegral.hpp> |
24 | | #include <utility> |
25 | | |
26 | | namespace QuantLib { |
27 | | |
28 | | PearsonSpreadEngine::PearsonSpreadEngine( |
29 | | ext::shared_ptr<GeneralizedBlackScholesProcess> process1, |
30 | | ext::shared_ptr<GeneralizedBlackScholesProcess> process2, |
31 | | Real correlation, |
32 | | Real integrationTolerance, |
33 | | Size maxIntegrationIterations, |
34 | | Real nStd) |
35 | 0 | : SpreadBlackScholesVanillaEngine( |
36 | 0 | std::move(process1), std::move(process2), correlation), |
37 | 0 | integrationTolerance_(integrationTolerance), |
38 | 0 | maxIntegrationIterations_(maxIntegrationIterations), |
39 | 0 | nStd_(nStd) { |
40 | 0 | } |
41 | | |
42 | | Real PearsonSpreadEngine::calculate( |
43 | | Real f1, Real f2, Real strike, Option::Type optionType, |
44 | 0 | Real variance1, Real variance2, DiscountFactor df) const { |
45 | |
|
46 | 0 | const Real sigma1 = std::sqrt(variance1); |
47 | 0 | const Real sigma2 = std::sqrt(variance2); |
48 | | |
49 | | // Under the forward measure, the joint dynamics are: |
50 | | // ln F1 ~ N(mu1, sigma1^2), ln F2 ~ N(mu2, sigma2^2) |
51 | | // with correlation rho_ |
52 | | // |
53 | | // Condition on z = standard normal driving F2: |
54 | | // F2(z) = f2 * exp(-0.5*sigma2^2 + sigma2*z) |
55 | | // |
56 | | // Conditional on z, F1 is log-normal with: |
57 | | // conditional mean of ln F1: mu1_cond = ln(f1) - 0.5*sigma1^2 + rho_*sigma1*z |
58 | | // conditional variance: sigma1_cond^2 = sigma1^2*(1 - rho_^2) |
59 | | // |
60 | | // The conditional spread option value is a Black call/put: |
61 | | // BS(F1_cond, K + F2(z), sigma1_cond) |
62 | |
|
63 | 0 | const Real sigma1Cond = sigma1 * std::sqrt(std::max(1.0 - rho_ * rho_, 0.0)); |
64 | |
|
65 | 0 | const NormalDistribution phi; |
66 | | |
67 | | // Integrate over the standard normal variable z |
68 | | // that drives F2. Truncate at +/- 8 standard deviations. |
69 | 0 | auto integrand = [&](Real z) -> Real { |
70 | 0 | const Real f2z = f2 * std::exp(-0.5 * variance2 + sigma2 * z); |
71 | 0 | const Real effectiveStrike = f2z + strike; |
72 | |
|
73 | 0 | if (effectiveStrike <= 0.0) |
74 | 0 | return phi(z) * std::max(0.0, f1 * std::exp(rho_ * sigma1 * z |
75 | 0 | - 0.5 * rho_ * rho_ * variance1) - effectiveStrike); |
76 | | |
77 | 0 | const Real f1Cond = f1 * std::exp(rho_ * sigma1 * z |
78 | 0 | - 0.5 * rho_ * rho_ * variance1); |
79 | |
|
80 | 0 | BlackCalculator black( |
81 | 0 | ext::make_shared<PlainVanillaPayoff>(optionType, effectiveStrike), |
82 | 0 | f1Cond, sigma1Cond, 1.0); |
83 | |
|
84 | 0 | return phi(z) * black.value(); |
85 | 0 | }; |
86 | |
|
87 | 0 | GaussLobattoIntegral integrator(maxIntegrationIterations_, |
88 | 0 | integrationTolerance_); |
89 | 0 | const Real undiscounted = integrator(integrand, -nStd_, nStd_); |
90 | |
|
91 | 0 | return df * undiscounted; |
92 | 0 | } |
93 | | } |