Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
localvol.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19// clang-format off
20#include <boost/test/unit_test.hpp>
21#include <boost/test/data/test_case.hpp>
22// clang-format on
23
31
32#include <oret/toplevelfixture.hpp>
33
35
38
39#include <ql/exercise.hpp>
40#include <ql/time/daycounters/actualactual.hpp>
41#include <ql/quotes/simplequote.hpp>
42#include <ql/instruments/vanillaoption.hpp>
43#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
44#include <ql/processes/stochasticprocessarray.hpp>
45#include <ql/termstructures/yield/flatforward.hpp>
46#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
47#include <ql/time/calendars/nullcalendar.hpp>
48#include <ql/pricingengines/blackformula.hpp>
49#include <ql/termstructures/volatility/sabr.hpp>
50
51#include <iomanip>
52
53using namespace ore::data;
54using namespace QuantLib;
55using namespace QuantExt;
56
57BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
58
59BOOST_AUTO_TEST_SUITE(LocalVolTest)
60
61namespace {
62void testCalibrationInstrumentRepricing(const std::vector<Date>& expiries, const std::vector<Real>& moneyness,
63 const QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess>& process,
64 const Size timeStepsPerYear, const Size paths, const Real tol) {
65
66 // set up a local vol model with simulation dates = expiries, calibrated to options (expiry, moneyness) with
67 // the expiry taken from the expiries vector and the moneyness taken from the moneyness vector
68
69 std::set<Date> simDates(expiries.begin(), expiries.end());
70
71 LocalVolModelBuilder builder(process->riskFreeRate(), process, simDates, std::set<Date>(), timeStepsPerYear,
72 LocalVolModelBuilder::Type::AndreasenHuge, moneyness, false);
73
74 Model::McParams mcParams;
75 mcParams.regressionOrder = 1;
76 auto localVol = QuantLib::ext::make_shared<LocalVol>(paths, "EUR", process->riskFreeRate(), "EQ-DUMMY", "EUR",
77 builder.model(), mcParams, simDates);
78
79 // loop over the calibration options and price them in the local vol model using MC
80 // the result should be close to the market price of the options
81
82 // engine to compute the market price
83 auto marketEngine = QuantLib::ext::make_shared<AnalyticEuropeanEngine>(process);
84
85 // context against we can run the script engine, add some variables here, the rest follows below
86 auto context = QuantLib::ext::make_shared<Context>();
87 context->scalars["Option"] = RandomVariable(paths, 0.0);
88 context->scalars["Underlying"] = IndexVec{paths, "EQ-DUMMY"};
89 context->scalars["PayCcy"] = CurrencyVec{paths, "EUR"};
90
91 // script engine to price a vanilla option
92 ScriptEngine scriptEngine(
93 ScriptParser("Option = PAY( max( PutCall * (Underlying(Expiry)-Strike), 0), Expiry, Expiry, PayCcy );").ast(),
94 context, localVol);
95
96 Real maxError = 0.0;
97 for (Size i = 0; i < expiries.size(); ++i) {
98 // compute the atm forward level for the given expiry
99 Real atmStrike = process->x0() / process->riskFreeRate()->discount(expiries[i]) *
100 process->dividendYield()->discount(expiries[i]);
101
102 for (Size j = 0; j < moneyness.size(); ++j) {
103
104 // skip check for options that are too far out of the money
105 Real t = process->riskFreeRate()->timeFromReference(expiries[i]);
106 if (std::fabs(moneyness[j]) > 3.72)
107 continue;
108
109 // set up the option and compute the market price
110 Option::Type optionType = moneyness[j] > 1.0 ? Option::Call : Option::Put;
111 Real strike =
112 atmStrike * std::exp(process->blackVolatility()->blackVol(t, atmStrike) * std::sqrt(t) * moneyness[j]);
113 VanillaOption option(QuantLib::ext::make_shared<PlainVanillaPayoff>(optionType, strike),
114 QuantLib::ext::make_shared<EuropeanExercise>(expiries[i]));
115 option.setPricingEngine(marketEngine);
116 Real marketPrice = option.NPV();
117
118 // price the option in the script engine
119 context->scalars["PutCall"] = RandomVariable(paths, optionType == Option::Call ? 1.0 : -1.0);
120 context->scalars["Expiry"] = EventVec{paths, expiries[i]};
121 context->scalars["Strike"] = RandomVariable(paths, strike);
122 scriptEngine.run();
123 Real scriptPrice = expectation(QuantLib::ext::get<RandomVariable>(context->scalars["Option"])).at(0);
124 // check the market price and the script price are close
125 BOOST_TEST_MESSAGE("expiry=" << QuantLib::io::iso_date(expiries[i]) << " moneyness=" << moneyness[j]
126 << " marketVol = " << process->blackVolatility()->blackVol(t, strike, true)
127 << " marketPrice=" << marketPrice << " mcPrice=" << scriptPrice
128 << " error=" << (scriptPrice - marketPrice));
129 BOOST_CHECK_SMALL(scriptPrice - marketPrice, tol);
130 maxError = std::max(maxError, scriptPrice - marketPrice);
131 }
132 }
133 BOOST_TEST_MESSAGE("max error = " << maxError);
134}
135} // namespace
136
137BOOST_AUTO_TEST_CASE(testFlatVols) {
138 BOOST_TEST_MESSAGE("Testing LocalVol with flat input vols...");
139
140 Date ref(7, May, 2019);
141 Settings::instance().evaluationDate() = ref;
142
143 std::vector<Date> expiries{ref + 1 * Months, ref + 3 * Months, ref + 6 * Months, ref + 9 * Months,
144 ref + 1 * Years, ref + 2 * Years, ref + 3 * Years, ref + 4 * Years,
145 ref + 5 * Years, ref + 7 * Years, ref + 10 * Years};
146
147 std::vector<Real> moneyness{-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0};
148
149 Handle<YieldTermStructure> r(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.02, Actual365Fixed()));
150 Handle<YieldTermStructure> q(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.03, Actual365Fixed()));
151 Handle<BlackVolTermStructure> vol(QuantLib::ext::make_shared<BlackConstantVol>(0, NullCalendar(), 0.10, Actual365Fixed()));
152 Handle<Quote> spot(QuantLib::ext::make_shared<SimpleQuote>(100.0));
153
154 auto process = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(spot, q, r, vol);
155
156 testCalibrationInstrumentRepricing(expiries, moneyness, process, 20, 10000, 0.30);
157}
158
159BOOST_AUTO_TEST_CASE(testSabrVols) {
160 BOOST_TEST_MESSAGE("Testing LocalVol with sabr input vols...");
161
162 Date ref(7, May, 2019);
163 Settings::instance().evaluationDate() = ref;
164
165 std::vector<Date> expiries{ref + 1 * Months, ref + 3 * Months, ref + 6 * Months, ref + 9 * Months,
166 ref + 1 * Years, ref + 2 * Years, ref + 3 * Years, ref + 4 * Years,
167 ref + 5 * Years, ref + 7 * Years, ref + 10 * Years};
168
169 std::vector<Real> moneyness{-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0};
170
171 Handle<YieldTermStructure> r(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.02, Actual365Fixed()));
172 Handle<YieldTermStructure> q(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.03, Actual365Fixed()));
173
174 class SabrTestSurface : public BlackVolatilityTermStructure {
175 public:
176 SabrTestSurface(const Handle<Quote>& spot, const Handle<YieldTermStructure>& r,
177 const Handle<YieldTermStructure>& q)
178 : BlackVolatilityTermStructure(0, NullCalendar(), Following, ActualActual(ActualActual::ISDA)), spot_(spot), r_(r), q_(q) {}
179 Date maxDate() const override { return Date::maxDate(); }
180 Real minStrike() const override { return 0.0; }
181 Real maxStrike() const override { return QL_MAX_REAL; }
182
183 private:
184 Real blackVolImpl(Time maturity, Real strike) const override {
185 Real forward = spot_->value() / r_->discount(maturity) * q_->discount(maturity);
186 Real w2 = std::min(maturity, 10.0) / 10.0, w1 = 1.0 - w2;
187 Real alpha = 0.17 * w1 + 0.10 * w2;
188 Real beta = 0.99;
189 Real nu = 0.3 * w1 + 0.05 * w2;
190 Real rho = -0.2;
191 return sabrVolatility(strike, forward, maturity, alpha, beta, nu, rho);
192 }
193 Handle<Quote> spot_;
194 Handle<YieldTermStructure> r_, q_;
195 };
196
197 Handle<Quote> spot(QuantLib::ext::make_shared<SimpleQuote>(100.0));
198 Handle<BlackVolTermStructure> vol(QuantLib::ext::make_shared<SabrTestSurface>(spot, r, q));
199
200 auto process = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(spot, q, r, vol);
201
202 testCalibrationInstrumentRepricing(expiries, moneyness, process, 20, 10000, 0.30);
203}
204
205BOOST_AUTO_TEST_SUITE_END()
206
207BOOST_AUTO_TEST_SUITE_END()
ast printer
black scholes model for n underlyings (fx, equity or commodity)
dummy model implementation
local vol model for n underlyings (fx, equity or commodity)
builder for an array of local vol processes
Time maturity
Definition: utilities.cpp:66
RandomVariable expectation(const RandomVariable &r)
scriptengine
script parser
static script analyser
Real at(const Size i) const
BOOST_AUTO_TEST_CASE(testFlatVols)
Definition: localvol.cpp:137