Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
brownianbridgepathinterpolator.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2020 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 <ql/math/comparison.hpp>
22#include <ql/math/distributions/normaldistribution.hpp>
23#include <ql/math/randomnumbers/mt19937uniformrng.hpp>
24
25using namespace QuantLib;
26
27namespace QuantExt {
28
29namespace {
30RandomVariable createNewVariate(const Size p, const MersenneTwisterUniformRng& mt) {
31 InverseCumulativeNormal icn;
32 RandomVariable r(p);
33 for (Size i = 0; i < p; ++i) {
34 r.set(i, icn(mt.nextReal()));
35 }
36 return r;
37}
38} // namespace
39
40void interpolateVariatesWithBrownianBridge(const std::vector<QuantLib::Real>& times,
41 std::vector<std::vector<QuantExt::RandomVariable>>& variates,
42 const Size seed) {
43
44 // check inputs
45
46 for (Size i = 1; i < times.size(); ++i) {
47 QL_REQUIRE(times[i] > times[i - 1] && !QuantLib::close_enough(times[i], times[i - 1]),
48 "interpolateVariatesWithBrownianBridge():: times must be ascending, got "
49 << times[i - 1] << " at " << i - 1 << " and " << times[i] << " at " << i);
50 }
51
52 QL_REQUIRE(variates.size() == times.size(), "interpolateVariatesWithBrownianBridge(): variates vector size ("
53 << variates.size() << ") must match times vector size ("
54 << times.size() << ")");
55
56 Size d = Null<Size>(), p = Null<Size>();
57 for (Size i = 0; i < times.size(); ++i) {
58 if (variates[i].empty())
59 continue;
60 if (d == Null<Size>()) {
61 d = variates[i].size();
62 p = variates[i].front().size();
63 QL_REQUIRE(p > 0,
64 "interpolatedVariatesWithBrownianBridge(): found RandomVariable of size 0 at time step " << i);
65 } else {
66 QL_REQUIRE(variates[i].size() == d,
67 "interpolateVariatesWithBrownianBridge(): variates dimension at time step "
68 << i << " (" << variates[i].size() << ") is expected to be " << d);
69 for (Size j = 0; j < d; ++j) {
70 QL_REQUIRE(variates[i][j].size() == p, "interpolateVariatesWithBrownianBridge(): variate at time step "
71 << i << " dimension " << j << " has "
72 << variates[i][j].size() << " paths, expected " << p);
73 }
74 }
75 }
76
77 QL_REQUIRE(d != Null<Size>(),
78 "interpolateVariatesWithBrownianBridge(): expected at least one time step with non-empty variate");
79
80 // we construct a Wiener process W(t) from the initially given variates by adding the given variates up
81 // on a unit distance time grid 1, 2, 3, 4, ... which we call the "standardised time grid".
82
83 std::vector<std::vector<QuantExt::RandomVariable>> paths(times.size(), std::vector<QuantExt::RandomVariable>(d));
84 std::vector<Real> standardisedTimes(times.size(), Null<Real>());
85
86 Size counter = 0;
87 Size lastTimeStep = Null<Size>();
88
89 std::vector<bool> initialised(times.size(), false);
90 for (Size i = 0; i < times.size(); ++i) {
91 if (variates[i].empty())
92 continue;
93 standardisedTimes[i] = static_cast<Real>(++counter);
94 for (Size j = 0; j < d; ++j) {
95 paths[i][j] = lastTimeStep == Null<Size>() ? variates[i][j] : paths[lastTimeStep][j] + variates[i][j];
96 }
97 lastTimeStep = i;
98 initialised[i] = true;
99 }
100
101 // next we fill in the missing times and create interpolated variates using a Brownian bridge, this will generate
102 // intermediate times on the unit distance grid from above as a scaled version of the original times
103
104 MersenneTwisterUniformRng mt(seed);
105
106 for (Size i = 0; i < times.size(); ++i) {
107 if (initialised[i])
108 continue;
109 // we interpolate between time indices l and r
110 Size l = i + 1, r = i;
111 while (l > 0 && variates[--l].empty())
112 ;
113 while (r < variates.size() - 1 && variates[++r].empty())
114 ;
115 if (variates[r].empty()) {
116 // if there is no right point to interpolate we just continue the path with new variates ...
117 QL_REQUIRE(!variates[l].empty(),
118 "interpolateVariatesWithBrownianBridge(): internal error, expected variates["
119 << l << "] to be non-empty");
120 for (Size k = l + 1; k <= r; ++k) {
121 for (Size j = 0; j < d; ++j) {
122 paths[k][j] = paths[k - 1][j] + createNewVariate(p, mt);
123 }
124 standardisedTimes[k] = standardisedTimes[k - 1] + 1.0;
125 initialised[k] = true;
126 }
127 } else {
128 // ... otherwise we interpolate using a Brownian bridge, this relies on Theorem 14.2 in
129 // Mark Joshi, "More Mathematical Finance", Pilot Whale Press
130 RandomVariable t, T;
131 // compute the standardised times first
132 if (variates[l].empty()) {
133 QL_REQUIRE(l == 0, "interpolateVariatesWithBrownianBridge(): internal error, expected l==0, got " << l);
134 for (Size k = l; k < r; ++k) {
135 standardisedTimes[k] = times[k] / times[r];
136 }
137 } else {
138 for (Size k = l; k < r; ++k) {
139 standardisedTimes[k] = standardisedTimes[l] + (times[k] - times[l]) / (times[r] - times[l]);
140 }
141 }
142 // now interpolate the path
143 for (Size k = l; k < r; ++k) {
144 if (variates[l].empty() && k == l) {
145 // interpolate between 0 and the first non-empty point
146 t = RandomVariable(p, standardisedTimes[k]);
147 T = RandomVariable(p, standardisedTimes[r]);
148 } else if (k > l) {
149 // interpolate between two non-empty points
150 t = RandomVariable(p, standardisedTimes[k] - standardisedTimes[k - 1]);
151 T = RandomVariable(p, standardisedTimes[r] - standardisedTimes[k - 1]);
152 } else {
153 // k=l and variates[l] non empty => nothing to do
154 continue;
155 }
156 RandomVariable stdDev = sqrt(t * (T - t) / T);
157 for (Size j = 0; j < d; ++j) {
158 RandomVariable expectedValue =
159 ((T - t) * (k == l ? RandomVariable(p, 0.0) : paths[k - 1][j]) + t * paths[r][j]) / T;
160 paths[k][j] = expectedValue + stdDev * createNewVariate(p, mt);
161 }
162 initialised[k] = true;
163 }
164 }
165 }
166
167 // finally we calcuate the sequenctial differences on the paths and rescale them to a unit distance grid
168 // in order to get the desired N(0,1) variates on the refined time grid
169
170 for (Size i = 0; i < times.size(); ++i) {
171 RandomVariable scaling(p, std::sqrt(1.0 / (standardisedTimes[i] - (i == 0 ? 0.0 : standardisedTimes[i - 1]))));
172 if (variates[i].empty())
173 variates[i] = std::vector<RandomVariable>(d, RandomVariable(p));
174 for (Size j = 0; j < d; ++j) {
175 variates[i][j] = (i == 0 ? paths[i][j] : paths[i][j] - paths[i - 1][j]) * scaling;
176 }
177 }
178
179 // results are set in variates, exit without returning anything
180
181 return;
182}
183
184} // namespace QuantExt
brownian bridge path interpolator
void interpolateVariatesWithBrownianBridge(const std::vector< QuantLib::Real > &times, std::vector< std::vector< QuantExt::RandomVariable > > &variates, const Size seed)
RandomVariable sqrt(RandomVariable x)