Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
defaultableequityjumpdiffusionmodel.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2021 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
20
21#include "toplevelfixture.hpp"
22
23#include <ql/currencies/europe.hpp>
24#include <ql/math/randomnumbers/mt19937uniformrng.hpp>
25#include <ql/models/marketmodels/browniangenerators/sobolbrowniangenerator.hpp>
26#include <ql/option.hpp>
27#include <ql/pricingengines/blackformula.hpp>
28#include <ql/quotes/simplequote.hpp>
29#include <ql/termstructures/credit/flathazardrate.hpp>
30#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
31#include <ql/termstructures/yield/flatforward.hpp>
32#include <ql/time/calendars/nullcalendar.hpp>
33#include <ql/time/daycounters/actual365fixed.hpp>
34#include <ql/timegrid.hpp>
35
36#include <boost/test/unit_test.hpp>
37
38using namespace QuantLib;
39using namespace QuantExt;
40
41BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
42
43BOOST_AUTO_TEST_SUITE(DefaultableEquityJumpDiffusionModelTest)
44
45namespace {
46
47// run a naive mc simulation and price defaultable zero bonds and equity call options at the payoffTimes
48
49void runMcSimulation(const QuantLib::ext::shared_ptr<DefaultableEquityJumpDiffusionModel>& model, const Size nPaths,
50 const Size seed, const Size timeSteps, const std::vector<Real>& payoffTimes,
51 const std::vector<Real>& equityCallStrikes, std::vector<Real>& resultDefaultableBonds,
52 std::vector<Real>& resultEquityOptions) {
53
54 TimeGrid grid(payoffTimes.begin(), payoffTimes.end(), timeSteps);
55
56 std::vector<Size> payoffIndices;
57 for (auto const& t : payoffTimes) {
58 payoffIndices.push_back(grid.index(t));
59 }
60
61 resultDefaultableBonds.clear();
62 resultEquityOptions.clear();
63 resultDefaultableBonds.resize(payoffTimes.size(), 0.0);
64 resultEquityOptions.resize(payoffTimes.size(), 0.0);
65
66 auto pathGen = QuantLib::ext::make_shared<SobolBrownianGenerator>(1, grid.size() - 1, SobolBrownianGenerator::Steps, seed,
67 SobolRsg::JoeKuoD7);
68 MersenneTwisterUniformRng mt(seed);
69 std::vector<Real> out(1);
70
71 for (Size p = 0; p < nPaths; ++p) {
72 Real S = model->equity()->equitySpot()->value();
73 Real z = std::log(S);
74 Real B = 1.0;
75 bool jump = false;
76 Size payoffIndex = 0;
77 pathGen->nextPath();
78 for (Size i = 1; i < grid.size(); ++i) {
79 // simulate path (without jumps)
80 pathGen->nextStep(out);
81 if (close_enough(S, 0.0))
82 continue;
83 Real t0 = grid[i - 1];
84 Real t1 = grid[i];
85 Real r = model->r(t0);
86 Real q = model->q(t0);
87 Real h = model->h(t0, S);
88 Real sigma = model->sigma(t0);
89 z += (r - q + model->eta() * h - 0.5 * sigma * sigma) * (t1 - t0) + sigma * std::sqrt(t1 - t0) * out[0];
90 B *= std::exp(-r * (t1 - t0));
91
92 // do we have a jump?
93 jump = jump || mt.nextReal() < h * (t1 - t0);
94
95 // jump to eta * S(t), as in process definition
96 S = std::exp(z) * (jump ? (1.0 - model->eta()) : 1.0);
97
98 // jump to zero, corresponding to calibration methodology
99 // S = std::exp(z) * (jump ? 0.0 : 1.0);
100
101 // simulate payoffs
102 if (i == payoffIndices[payoffIndex]) {
103 if (!jump) {
104 resultDefaultableBonds[payoffIndex] += B / static_cast<Real>(nPaths);
105 }
106 resultEquityOptions[payoffIndex] +=
107 std::max(S - equityCallStrikes[payoffIndex], 0.0) * B / static_cast<Real>(nPaths);
108 ++payoffIndex;
109 }
110 }
111 }
112}
113
114} // namespace
115
117
118 BOOST_TEST_MESSAGE("Test defaultable equity jump diffusion model calibration with closed form bootstrap for p=0");
119
120 Real S0 = 100.0;
121 std::vector<Real> stepTimes = {1.0, 2.0, 3.0, 4.0, 5.0};
122
123 Handle<YieldTermStructure> rate(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.01, Actual365Fixed()));
124 Handle<YieldTermStructure> dividend(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.02, Actual365Fixed()));
125 Handle<BlackVolTermStructure> vol(QuantLib::ext::make_shared<BlackConstantVol>(0, NullCalendar(), 0.3, Actual365Fixed()));
126 Handle<DefaultProbabilityTermStructure> creditCurve(
127 QuantLib::ext::make_shared<FlatHazardRate>(0, NullCalendar(), 0.0050, Actual365Fixed()));
128
129 auto equity = QuantLib::ext::make_shared<EquityIndex2>("myEqIndex", NullCalendar(), EURCurrency(),
130 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(S0)), rate, dividend);
131
132 std::vector<Real> strikes;
133 for (auto const& t : stepTimes) {
134 Real forward = equity->equitySpot()->value() * equity->equityDividendCurve()->discount(t) /
135 equity->equityForecastCurve()->discount(t);
136 strikes.push_back(forward);
137 }
138
139 /* for smaller etas (like 0.5) the mc simulation will produce higher call prices than the market because
140 - in the model calibration we assume that after a jump to default the equity does not contribute
141 to the option payoff
142 - in the mc simulation the equity might contribute to the call payoff for eta << 1, if the after-jump
143 equity price is still above the strike (atm forward)
144 */
145
146 std::vector<Real> etas = {1.0, 0.9, 0.8};
147
148 for (auto const& eta : etas) {
149
150 std::vector<Real> resultDefaultableBonds, resultEquityOptions;
151 // mode does not matter, since p=0 and we don't enforce Fokker-Planck bootstrap
152 auto modelBuilder = QuantLib::ext::make_shared<DefaultableEquityJumpDiffusionModelBuilder>(
153 stepTimes, equity, vol, creditCurve, 0.0, eta, false, 24, 100, 1E-4, 1.5, Null<Real>(),
154 DefaultableEquityJumpDiffusionModelBuilder::BootstrapMode::Simultaneously, false);
155 auto model = *modelBuilder->model();
156
157 runMcSimulation(model, 100000, 121, 5 * 24, stepTimes, strikes, resultDefaultableBonds, resultEquityOptions);
158
159 Real tol = 0.1; // 0.1 percent
160 BOOST_TEST_MESSAGE(std::right << std::setw(5) << "p" << std::setw(5) << "eta" << std::setw(5) << "t"
161 << std::setw(16) << "h0" << std::setw(16) << "sigma" << std::setw(16) << "bond mc"
162 << std::setw(16) << "bond mkt" << std::setw(16) << "equityCall mc"
163 << std::setw(16) << "equityCall mkt" << std::setw(16) << "bond err %"
164 << std::setw(16) << "eqCall err %");
165 for (Size i = 0; i < stepTimes.size(); ++i) {
166 Real bondMarket = rate->discount(stepTimes[i]) * creditCurve->survivalProbability(stepTimes[i]);
167 Real eqOptionMarket =
168 blackFormula(Option::Call, strikes[i], strikes[i],
169 std::sqrt(vol->blackVariance(stepTimes[i], strikes[i])), rate->discount(stepTimes[i]));
170 BOOST_TEST_MESSAGE(std::right
171 << std::setw(5) << 0.0 << std::setw(5) << eta << std::setw(5) << stepTimes[i]
172 << std::setw(16) << model->h0()[i] << std::setw(16) << model->sigma()[i] << std::setw(16)
173 << resultDefaultableBonds[i] << std::setw(16) << bondMarket << std::setw(16)
174 << resultEquityOptions[i] << std::setw(16) << eqOptionMarket << std::setw(16)
175 << 100.0 * (resultDefaultableBonds[i] - bondMarket) / bondMarket << std::setw(16)
176 << 100.0 * (resultEquityOptions[i] - eqOptionMarket) / eqOptionMarket);
177 BOOST_CHECK_CLOSE(resultDefaultableBonds[i], bondMarket, tol);
178 BOOST_CHECK_CLOSE(resultEquityOptions[i], eqOptionMarket, tol);
179 }
180 BOOST_TEST_MESSAGE("done.");
181 }
182}
183
184BOOST_AUTO_TEST_CASE(test_nonzero_p) {
185
186 BOOST_TEST_MESSAGE("Test defaultable equity jump diffusion model calibration with Fokker-Planck bootstrap");
187
188 Real S0 = 100.0;
189 std::vector<Real> stepTimes = {1.0, 2.0, 3.0, 4.0, 5.0};
190
191 Handle<YieldTermStructure> rate(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.01, Actual365Fixed()));
192 Handle<YieldTermStructure> dividend(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.02, Actual365Fixed()));
193 Handle<BlackVolTermStructure> vol(QuantLib::ext::make_shared<BlackConstantVol>(0, NullCalendar(), 0.3, Actual365Fixed()));
194 Handle<DefaultProbabilityTermStructure> creditCurve(
195 QuantLib::ext::make_shared<FlatHazardRate>(0, NullCalendar(), 0.0050, Actual365Fixed()));
196
197 auto equity = QuantLib::ext::make_shared<EquityIndex2>("myEqIndex", NullCalendar(), EURCurrency(),
198 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(S0)), rate, dividend);
199
200 std::vector<Real> strikes;
201 for (auto const& t : stepTimes) {
202 Real forward = equity->equitySpot()->value() * equity->equityDividendCurve()->discount(t) /
203 equity->equityForecastCurve()->discount(t);
204 strikes.push_back(forward);
205 }
206
207 /* see above for a comment on small etas in the test */
208
209 std::vector<DefaultableEquityJumpDiffusionModelBuilder::BootstrapMode> modes = {
210 DefaultableEquityJumpDiffusionModelBuilder::BootstrapMode::Alternating,
211 DefaultableEquityJumpDiffusionModelBuilder::BootstrapMode::Simultaneously};
212 std::vector<Real> ps = {0.0, 0.5};
213 std::vector<Real> etas = {1.0, 0.9};
214
215 for (auto const& mode : modes) {
216 BOOST_TEST_MESSAGE(
217 "Bootstrap mode = " << (mode == DefaultableEquityJumpDiffusionModelBuilder::BootstrapMode::Alternating
218 ? "Alternating"
219 : "Simultaneously"));
220 for (auto const& p : ps) {
221 for (auto const& eta : etas) {
222
223 std::vector<Real> resultDefaultableBonds, resultEquityOptions;
224 auto modelBuilder = QuantLib::ext::make_shared<DefaultableEquityJumpDiffusionModelBuilder>(
225 stepTimes, equity, vol, creditCurve, p, eta, false, 24, 400, 1E-5, 1.5, Null<Real>(), mode, true);
226 auto model = *modelBuilder->model();
227
228 runMcSimulation(model, 100000, 121, 5 * 24, stepTimes, strikes, resultDefaultableBonds,
229 resultEquityOptions);
230
231 Real tol = 0.2; // 0.2 percent
232 BOOST_TEST_MESSAGE(std::right << std::setw(5) << "p" << std::setw(5) << "eta" << std::setw(5) << "t"
233 << std::setw(16) << "h0" << std::setw(16) << "sigma" << std::setw(16)
234 << "bond mc" << std::setw(16) << "bond mkt" << std::setw(16)
235 << "equityCall mc" << std::setw(16) << "equityCall mkt" << std::setw(16)
236 << "bond err %" << std::setw(16) << "eqCall err %");
237 for (Size i = 0; i < stepTimes.size(); ++i) {
238 Real bondMarket = rate->discount(stepTimes[i]) * creditCurve->survivalProbability(stepTimes[i]);
239 Real eqOptionMarket = blackFormula(Option::Call, strikes[i], strikes[i],
240 std::sqrt(vol->blackVariance(stepTimes[i], strikes[i])),
241 rate->discount(stepTimes[i]));
242 BOOST_TEST_MESSAGE(std::right
243 << std::setw(5) << p << std::setw(5) << eta << std::setw(5) << stepTimes[i]
244 << std::setw(16) << model->h0()[i] << std::setw(16) << model->sigma()[i]
245 << std::setw(16) << resultDefaultableBonds[i] << std::setw(16) << bondMarket
246 << std::setw(16) << resultEquityOptions[i] << std::setw(16) << eqOptionMarket
247 << std::setw(16) << 100.0 * (resultDefaultableBonds[i] - bondMarket) / bondMarket
248 << std::setw(16)
249 << 100.0 * (resultEquityOptions[i] - eqOptionMarket) / eqOptionMarket);
250 BOOST_CHECK_CLOSE(resultDefaultableBonds[i], bondMarket, tol);
251 BOOST_CHECK_CLOSE(resultEquityOptions[i], eqOptionMarket, tol);
252 }
253 BOOST_TEST_MESSAGE("done.");
254 }
255 }
256 }
257}
258
259BOOST_AUTO_TEST_SUITE_END()
260
261BOOST_AUTO_TEST_SUITE_END()
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
vector< Real > strikes
BOOST_AUTO_TEST_CASE(test_zero_p)
Fixture that can be used at top level.