• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lballabio / QuantLib / 18905857194

29 Oct 2025 11:09AM UTC coverage: 74.32% (+0.4%) from 73.914%
18905857194

Pull #2344

github

web-flow
Merge d353587bf into d823f4ecb
Pull Request #2344: add multicurve bootstrap

99 of 103 new or added lines in 8 files covered. (96.12%)

216 existing lines in 13 files now uncovered.

57072 of 76792 relevant lines covered (74.32%)

8781066.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.95
/ql/experimental/barrieroption/vannavolgabarrierengine.cpp
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
 <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/experimental/barrieroption/vannavolgabarrierengine.hpp>
21
#include <ql/experimental/barrieroption/vannavolgainterpolation.hpp>
22
#include <ql/math/matrix.hpp>
23
#include <ql/pricingengines/barrier/analyticbarrierengine.hpp>
24
#include <ql/pricingengines/blackdeltacalculator.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,
120✔
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)
120✔
45
    : atmVol_(std::move(atmVol)), vol25Put_(std::move(vol25Put)), vol25Call_(std::move(vol25Call)),
120✔
46
      T_(atmVol_->maturity()), spotFX_(std::move(spotFX)), domesticTS_(std::move(domesticTS)),
120✔
47
      foreignTS_(std::move(foreignTS)), adaptVanDelta_(adaptVanDelta),
120✔
48
      bsPriceWithSmile_(bsPriceWithSmile) {
120✔
49
        QL_REQUIRE(vol25Put_->delta() == -0.25, "25 delta put is required by vanna volga method");
120✔
50
        QL_REQUIRE(vol25Call_->delta() == 0.25, "25 delta call is required by vanna volga method");
120✔
51

52
        QL_REQUIRE(vol25Put_->maturity() == vol25Call_->maturity() &&
120✔
53
                       vol25Put_->maturity() == atmVol_->maturity(),
54
                   "Maturity of 3 vols are not the same");
55

56
        QL_REQUIRE(!domesticTS_.empty(), "domestic yield curve is not defined");
120✔
57
        QL_REQUIRE(!foreignTS_.empty(), "foreign yield curve is not defined");
120✔
58

59
        registerWith(atmVol_);
240✔
60
        registerWith(vol25Put_);
240✔
61
        registerWith(vol25Call_);
240✔
62
        registerWith(spotFX_);
240✔
63
        registerWith(domesticTS_);
240✔
64
        registerWith(foreignTS_);
120✔
65
    }
120✔
66

67
    void VannaVolgaBarrierEngine::calculate() const {
120✔
68

69
        QL_REQUIRE(arguments_.barrierType == Barrier::UpIn || arguments_.barrierType == Barrier::UpOut ||
120✔
70
            arguments_.barrierType == Barrier::DownIn || arguments_.barrierType == Barrier::DownOut,
71
            "Invalid barrier type");
72

73
        const Real sigmaShift_vega = 0.0001;
74
        const Real sigmaShift_volga = 0.0001;
75
        const Real spotShift_delta = 0.0001 * spotFX_->value();
120✔
76
        const Real sigmaShift_vanna = 0.0001;
77

78
        Handle<Quote> x0Quote(
79
            ext::make_shared<SimpleQuote>(spotFX_->value())); //used for shift
240✔
80
        Handle<Quote> atmVolQuote(
81
            ext::make_shared<SimpleQuote>(atmVol_->value())); //used for shift
240✔
82

83
        ext::shared_ptr<BlackVolTermStructure> blackVolTS =
84
            ext::make_shared<BlackConstantVol>(
120✔
85
                Settings::instance().evaluationDate(),
120✔
86
                NullCalendar(), atmVolQuote, Actual365Fixed());
240✔
87
        ext::shared_ptr<BlackScholesMertonProcess> stochProcess =
88
            ext::make_shared<BlackScholesMertonProcess>(
89
                                 x0Quote,
90
                                 foreignTS_,
120✔
91
                                 domesticTS_,
120✔
92
                                 Handle<BlackVolTermStructure>(blackVolTS));
120✔
93

94
        ext::shared_ptr<PricingEngine> engineBS =
95
            ext::make_shared<AnalyticBarrierEngine>(stochProcess);
120✔
96

97
        BlackDeltaCalculator blackDeltaCalculatorAtm(
98
                        Option::Call, atmVol_->deltaType(), x0Quote->value(),
240✔
99
                        domesticTS_->discount(T_), foreignTS_->discount(T_),
120✔
100
                        atmVol_->value() * sqrt(T_));
480✔
101
        Real atmStrike = blackDeltaCalculatorAtm.atmStrike(atmVol_->atmType());
120✔
102

103
        Real call25Vol = vol25Call_->value();
120✔
104
        Real put25Vol = vol25Put_->value();
120✔
105

106
        BlackDeltaCalculator blackDeltaCalculatorPut25(Option::Put, vol25Put_->deltaType(), x0Quote->value(), 
240✔
107
                                                      domesticTS_->discount(T_), foreignTS_->discount(T_),
120✔
108
                                                      put25Vol * sqrt(T_));
480✔
109
        Real put25Strike = blackDeltaCalculatorPut25.strikeFromDelta(-0.25);
120✔
110
        BlackDeltaCalculator blackDeltaCalculatorCall25(Option::Call, vol25Call_->deltaType(), x0Quote->value(), 
240✔
111
                                                      domesticTS_->discount(T_), foreignTS_->discount(T_),
120✔
112
                                                      call25Vol * sqrt(T_));
480✔
113
        Real call25Strike = blackDeltaCalculatorCall25.strikeFromDelta(0.25);
120✔
114

115

116
        //here use vanna volga interpolated smile to price vanilla
117
        std::vector<Real> strikes;
118
        std::vector<Real> vols;
119
        strikes.push_back(put25Strike);
120✔
120
        vols.push_back(put25Vol);
120✔
121
        strikes.push_back(atmStrike);
120✔
122
        vols.push_back(atmVol_->value());
120✔
123
        strikes.push_back(call25Strike);
120✔
124
        vols.push_back(call25Vol);
120✔
125
        VannaVolga vannaVolga(x0Quote->value(), domesticTS_->discount(T_), foreignTS_->discount(T_), T_);
120✔
126
        Interpolation interpolation = vannaVolga.interpolate(strikes.begin(), strikes.end(), vols.begin());
120✔
127
        interpolation.enableExtrapolation();
128
        const ext::shared_ptr<StrikedTypePayoff> payoff =
129
                                        ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
120✔
130
        Real strikeVol = interpolation(payoff->strike());
120✔
131

132
        //vanilla option price
133
        Real forward = x0Quote->value() * foreignTS_->discount(T_) / domesticTS_->discount(T_);
120✔
134
        Real vanillaOption = blackFormula(payoff->optionType(), payoff->strike(), 
240✔
135
                                      forward, 
136
                                      strikeVol * sqrt(T_),
120✔
137
                                      domesticTS_->discount(T_));
240✔
138
        results_.additionalResults["Forward"] = forward;
120✔
139
        results_.additionalResults["StrikeVol"] = strikeVol;
120✔
140

141
        //spot > barrier up&out 0
142
        if(x0Quote->value() >= arguments_.barrier && arguments_.barrierType == Barrier::UpOut){
120✔
143
            results_.value = 0.0;
×
144
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
×
145
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
×
146
            results_.additionalResults["BarrierOutPrice"] = Real(0.0);
×
147
        }
148
        //spot > barrier up&in vanilla
149
        else if(x0Quote->value() >= arguments_.barrier && arguments_.barrierType == Barrier::UpIn){
120✔
150
            results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
×
151
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
×
152
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
×
153
            results_.additionalResults["BarrierOutPrice"] = Real(0.0);
×
154
        }
155
        //spot < barrier down&out 0
156
        else if(x0Quote->value() <= arguments_.barrier && arguments_.barrierType == Barrier::DownOut){
120✔
157
            results_.value = 0.0;
×
158
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
×
159
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
×
160
            results_.additionalResults["BarrierOutPrice"] = Real(0.0);
×
161
        }
162
        //spot < barrier down&in vanilla
163
        else if(x0Quote->value() <= arguments_.barrier && arguments_.barrierType == Barrier::DownIn){
120✔
164
            results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
×
165
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
×
166
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
×
167
            results_.additionalResults["BarrierOutPrice"] = Real(0.0);
×
168
        }
169
        else{
170

171
            //set up BS barrier option pricing
172
            //only calculate out barrier option price
173
            // in barrier price = vanilla - out barrier
174
            Barrier::Type barrierType;
175
            if(arguments_.barrierType == Barrier::UpOut)
120✔
176
                barrierType = arguments_.barrierType;
177
            else if(arguments_.barrierType == Barrier::UpIn)
100✔
178
                barrierType = Barrier::UpOut;
179
            else if(arguments_.barrierType == Barrier::DownOut)
80✔
180
                barrierType = arguments_.barrierType;
181
            else
182
                barrierType = Barrier::DownOut;
183

184
            BarrierOption barrierOption(barrierType,
185
                                        arguments_.barrier,
186
                                        arguments_.rebate,
187
                                        ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff),
×
188
                                        arguments_.exercise);
120✔
189

190
            barrierOption.setPricingEngine(engineBS);
120✔
191

192
            //BS price with atm vol
193
            Real priceBS = barrierOption.NPV();
120✔
194
            Real price25CallBS = blackFormula(Option::Call,call25Strike,
120✔
195
                                              forward, 
196
                                              atmVol_->value() * sqrt(T_),
120✔
197
                                              domesticTS_->discount(T_));
240✔
198
            Real price25PutBS = blackFormula(Option::Put,put25Strike,
120✔
199
                                              forward,
200
                                              atmVol_->value() * sqrt(T_),
120✔
201
                                              domesticTS_->discount(T_));
240✔
202

203
            //market price
204
            Real price25CallMkt = blackFormula(Option::Call,call25Strike,
120✔
205
                                              forward, 
206
                                              call25Vol * sqrt(T_),
120✔
207
                                              domesticTS_->discount(T_));
240✔
208
            Real price25PutMkt = blackFormula(Option::Put,put25Strike,
120✔
209
                                              forward,
210
                                              put25Vol * sqrt(T_),
120✔
211
                                              domesticTS_->discount(T_));
240✔
212

213

214
            //Analytical Black Scholes formula for vanilla option
215
            NormalDistribution norm;
120✔
216
            Real d1atm = (std::log(forward/atmStrike) 
120✔
217
                           + 0.5*std::pow(atmVolQuote->value(),2.0) * T_)/(atmVolQuote->value() * sqrt(T_));
120✔
218
            Real vegaAtm_Analytical = x0Quote->value() * norm(d1atm) * sqrt(T_) * foreignTS_->discount(T_);
120✔
219
            Real vannaAtm_Analytical = vegaAtm_Analytical/x0Quote->value() *(1.0 - d1atm/(atmVolQuote->value()*sqrt(T_)));
120✔
220
            Real volgaAtm_Analytical = vegaAtm_Analytical * d1atm * (d1atm - atmVolQuote->value() * sqrt(T_))/atmVolQuote->value();
120✔
221

222
            Real d125call = (std::log(forward/call25Strike) 
120✔
223
                           + 0.5*std::pow(atmVolQuote->value(),2.0) * T_)/(atmVolQuote->value() * sqrt(T_));
120✔
224
            Real vega25Call_Analytical = x0Quote->value() * norm(d125call) * sqrt(T_) * foreignTS_->discount(T_);
120✔
225
            Real vanna25Call_Analytical = vega25Call_Analytical/x0Quote->value() *(1.0 - d125call/(atmVolQuote->value()*sqrt(T_)));
120✔
226
            Real volga25Call_Analytical = vega25Call_Analytical * d125call * (d125call - atmVolQuote->value() * sqrt(T_))/atmVolQuote->value();
120✔
227

228
            Real d125Put = (std::log(forward/put25Strike) 
120✔
229
                           + 0.5*std::pow(atmVolQuote->value(),2.0) * T_)/(atmVolQuote->value() * sqrt(T_));
120✔
230
            Real vega25Put_Analytical = x0Quote->value() * norm(d125Put) * sqrt(T_) * foreignTS_->discount(T_);
120✔
231
            Real vanna25Put_Analytical = vega25Put_Analytical/x0Quote->value() *(1.0 - d125Put/(atmVolQuote->value()*sqrt(T_)));
120✔
232
            Real volga25Put_Analytical = vega25Put_Analytical * d125Put * (d125Put - atmVolQuote->value() * sqrt(T_))/atmVolQuote->value();
120✔
233

234

235
            //BS vega
236
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_vega);
120✔
237
            barrierOption.recalculate();
120✔
238
            Real vegaBarBS = (barrierOption.NPV() - priceBS)/sigmaShift_vega;
120✔
239

240
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() - sigmaShift_vega);//setback
120✔
241

242
            //BS volga
243

244
            //vegaBar2
245
            //base NPV
246
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_volga);
120✔
247
            barrierOption.recalculate();
120✔
248
            Real priceBS2 = barrierOption.NPV();
120✔
249

250
            //shifted npv
251
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_vega);
120✔
252
            barrierOption.recalculate();
120✔
253
            Real vegaBarBS2 = (barrierOption.NPV() - priceBS2)/sigmaShift_vega;
120✔
254
            Real volgaBarBS = (vegaBarBS2 - vegaBarBS)/sigmaShift_volga;
120✔
255

256
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() 
120✔
257
                                                                                               - sigmaShift_volga 
120✔
258
                                                                                               - sigmaShift_vega);//setback
259

260
            //BS Delta
261
            //base delta
262
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() + spotShift_delta);//shift forth
120✔
263
            barrierOption.recalculate();
120✔
264
            Real priceBS_delta1 = barrierOption.NPV();
120✔
265

266
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() - 2 * spotShift_delta);//shift back
120✔
267
            barrierOption.recalculate();
120✔
268
            Real priceBS_delta2 = barrierOption.NPV();
120✔
269

270
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() +  spotShift_delta);//set back
120✔
271
            Real deltaBar1 = (priceBS_delta1 - priceBS_delta2)/(2.0*spotShift_delta);
120✔
272

273
            //shifted delta
274
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_vanna);//shift sigma
120✔
275
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() + spotShift_delta);//shift forth
120✔
276
            barrierOption.recalculate();
120✔
277
            priceBS_delta1 = barrierOption.NPV();
120✔
278

279
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() - 2 * spotShift_delta);//shift back
120✔
280
            barrierOption.recalculate();
120✔
281
            priceBS_delta2 = barrierOption.NPV();
120✔
282

283
            ext::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() +  spotShift_delta);//set back
120✔
284
            Real deltaBar2 = (priceBS_delta1 - priceBS_delta2)/(2.0*spotShift_delta);
120✔
285

286
            Real vannaBarBS = (deltaBar2 - deltaBar1)/sigmaShift_vanna;
120✔
287

288
            ext::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() - sigmaShift_vanna);//set back
120✔
289

290
            //Matrix
291
            Matrix A(3,3,0.0);
120✔
292

293
            //analytical
294
            A[0][0] = vegaAtm_Analytical;
120✔
295
            A[0][1] = vega25Call_Analytical;
120✔
296
            A[0][2] = vega25Put_Analytical;
120✔
297
            A[1][0] = vannaAtm_Analytical;
120✔
298
            A[1][1] = vanna25Call_Analytical;
120✔
299
            A[1][2] = vanna25Put_Analytical;
120✔
300
            A[2][0] = volgaAtm_Analytical;
120✔
301
            A[2][1] = volga25Call_Analytical;
120✔
302
            A[2][2] = volga25Put_Analytical;
120✔
303

304
            Array b(3,0.0);
120✔
305
            b[0] = vegaBarBS;
120✔
306
            b[1] = vannaBarBS;
120✔
307
            b[2] = volgaBarBS;
120✔
308

309
            Array q = inverse(A) * b;
120✔
310

311
            //touch probability
312
            CumulativeNormalDistribution cnd;
120✔
313
            Real mu = domesticTS_->zeroRate(T_, Continuous).rate() - foreignTS_->zeroRate(T_, Continuous).rate() - pow(atmVol_->value(), 2.0)/2.0;
240✔
314
            Real h2 = (log(arguments_.barrier/x0Quote->value()) + mu*T_)/(atmVol_->value()*sqrt(T_));
120✔
315
            Real h2Prime = (log(x0Quote->value()/arguments_.barrier) + mu*T_)/(atmVol_->value()*sqrt(T_));
120✔
316
            Real probTouch = 0.0;
317
            if(arguments_.barrierType == Barrier::UpIn || arguments_.barrierType == Barrier::UpOut)
120✔
318
                probTouch = cnd(h2Prime) + pow(arguments_.barrier/x0Quote->value(), 2.0*mu/pow(atmVol_->value(), 2.0))*cnd(-h2);
40✔
319
            else
320
                probTouch = cnd(-h2Prime) + pow(arguments_.barrier/x0Quote->value(), 2.0*mu/pow(atmVol_->value(), 2.0))*cnd(h2);
80✔
321
            Real p_survival = 1.0 - probTouch;
120✔
322

323
            Real lambda = p_survival ;
120✔
324
            Real adjust = q[1]*(price25CallMkt - price25CallBS)
120✔
325
                        + q[2]*(price25PutMkt - price25PutBS);
120✔
326
            Real outPrice = priceBS + lambda*adjust;//
120✔
327
            Real inPrice;
328

329
            //adapt Vanilla delta
330
            if (adaptVanDelta_) {
120✔
331
                outPrice += lambda*(bsPriceWithSmile_ - vanillaOption);
120✔
332
                //capfloored by (0, vanilla)
333
                outPrice = std::max(0.0, std::min(bsPriceWithSmile_, outPrice));
232✔
334
                inPrice = bsPriceWithSmile_ - outPrice;
120✔
335
            }
336
            else{
337
                //capfloored by (0, vanilla)
UNCOV
338
                outPrice = std::max(0.0, std::min(vanillaOption, outPrice));
×
UNCOV
339
                inPrice = vanillaOption - outPrice;
×
340
            }
341

342
            if(arguments_.barrierType == Barrier::DownOut || arguments_.barrierType == Barrier::UpOut)
120✔
343
                results_.value = outPrice;
60✔
344
            else
345
                results_.value = inPrice;
60✔
346
            results_.additionalResults["VanillaPrice"] = vanillaOption;
120✔
347
            results_.additionalResults["BarrierInPrice"] = inPrice;
120✔
348
            results_.additionalResults["BarrierOutPrice"] = outPrice;
120✔
349
            results_.additionalResults["lambda"] = lambda;
240✔
350
         }
120✔
351
    }
120✔
352
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc