Coverage Report

Created: 2025-08-28 06:30

/src/quantlib/ql/pricingengines/exotic/analyticcompoundoptionengine.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) 2009 Dimitri Reiswich
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
 <http://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/exotic/analyticcompoundoptionengine.hpp>
21
#include <ql/math/solvers1d/brent.hpp>
22
#include <ql/pricingengines/blackformula.hpp>
23
#include <utility>
24
25
namespace QuantLib {
26
27
    namespace {
28
29
        // Helper Class needed to solve an implicit problem of finding a
30
        // spot to a corresponding option price.
31
        class ImpliedSpotHelper {
32
          public:
33
            ImpliedSpotHelper(DiscountFactor dividendDiscount,
34
                              DiscountFactor riskFreeDiscount,
35
                              Real standardDeviation,
36
                              ext::shared_ptr<PlainVanillaPayoff> payoff,
37
                              Real strike)
38
0
            : dividendDiscount_(dividendDiscount), riskFreeDiscount_(riskFreeDiscount),
39
0
              standardDeviation_(standardDeviation), strike_(strike), payoff_(std::move(payoff)) {}
40
0
            Real operator()(Real spot) const {
41
0
                Real forwardPrice = spot*dividendDiscount_/riskFreeDiscount_;
42
0
                Real value = blackFormula(payoff_, forwardPrice,
43
0
                                          standardDeviation_,riskFreeDiscount_);
44
0
                return value - strike_;
45
0
            }
46
          private:
47
            DiscountFactor dividendDiscount_;
48
            DiscountFactor riskFreeDiscount_;
49
            Real standardDeviation_;
50
            Real strike_;
51
            ext::shared_ptr<PlainVanillaPayoff> payoff_;
52
        };
53
54
    }
55
56
    AnalyticCompoundOptionEngine::AnalyticCompoundOptionEngine(
57
        ext::shared_ptr<GeneralizedBlackScholesProcess> process)
58
0
    : process_(std::move(process)) {
59
0
        registerWith(process_);
60
0
    }
61
62
0
    void AnalyticCompoundOptionEngine::calculate() const {
63
64
0
        QL_REQUIRE(strikeDaughter()>0.0,
65
0
                   "Daughter strike must be positive");
66
67
0
        QL_REQUIRE(strikeMother()>0.0,
68
0
                   "Mother strike must be positive");
69
70
0
        QL_REQUIRE(spot() > 0.0, "negative or null underlying given");
71
72
        /* Solver Setup ***************************************************/
73
0
        Date helpDate(process_->riskFreeRate()->referenceDate());
74
0
        Date helpMaturity=helpDate+(maturityDaughter()-maturityMother())*Days;
75
0
        Real vol =process_->blackVolatility()->blackVol(helpMaturity,
76
0
                                                        strikeDaughter());
77
78
0
        Time helpTimeToMat=process_->time(helpMaturity);
79
0
        vol=vol*std::sqrt(helpTimeToMat);
80
81
0
        DiscountFactor dividendDiscount =
82
0
            process_->dividendYield()->discount(helpMaturity);
83
84
0
        DiscountFactor riskFreeDiscount =
85
0
            process_->riskFreeRate()->discount(helpMaturity);
86
87
88
0
        ext::shared_ptr<ImpliedSpotHelper> f(
89
0
                new ImpliedSpotHelper(dividendDiscount, riskFreeDiscount,
90
0
                                      vol, payoffDaughter(), strikeMother()));
91
92
0
        Brent solver;
93
0
        solver.setMaxEvaluations(1000);
94
0
        Real accuracy = 1.0e-6;
95
96
0
        Real sSolved=solver.solve(*f, accuracy, strikeDaughter(), 1.0e-6, strikeDaughter()*1000.0);
97
0
        Real X=transformX(sSolved); // transform stock to return as in Wystup's book
98
        /* Solver Setup Finished*****************************************/
99
100
0
        Real phi=typeDaughter(); // -1 or 1
101
0
        Real w=typeMother(); // -1 or 1
102
103
0
        Real rho=std::sqrt(residualTimeMother()/residualTimeDaughter());
104
0
        BivariateCumulativeNormalDistributionDr78 N2(w*rho) ;
105
106
0
        DiscountFactor ddD=dividendDiscountDaughter();
107
0
        DiscountFactor rdD=riskFreeDiscountDaughter();
108
        //DiscountFactor ddM=dividendDiscountMother();
109
0
        DiscountFactor rdM=riskFreeDiscountMother();
110
111
0
        Real XmSM=X-stdDeviationMother();
112
0
        Real S=spot();
113
0
        Real dP=dPlus();
114
0
        Real dPT12=dPlusTau12(sSolved);
115
0
        Real vD=volatilityDaughter();
116
117
0
        Real dM=dMinus();
118
0
        Real strD=strikeDaughter();
119
0
        Real strM=strikeMother();
120
0
        Real rTM=residualTimeMother();
121
0
        Real rTD=residualTimeDaughter();
122
123
0
        Real rD=riskFreeRateDaughter();
124
0
        Real dD=dividendRateDaughter();
125
126
0
        Real N2XmSM=N2(-phi*w*XmSM,phi*dP);
127
0
        Real N2X=N2(-phi*w*X,phi*dM);
128
0
        Real NeX=N_(-phi*w*e(X));
129
0
        Real NX=N_(-phi*w*X);
130
0
        Real NT12=N_(phi*dPT12);
131
0
        Real ndP=n_(dP);
132
0
        Real nXm=n_(XmSM);
133
0
        Real invMTime=1/std::sqrt(rTM);
134
0
        Real invDTime=1/std::sqrt(rTD);
135
136
0
        Real tempRes=phi*w*S*ddD*N2XmSM-phi*w*strD*rdD*N2X-w*strM*rdM*NX;
137
0
        Real tempDelta=phi*w*ddD*N2XmSM;
138
0
        Real tempGamma=(ddD/(vD*S))*(invMTime*nXm*NT12+w*invDTime*ndP*NeX);
139
0
        Real tempVega=ddD*S*((1/invMTime)*nXm*NT12+w*(1/invDTime)*ndP*NeX);
140
0
        Real tempTheta=phi*w*dD*S*ddD*N2XmSM-phi*w*rD*strD*rdD*N2X-w*rD*strM*rdM*NX;
141
0
        tempTheta-=0.5*vD*S*ddD*(invMTime*nXm*NT12+w*invDTime*ndP*NeX);
142
143
0
        results_.value=tempRes;
144
0
        results_.delta=tempDelta;
145
0
        results_.gamma=tempGamma;
146
0
        results_.vega=tempVega;
147
0
        results_.theta=tempTheta;
148
0
    }
149
150
0
    Real AnalyticCompoundOptionEngine::typeDaughter() const {
151
        // returns -1 or 1 according to put or call
152
0
        return (Real) payoffDaughter()->optionType();
153
0
    }
154
155
0
    Real AnalyticCompoundOptionEngine::typeMother() const {
156
0
        return (Real) payoffMother()->optionType();
157
0
    }
158
159
0
    Date AnalyticCompoundOptionEngine::maturityDaughter() const {
160
0
        return arguments_.daughterExercise->lastDate();
161
0
    }
162
163
0
    Date AnalyticCompoundOptionEngine::maturityMother() const {
164
0
        return arguments_.exercise->lastDate();
165
0
    }
166
167
0
    Time AnalyticCompoundOptionEngine::residualTimeDaughter() const {
168
0
        return process_->time(maturityDaughter());
169
0
    }
170
171
0
    Time AnalyticCompoundOptionEngine::residualTimeMother() const {
172
0
        return process_->time(maturityMother());
173
0
    }
174
175
0
    Time AnalyticCompoundOptionEngine::residualTimeMotherDaughter() const {
176
0
        return residualTimeDaughter()-residualTimeMother();
177
0
    }
178
179
180
0
    Real AnalyticCompoundOptionEngine::volatilityDaughter() const {
181
0
        return process_->blackVolatility()->blackVol(maturityDaughter(),
182
0
                                                     strikeDaughter());
183
0
    }
184
185
186
0
    Real AnalyticCompoundOptionEngine::volatilityMother() const {
187
0
        return process_->blackVolatility()->blackVol(maturityMother(),
188
0
                                                     strikeMother());
189
0
    }
190
191
0
    Real AnalyticCompoundOptionEngine::stdDeviationDaughter() const {
192
0
        return volatilityDaughter()*std::sqrt(residualTimeDaughter());
193
0
    }
194
195
0
    Real AnalyticCompoundOptionEngine::stdDeviationMother() const {
196
0
        return volatilityMother()*std::sqrt(residualTimeMother());
197
0
    }
198
199
200
    ext::shared_ptr<PlainVanillaPayoff>
201
0
    AnalyticCompoundOptionEngine::payoffDaughter() const {
202
0
        ext::shared_ptr<PlainVanillaPayoff> dPayoff =
203
0
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(
204
0
                                                   arguments_.daughterPayoff);
205
0
        QL_REQUIRE(dPayoff, "non-plain payoff given");
206
0
        return dPayoff;
207
0
    }
208
209
    ext::shared_ptr<PlainVanillaPayoff>
210
0
    AnalyticCompoundOptionEngine::payoffMother() const {
211
0
        ext::shared_ptr<PlainVanillaPayoff> mPayoff =
212
0
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
213
0
        QL_REQUIRE(mPayoff, "non-plain payoff given");
214
0
        return mPayoff;
215
0
    }
216
217
0
    Real AnalyticCompoundOptionEngine::strikeMother() const {
218
0
        return payoffMother()->strike();
219
0
    }
220
221
0
    Real AnalyticCompoundOptionEngine::strikeDaughter() const {
222
0
        return payoffDaughter()->strike();
223
0
    }
224
225
0
    DiscountFactor AnalyticCompoundOptionEngine::riskFreeDiscountDaughter() const {
226
0
        return process_->riskFreeRate()->discount(residualTimeDaughter());
227
0
    }
228
229
0
    DiscountFactor AnalyticCompoundOptionEngine::riskFreeDiscountMother() const {
230
0
        return process_->riskFreeRate()->discount(residualTimeMother());
231
0
    }
232
233
0
    DiscountFactor AnalyticCompoundOptionEngine::riskFreeDiscountMotherDaughter() const {
234
0
        return process_->riskFreeRate()->discount(residualTimeMotherDaughter());
235
0
    }
236
237
0
    DiscountFactor AnalyticCompoundOptionEngine::dividendDiscountDaughter() const {
238
0
        return process_->dividendYield()->discount(residualTimeDaughter());
239
0
    }
240
241
0
    DiscountFactor AnalyticCompoundOptionEngine::dividendDiscountMother() const {
242
0
        return process_->dividendYield()->discount(residualTimeMother());
243
0
    }
244
245
0
    DiscountFactor AnalyticCompoundOptionEngine::dividendDiscountMotherDaughter() const {
246
0
        return process_->dividendYield()->discount(residualTimeMotherDaughter());
247
0
    }
248
249
0
    Real AnalyticCompoundOptionEngine::dPlus() const {
250
0
        Real forward = spot() * dividendDiscountDaughter() / riskFreeDiscountDaughter();
251
0
        Real sd=stdDeviationDaughter();
252
0
        return std::log(forward/strikeDaughter())/sd+0.5*sd;
253
0
    }
254
255
0
    Real AnalyticCompoundOptionEngine::dMinus() const {
256
0
        return dPlus()-stdDeviationDaughter();
257
0
    }
258
259
0
    Real AnalyticCompoundOptionEngine::dPlusTau12(Real S) const {
260
0
        Real forward = S * dividendDiscountMotherDaughter() / riskFreeDiscountMotherDaughter();
261
0
        Real sd=volatilityDaughter()*std::sqrt(residualTimeMotherDaughter());
262
0
        return std::log(forward/strikeDaughter())/sd+0.5*sd;
263
0
    }
264
265
0
    Real AnalyticCompoundOptionEngine::spot() const {
266
0
        return process_->x0();
267
0
    }
268
269
0
    Real AnalyticCompoundOptionEngine::riskFreeRateDaughter() const {
270
0
        return process_->riskFreeRate()->zeroRate(residualTimeDaughter(),
271
0
                                                  Continuous,
272
0
                                                  NoFrequency);
273
0
    }
274
275
0
    Real AnalyticCompoundOptionEngine::dividendRateDaughter() const {
276
0
        return process_->dividendYield()->zeroRate(residualTimeDaughter(),
277
0
                                                   Continuous,
278
0
                                                   NoFrequency);
279
0
    }
280
281
0
    Real AnalyticCompoundOptionEngine::transformX(Real X) const {
282
283
0
        Real sd=stdDeviationMother();
284
0
        Real resX=riskFreeDiscountMother()*X/(spot()*dividendDiscountMother());
285
0
        resX=resX*std::exp(0.5*sd*sd);
286
0
        resX=std::log(resX);
287
288
0
        return resX/sd;
289
0
    }
290
291
0
    Real AnalyticCompoundOptionEngine::e(Real X) const {
292
0
        Real rtM=residualTimeMother();
293
0
        Real rtD=residualTimeDaughter();
294
295
0
        return (X*std::sqrt(rtD)+std::sqrt(rtM)*dMinus())/std::sqrt(rtD-rtM);
296
0
    }
297
298
}