Coverage Report

Created: 2025-08-11 06:28

/src/quantlib/ql/experimental/barrieroption/vannavolgabarrierengine.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) 2013 Yue Tian
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/experimental/barrieroption/vannavolgabarrierengine.hpp>
21
#include <ql/experimental/barrieroption/vannavolgainterpolation.hpp>
22
#include <ql/experimental/fx/blackdeltacalculator.hpp>
23
#include <ql/math/matrix.hpp>
24
#include <ql/pricingengines/barrier/analyticbarrierengine.hpp>
25
#include <ql/pricingengines/blackformula.hpp>
26
#include <ql/quotes/simplequote.hpp>
27
#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
28
#include <ql/time/calendars/nullcalendar.hpp>
29
#include <utility>
30
31
using std::pow;
32
using std::log;
33
using std::sqrt;
34
35
namespace QuantLib {
36
37
    VannaVolgaBarrierEngine::VannaVolgaBarrierEngine(Handle<DeltaVolQuote> atmVol,
38
                                                     Handle<DeltaVolQuote> vol25Put,
39
                                                     Handle<DeltaVolQuote> vol25Call,
40
                                                     Handle<Quote> spotFX,
41
                                                     Handle<YieldTermStructure> domesticTS,
42
                                                     Handle<YieldTermStructure> foreignTS,
43
                                                     const bool adaptVanDelta,
44
                                                     const Real bsPriceWithSmile)
45
0
    : atmVol_(std::move(atmVol)), vol25Put_(std::move(vol25Put)), vol25Call_(std::move(vol25Call)),
46
0
      T_(atmVol_->maturity()), spotFX_(std::move(spotFX)), domesticTS_(std::move(domesticTS)),
47
0
      foreignTS_(std::move(foreignTS)), adaptVanDelta_(adaptVanDelta),
48
0
      bsPriceWithSmile_(bsPriceWithSmile) {
49
0
        QL_REQUIRE(vol25Put_->delta() == -0.25, "25 delta put is required by vanna volga method");
50
0
        QL_REQUIRE(vol25Call_->delta() == 0.25, "25 delta call is required by vanna volga method");
51
52
0
        QL_REQUIRE(vol25Put_->maturity() == vol25Call_->maturity() &&
53
0
                       vol25Put_->maturity() == atmVol_->maturity(),
54
0
                   "Maturity of 3 vols are not the same");
55
56
0
        QL_REQUIRE(!domesticTS_.empty(), "domestic yield curve is not defined");
57
0
        QL_REQUIRE(!foreignTS_.empty(), "foreign yield curve is not defined");
58
59
0
        registerWith(atmVol_);
60
0
        registerWith(vol25Put_);
61
0
        registerWith(vol25Call_);
62
0
        registerWith(spotFX_);
63
0
        registerWith(domesticTS_);
64
0
        registerWith(foreignTS_);
65
0
    }
66
67
0
    void VannaVolgaBarrierEngine::calculate() const {
68
69
0
        QL_REQUIRE(arguments_.barrierType == Barrier::UpIn || arguments_.barrierType == Barrier::UpOut ||
70
0
            arguments_.barrierType == Barrier::DownIn || arguments_.barrierType == Barrier::DownOut,
71
0
            "Invalid barrier type");
72
73
0
        const Real sigmaShift_vega = 0.0001;
74
0
        const Real sigmaShift_volga = 0.0001;
75
0
        const Real spotShift_delta = 0.0001 * spotFX_->value();
76
0
        const Real sigmaShift_vanna = 0.0001;
77
78
0
        Handle<Quote> x0Quote(
79
0
            ext::make_shared<SimpleQuote>(spotFX_->value())); //used for shift
80
0
        Handle<Quote> atmVolQuote(
81
0
            ext::make_shared<SimpleQuote>(atmVol_->value())); //used for shift
82
83
0
        ext::shared_ptr<BlackVolTermStructure> blackVolTS =
84
0
            ext::make_shared<BlackConstantVol>(
85
0
                Settings::instance().evaluationDate(),
86
0
                NullCalendar(), atmVolQuote, Actual365Fixed());
87
0
        ext::shared_ptr<BlackScholesMertonProcess> stochProcess =
88
0
            ext::make_shared<BlackScholesMertonProcess>(
89
0
                                 x0Quote,
90
0
                                 foreignTS_,
91
0
                                 domesticTS_,
92
0
                                 Handle<BlackVolTermStructure>(blackVolTS));
93
94
0
        ext::shared_ptr<PricingEngine> engineBS =
95
0
            ext::make_shared<AnalyticBarrierEngine>(stochProcess);
96
97
0
        BlackDeltaCalculator blackDeltaCalculatorAtm(
98
0
                        Option::Call, atmVol_->deltaType(), x0Quote->value(),
99
0
                        domesticTS_->discount(T_), foreignTS_->discount(T_),
100
0
                        atmVol_->value() * sqrt(T_));
101
0
        Real atmStrike = blackDeltaCalculatorAtm.atmStrike(atmVol_->atmType());
102
103
0
        Real call25Vol = vol25Call_->value();
104
0
        Real put25Vol = vol25Put_->value();
105
106
0
        BlackDeltaCalculator blackDeltaCalculatorPut25(Option::Put, vol25Put_->deltaType(), x0Quote->value(), 
107
0
                                                      domesticTS_->discount(T_), foreignTS_->discount(T_),
108
0
                                                      put25Vol * sqrt(T_));
109
0
        Real put25Strike = blackDeltaCalculatorPut25.strikeFromDelta(-0.25);
110
0
        BlackDeltaCalculator blackDeltaCalculatorCall25(Option::Call, vol25Call_->deltaType(), x0Quote->value(), 
111
0
                                                      domesticTS_->discount(T_), foreignTS_->discount(T_),
112
0
                                                      call25Vol * sqrt(T_));
113
0
        Real call25Strike = blackDeltaCalculatorCall25.strikeFromDelta(0.25);
114
115
116
        //here use vanna volga interpolated smile to price vanilla
117
0
        std::vector<Real> strikes;
118
0
        std::vector<Real> vols;
119
0
        strikes.push_back(put25Strike);
120
0
        vols.push_back(put25Vol);
121
0
        strikes.push_back(atmStrike);
122
0
        vols.push_back(atmVol_->value());
123
0
        strikes.push_back(call25Strike);
124
0
        vols.push_back(call25Vol);
125
0
        VannaVolga vannaVolga(x0Quote->value(), domesticTS_->discount(T_), foreignTS_->discount(T_), T_);
126
0
        Interpolation interpolation = vannaVolga.interpolate(strikes.begin(), strikes.end(), vols.begin());
127
0
        interpolation.enableExtrapolation();
128
0
        const ext::shared_ptr<StrikedTypePayoff> payoff =
129
0
                                        ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
130
0
        Real strikeVol = interpolation(payoff->strike());
131
132
        //vanilla option price
133
0
        Real forward = x0Quote->value() * foreignTS_->discount(T_) / domesticTS_->discount(T_);
134
0
        Real vanillaOption = blackFormula(payoff->optionType(), payoff->strike(), 
135
0
                                      forward, 
136
0
                                      strikeVol * sqrt(T_),
137
0
                                      domesticTS_->discount(T_));
138
0
        results_.additionalResults["Forward"] = forward;
139
0
        results_.additionalResults["StrikeVol"] = strikeVol;
140
141
        //spot > barrier up&out 0
142
0
        if(x0Quote->value() >= arguments_.barrier && arguments_.barrierType == Barrier::UpOut){
143
0
            results_.value = 0.0;
144
0
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
145
0
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
146
0
            results_.additionalResults["BarrierOutPrice"] = Real(0.0);
147
0
        }
148
        //spot > barrier up&in vanilla
149
0
        else if(x0Quote->value() >= arguments_.barrier && arguments_.barrierType == Barrier::UpIn){
150
0
            results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
151
0
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
152
0
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
153
0
            results_.additionalResults["BarrierOutPrice"] = Real(0.0);
154
0
        }
155
        //spot < barrier down&out 0
156
0
        else if(x0Quote->value() <= arguments_.barrier && arguments_.barrierType == Barrier::DownOut){
157
0
            results_.value = 0.0;
158
0
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
159
0
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
160
0
            results_.additionalResults["BarrierOutPrice"] = Real(0.0);
161
0
        }
162
        //spot < barrier down&in vanilla
163
0
        else if(x0Quote->value() <= arguments_.barrier && arguments_.barrierType == Barrier::DownIn){
164
0
            results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
165
0
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
166
0
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
167
0
            results_.additionalResults["BarrierOutPrice"] = Real(0.0);
168
0
        }
169
0
        else{
170
171
            //set up BS barrier option pricing
172
            //only calculate out barrier option price
173
            // in barrier price = vanilla - out barrier
174
0
            Barrier::Type barrierType;
175
0
            if(arguments_.barrierType == Barrier::UpOut)
176
0
                barrierType = arguments_.barrierType;
177
0
            else if(arguments_.barrierType == Barrier::UpIn)
178
0
                barrierType = Barrier::UpOut;
179
0
            else if(arguments_.barrierType == Barrier::DownOut)
180
0
                barrierType = arguments_.barrierType;
181
0
            else
182
0
                barrierType = Barrier::DownOut;
183
184
0
            BarrierOption barrierOption(barrierType,
185
0
                                        arguments_.barrier,
186
0
                                        arguments_.rebate,
187
0
                                        ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff),
188
0
                                        arguments_.exercise);
189
190
0
            barrierOption.setPricingEngine(engineBS);
191
192
            //BS price with atm vol
193
0
            Real priceBS = barrierOption.NPV();
194
195
0
            Real priceAtmCallBS = blackFormula(Option::Call,atmStrike,
196
0
                                              forward, 
197
0
                                              atmVol_->value() * sqrt(T_),
198
0
                                              domesticTS_->discount(T_));
199
0
            Real price25CallBS = blackFormula(Option::Call,call25Strike,
200
0
                                              forward, 
201
0
                                              atmVol_->value() * sqrt(T_),
202
0
                                              domesticTS_->discount(T_));
203
0
            Real price25PutBS = blackFormula(Option::Put,put25Strike,
204
0
                                              forward,
205
0
                                              atmVol_->value() * sqrt(T_),
206
0
                                              domesticTS_->discount(T_));
207
208
            //market price
209
0
            Real priceAtmCallMkt = blackFormula(Option::Call,atmStrike,
210
0
                                              forward, 
211
0
                                              atmVol_->value() * sqrt(T_),
212
0
                                              domesticTS_->discount(T_));
213
214
0
            Real price25CallMkt = blackFormula(Option::Call,call25Strike,
215
0
                                              forward, 
216
0
                                              call25Vol * sqrt(T_),
217
0
                                              domesticTS_->discount(T_));
218
0
            Real price25PutMkt = blackFormula(Option::Put,put25Strike,
219
0
                                              forward,
220
0
                                              put25Vol * sqrt(T_),
221
0
                                              domesticTS_->discount(T_));
222
223
224
            //Analytical Black Scholes formula for vanilla option
225
0
            NormalDistribution norm;
226
0
            Real d1atm = (std::log(forward/atmStrike) 
227
0
                           + 0.5*std::pow(atmVolQuote->value(),2.0) * T_)/(atmVolQuote->value() * sqrt(T_));
228
0
            Real vegaAtm_Analytical = x0Quote->value() * norm(d1atm) * sqrt(T_) * foreignTS_->discount(T_);
229
0
            Real vannaAtm_Analytical = vegaAtm_Analytical/x0Quote->value() *(1.0 - d1atm/(atmVolQuote->value()*sqrt(T_)));
230
0
            Real volgaAtm_Analytical = vegaAtm_Analytical * d1atm * (d1atm - atmVolQuote->value() * sqrt(T_))/atmVolQuote->value();
231
232
0
            Real d125call = (std::log(forward/call25Strike) 
233
0
                           + 0.5*std::pow(atmVolQuote->value(),2.0) * T_)/(atmVolQuote->value() * sqrt(T_));
234
0
            Real vega25Call_Analytical = x0Quote->value() * norm(d125call) * sqrt(T_) * foreignTS_->discount(T_);
235
0
            Real vanna25Call_Analytical = vega25Call_Analytical/x0Quote->value() *(1.0 - d125call/(atmVolQuote->value()*sqrt(T_)));
236
0
            Real volga25Call_Analytical = vega25Call_Analytical * d125call * (d125call - atmVolQuote->value() * sqrt(T_))/atmVolQuote->value();
237
238
0
            Real d125Put = (std::log(forward/put25Strike) 
239
0
                           + 0.5*std::pow(atmVolQuote->value(),2.0) * T_)/(atmVolQuote->value() * sqrt(T_));
240
0
            Real vega25Put_Analytical = x0Quote->value() * norm(d125Put) * sqrt(T_) * foreignTS_->discount(T_);
241
0
            Real vanna25Put_Analytical = vega25Put_Analytical/x0Quote->value() *(1.0 - d125Put/(atmVolQuote->value()*sqrt(T_)));
242
0
            Real volga25Put_Analytical = vega25Put_Analytical * d125Put * (d125Put - atmVolQuote->value() * sqrt(T_))/atmVolQuote->value();
243
244
245
            //BS vega
246
0
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_vega);
247
0
            barrierOption.recalculate();
248
0
            Real vegaBarBS = (barrierOption.NPV() - priceBS)/sigmaShift_vega;
249
250
0
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() - sigmaShift_vega);//setback
251
252
            //BS volga
253
254
            //vegaBar2
255
            //base NPV
256
0
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_volga);
257
0
            barrierOption.recalculate();
258
0
            Real priceBS2 = barrierOption.NPV();
259
260
            //shifted npv
261
0
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_vega);
262
0
            barrierOption.recalculate();
263
0
            Real vegaBarBS2 = (barrierOption.NPV() - priceBS2)/sigmaShift_vega;
264
0
            Real volgaBarBS = (vegaBarBS2 - vegaBarBS)/sigmaShift_volga;
265
266
0
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() 
267
0
                                                                                               - sigmaShift_volga 
268
0
                                                                                               - sigmaShift_vega);//setback
269
270
            //BS Delta
271
            //base delta
272
0
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() + spotShift_delta);//shift forth
273
0
            barrierOption.recalculate();
274
0
            Real priceBS_delta1 = barrierOption.NPV();
275
276
0
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() - 2 * spotShift_delta);//shift back
277
0
            barrierOption.recalculate();
278
0
            Real priceBS_delta2 = barrierOption.NPV();
279
280
0
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() +  spotShift_delta);//set back
281
0
            Real deltaBar1 = (priceBS_delta1 - priceBS_delta2)/(2.0*spotShift_delta);
282
283
            //shifted delta
284
0
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_vanna);//shift sigma
285
0
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() + spotShift_delta);//shift forth
286
0
            barrierOption.recalculate();
287
0
            priceBS_delta1 = barrierOption.NPV();
288
289
0
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() - 2 * spotShift_delta);//shift back
290
0
            barrierOption.recalculate();
291
0
            priceBS_delta2 = barrierOption.NPV();
292
293
0
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() +  spotShift_delta);//set back
294
0
            Real deltaBar2 = (priceBS_delta1 - priceBS_delta2)/(2.0*spotShift_delta);
295
296
0
            Real vannaBarBS = (deltaBar2 - deltaBar1)/sigmaShift_vanna;
297
298
0
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() - sigmaShift_vanna);//set back
299
300
            //Matrix
301
0
            Matrix A(3,3,0.0);
302
303
            //analytical
304
0
            A[0][0] = vegaAtm_Analytical;
305
0
            A[0][1] = vega25Call_Analytical;
306
0
            A[0][2] = vega25Put_Analytical;
307
0
            A[1][0] = vannaAtm_Analytical;
308
0
            A[1][1] = vanna25Call_Analytical;
309
0
            A[1][2] = vanna25Put_Analytical;
310
0
            A[2][0] = volgaAtm_Analytical;
311
0
            A[2][1] = volga25Call_Analytical;
312
0
            A[2][2] = volga25Put_Analytical;
313
314
0
            Array b(3,0.0);
315
0
            b[0] = vegaBarBS;
316
0
            b[1] = vannaBarBS;
317
0
            b[2] = volgaBarBS;
318
319
0
            Array q = inverse(A) * b;
320
321
            //touch probability
322
0
            CumulativeNormalDistribution cnd;
323
0
            Real mu = domesticTS_->zeroRate(T_, Continuous).rate() - foreignTS_->zeroRate(T_, Continuous).rate() - pow(atmVol_->value(), 2.0)/2.0;
324
0
            Real h2 = (log(arguments_.barrier/x0Quote->value()) + mu*T_)/(atmVol_->value()*sqrt(T_));
325
0
            Real h2Prime = (log(x0Quote->value()/arguments_.barrier) + mu*T_)/(atmVol_->value()*sqrt(T_));
326
0
            Real probTouch = 0.0;
327
0
            if(arguments_.barrierType == Barrier::UpIn || arguments_.barrierType == Barrier::UpOut)
328
0
                probTouch = cnd(h2Prime) + pow(arguments_.barrier/x0Quote->value(), 2.0*mu/pow(atmVol_->value(), 2.0))*cnd(-h2);
329
0
            else
330
0
                probTouch = cnd(-h2Prime) + pow(arguments_.barrier/x0Quote->value(), 2.0*mu/pow(atmVol_->value(), 2.0))*cnd(h2);
331
0
            Real p_survival = 1.0 - probTouch;
332
333
0
            Real lambda = p_survival ;
334
0
            Real adjust = q[0]*(priceAtmCallMkt - priceAtmCallBS) 
335
0
                        + q[1]*(price25CallMkt - price25CallBS)
336
0
                        + q[2]*(price25PutMkt - price25PutBS);
337
0
            Real outPrice = priceBS + lambda*adjust;//
338
0
            Real inPrice;
339
340
            //adapt Vanilla delta
341
0
            if (adaptVanDelta_) {
342
0
                outPrice += lambda*(bsPriceWithSmile_ - vanillaOption);
343
                //capfloored by (0, vanilla)
344
0
                outPrice = std::max(0.0, std::min(bsPriceWithSmile_, outPrice));
345
0
                inPrice = bsPriceWithSmile_ - outPrice;
346
0
            }
347
0
            else{
348
                //capfloored by (0, vanilla)
349
0
                outPrice = std::max(0.0, std::min(vanillaOption, outPrice));
350
0
                inPrice = vanillaOption - outPrice;
351
0
            }
352
353
0
            if(arguments_.barrierType == Barrier::DownOut || arguments_.barrierType == Barrier::UpOut)
354
0
                results_.value = outPrice;
355
0
            else
356
0
                results_.value = inPrice;
357
0
            results_.additionalResults["VanillaPrice"] = vanillaOption;
358
0
            results_.additionalResults["BarrierInPrice"] = inPrice;
359
0
            results_.additionalResults["BarrierOutPrice"] = outPrice;
360
0
            results_.additionalResults["lambda"] = lambda;
361
0
         }
362
0
    }
363
}