Coverage Report

Created: 2026-06-08 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/processes/g2process.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2006 Banca Profilo S.p.A.
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/processes/g2process.hpp>
21
#include <ql/processes/eulerdiscretization.hpp>
22
23
namespace QuantLib {
24
25
    G2Process::G2Process(Real a, Real sigma, Real b, Real eta, Real rho,
26
                         const Handle<YieldTermStructure>& termStructure)
27
0
    : a_(a), sigma_(sigma), b_(b), eta_(eta), rho_(rho),
28
0
      xProcess_(new QuantLib::OrnsteinUhlenbeckProcess(a, sigma, 0.0)),
29
0
      yProcess_(new QuantLib::OrnsteinUhlenbeckProcess(b, eta, 0.0)),
30
0
      termStructure_(termStructure) {
31
0
        registerWith(termStructure_);
32
0
    }
33
34
0
    Size G2Process::size() const {
35
0
        return 2;
36
0
    }
37
38
0
    Array G2Process::initialValues() const {
39
0
        Real z1_0 = termStructure_.empty() ? x0_ : phi(0.0);
40
0
        return { z1_0, y0_ };
41
0
    }
42
43
0
    Array G2Process::drift(Time t, const Array& z) const {
44
        // Drift in shifted coordinates z1 = x + phi(t), z2 = y:
45
        //   dz1 = (-a*z1 + a*phi(t) + phi'(t)) dt + sigma dW1
46
        //   dz2 = -b*z2 dt + eta dW2
47
0
        Real shiftDrift = 0.0;
48
0
        if (!termStructure_.empty()) {
49
0
            const Real h = 1.0e-4;
50
0
            Real phi_t  = phi(t);
51
0
            Real phi_th = phi(t + h);
52
0
            Real phiPrime = (phi_th - phi_t) / h;
53
0
            shiftDrift = a_ * phi_t + phiPrime;
54
0
        }
55
0
        return {
56
0
            xProcess_->drift(t, z[0]) + shiftDrift,
57
0
            yProcess_->drift(t, z[1])
58
0
        };
59
0
    }
60
61
0
    Matrix G2Process::diffusion(Time, const Array&) const {
62
        /* the correlation matrix is
63
           |  1   rho |
64
           | rho   1  |
65
           whose square root (which is used here) is
66
           |  1          0       |
67
           | rho   sqrt(1-rho^2) |
68
        */
69
0
        Matrix tmp(2,2);
70
0
        Real sigma1 = sigma_;
71
0
        Real sigma2 = eta_;
72
0
        tmp[0][0] = sigma1;       tmp[0][1] = 0.0;
73
0
        tmp[1][0] = rho_*sigma1;  tmp[1][1] = std::sqrt(1.0-rho_*rho_)*sigma2;
74
0
        return tmp;
75
0
    }
76
77
    Array G2Process::expectation(Time t0, const Array& z0,
78
0
                                 Time dt) const {
79
        // E[z1(t0+dt) | z1(t0)] = z1(t0)*exp(-a*dt)
80
        //                       + phi(t0+dt) - phi(t0)*exp(-a*dt)
81
        // E[z2(t0+dt) | z2(t0)] = z2(t0)*exp(-b*dt)
82
0
        Real shiftExp = 0.0;
83
0
        if (!termStructure_.empty()) {
84
0
            shiftExp = phi(t0 + dt) - phi(t0) * std::exp(-a_ * dt);
85
0
        }
86
0
        return {
87
0
            xProcess_->expectation(t0, z0[0], dt) + shiftExp,
88
0
            yProcess_->expectation(t0, z0[1], dt)
89
0
        };
90
0
    }
91
92
0
    Matrix G2Process::stdDeviation(Time t0, const Array& x0, Time dt) const {
93
        /* the correlation matrix is
94
           |  1   rho |
95
           | rho   1  |
96
           whose square root (which is used here) is
97
           |  1          0       |
98
           | rho   sqrt(1-rho^2) |
99
        */
100
0
        Matrix tmp(2,2);
101
0
        Real sigma1 = xProcess_->stdDeviation(t0, x0[0], dt);
102
0
        Real sigma2 = yProcess_->stdDeviation(t0, x0[1], dt);
103
0
        Real expa = std::exp(-a_*dt), expb = std::exp(-b_*dt);
104
0
        Real H = (rho_*sigma_*eta_)/(a_+b_)*(1-expa*expb);
105
0
        Real den =
106
0
            (0.5*sigma_*eta_)*std::sqrt((1-expa*expa)*(1-expb*expb)/(a_*b_));
107
0
        Real newRho = H/den;
108
0
        tmp[0][0] = sigma1;
109
0
        tmp[0][1] = 0.0;
110
0
        tmp[1][0] = newRho*sigma2;
111
0
        tmp[1][1] = std::sqrt(1.0-newRho*newRho)*sigma2;
112
0
        return tmp;
113
0
    }
114
115
0
    Matrix G2Process::covariance(Time t0, const Array& x0, Time dt) const {
116
0
        Matrix sigma = stdDeviation(t0, x0, dt);
117
0
        Matrix result = sigma*transpose(sigma);
118
0
        return result;
119
0
    }
120
121
0
    Real G2Process::x0() const {
122
0
        return termStructure_.empty() ? x0_ : phi(0.0);
123
0
    }
124
125
0
    Real G2Process::y0() const {
126
0
        return y0_;
127
0
    }
128
129
0
    Real G2Process::a() const {
130
0
        return a_;
131
0
    }
132
133
0
    Real G2Process::sigma() const {
134
0
        return sigma_;
135
0
    }
136
137
0
    Real G2Process::b() const {
138
0
        return b_;
139
0
    }
140
141
0
    Real G2Process::eta() const {
142
0
        return eta_;
143
0
    }
144
145
0
    Real G2Process::rho() const {
146
0
        return rho_;
147
0
    }
148
149
0
    const Handle<YieldTermStructure>& G2Process::termStructure() const {
150
0
        return termStructure_;
151
0
    }
152
153
0
    Real G2Process::phi(Time t) const {
154
0
        QL_REQUIRE(!termStructure_.empty(),
155
0
                   "no term structure given to G2Process");
156
0
        Rate forward = termStructure_->forwardRate(t, t, Continuous, NoFrequency);
157
0
        Real temp1 = sigma_*(1.0-std::exp(-a_*t))/a_;
158
0
        Real temp2 = eta_ *(1.0-std::exp(-b_*t))/b_;
159
0
        return 0.5*temp1*temp1 + 0.5*temp2*temp2 + rho_*temp1*temp2 + forward;
160
0
    }
161
162
0
    Rate G2Process::shortRate(Time, Real z1, Real z2) const {
163
        // The simulated state already includes phi(t) in z1, so r = z1 + z2.
164
0
        return z1 + z2;
165
0
    }
166
167
168
    G2ForwardProcess::G2ForwardProcess(Real a, Real sigma, Real b, Real eta, Real rho,
169
                                       const Handle<YieldTermStructure>& termStructure)
170
0
    : a_(a), sigma_(sigma), b_(b), eta_(eta), rho_(rho),
171
0
      xProcess_(new QuantLib::OrnsteinUhlenbeckProcess(a, sigma, 0.0)),
172
0
      yProcess_(new QuantLib::OrnsteinUhlenbeckProcess(b, eta, 0.0)),
173
0
      termStructure_(termStructure) {
174
0
        registerWith(termStructure_);
175
0
    }
176
177
0
    Size G2ForwardProcess::size() const {
178
0
        return 2;
179
0
    }
180
181
0
    Array G2ForwardProcess::initialValues() const {
182
0
        Real z1_0 = termStructure_.empty() ? x0_ : phi(0.0);
183
0
        return { z1_0, y0_ };
184
0
    }
185
186
0
    Array G2ForwardProcess::drift(Time t, const Array& z) const {
187
0
        Real shiftDrift = 0.0;
188
0
        if (!termStructure_.empty()) {
189
0
            const Real h = 1.0e-4;
190
0
            Real phi_t  = phi(t);
191
0
            Real phi_th = phi(t + h);
192
0
            Real phiPrime = (phi_th - phi_t) / h;
193
0
            shiftDrift = a_ * phi_t + phiPrime;
194
0
        }
195
0
        return {
196
0
            xProcess_->drift(t, z[0]) + xForwardDrift(t, T_) + shiftDrift,
197
0
            yProcess_->drift(t, z[1]) + yForwardDrift(t, T_)
198
0
        };
199
0
    }
200
201
0
    Matrix G2ForwardProcess::diffusion(Time, const Array&) const {
202
0
        Matrix tmp(2,2);
203
0
        Real sigma1 = sigma_;
204
0
        Real sigma2 = eta_;
205
0
        tmp[0][0] = sigma1;       tmp[0][1] = 0.0;
206
0
        tmp[1][0] = rho_*sigma1;  tmp[1][1] = std::sqrt(1.0-rho_*rho_)*sigma2;
207
0
        return tmp;
208
0
    }
209
210
    Array G2ForwardProcess::expectation(Time t0, const Array& z0,
211
0
                                        Time dt) const {
212
0
        Real shiftExp = 0.0;
213
0
        if (!termStructure_.empty()) {
214
0
            shiftExp = phi(t0 + dt) - phi(t0) * std::exp(-a_ * dt);
215
0
        }
216
0
        return {
217
0
            xProcess_->expectation(t0, z0[0], dt) - Mx_T(t0, t0+dt, T_) + shiftExp,
218
0
            yProcess_->expectation(t0, z0[1], dt) - My_T(t0, t0+dt, T_)
219
0
        };
220
0
    }
221
222
0
    Matrix G2ForwardProcess::stdDeviation(Time t0, const Array& x0, Time dt) const {
223
0
        Matrix tmp(2,2);
224
0
        Real sigma1 = xProcess_->stdDeviation(t0, x0[0], dt);
225
0
        Real sigma2 = yProcess_->stdDeviation(t0, x0[1], dt);
226
0
        Real expa = std::exp(-a_*dt), expb = std::exp(-b_*dt);
227
0
        Real H = (rho_*sigma_*eta_)/(a_+b_)*(1-expa*expb);
228
0
        Real den =
229
0
            (0.5*sigma_*eta_)*std::sqrt((1-expa*expa)*(1-expb*expb)/(a_*b_));
230
0
        Real newRho = H/den;
231
0
        tmp[0][0] = sigma1;
232
0
        tmp[0][1] = 0.0;
233
0
        tmp[1][0] = newRho*sigma2;
234
0
        tmp[1][1] = std::sqrt(1.0-newRho*newRho)*sigma2;
235
0
        return tmp;
236
0
    }
237
238
0
    Matrix G2ForwardProcess::covariance(Time t0, const Array& x0, Time dt) const {
239
0
        Matrix sigma = stdDeviation(t0, x0, dt);
240
0
        Matrix result = sigma*transpose(sigma);
241
0
        return result;
242
0
    }
243
244
0
    Real G2ForwardProcess::xForwardDrift(Time t, Time T) const {
245
0
        Real expatT = std::exp(-a_*(T-t));
246
0
        Real expbtT = std::exp(-b_*(T-t));
247
248
0
        return -(sigma_*sigma_/a_) * (1-expatT)
249
0
              - (rho_*sigma_*eta_/b_) * (1-expbtT);
250
0
    }
251
252
0
    Real G2ForwardProcess::yForwardDrift(Time t, Time T) const {
253
0
        Real expatT = std::exp(-a_*(T-t));
254
0
        Real expbtT = std::exp(-b_*(T-t));
255
256
0
        return -(eta_*eta_/b_) * (1-expbtT)
257
0
              - (rho_*sigma_*eta_/a_) * (1-expatT);
258
0
    }
259
260
0
    Real G2ForwardProcess::Mx_T(Real s, Real t, Real T) const {
261
0
        Real M;
262
0
        M = ( (sigma_*sigma_)/(a_*a_) + (rho_*sigma_*eta_)/(a_*b_) )
263
0
          * (1-std::exp(-a_*(t-s)));
264
0
        M += -(sigma_*sigma_)/(2*a_*a_) *
265
0
              (std::exp(-a_*(T-t))-std::exp(-a_*(T+t-2*s)));
266
0
        M += -(rho_*sigma_*eta_)/(b_*(a_+b_))
267
0
            * (std::exp(-b_*(T-t)) -std::exp(-b_*T-a_*t+(a_+b_)*s));
268
0
        return M;
269
0
    }
270
271
0
    Real G2ForwardProcess::My_T(Real s, Real t, Real T) const {
272
0
        Real M;
273
0
        M = ( (eta_*eta_)/(b_*b_) + (rho_*sigma_*eta_)/(a_*b_) )
274
0
          * (1-std::exp(-b_*(t-s)));
275
0
        M += -(eta_*eta_)/(2*b_*b_) *
276
0
              (std::exp(-b_*(T-t))-std::exp(-b_*(T+t-2*s)));
277
0
        M += -(rho_*sigma_*eta_)/(a_*(a_+b_))
278
0
            * (std::exp(-a_*(T-t))-std::exp(-a_*T-b_*t+(a_+b_)*s));
279
0
        return M;
280
0
    }
281
282
0
    const Handle<YieldTermStructure>& G2ForwardProcess::termStructure() const {
283
0
        return termStructure_;
284
0
    }
285
286
0
    Real G2ForwardProcess::phi(Time t) const {
287
0
        QL_REQUIRE(!termStructure_.empty(),
288
0
                   "no term structure given to G2ForwardProcess");
289
0
        Rate forward = termStructure_->forwardRate(t, t, Continuous, NoFrequency);
290
0
        Real temp1 = sigma_*(1.0-std::exp(-a_*t))/a_;
291
0
        Real temp2 = eta_ *(1.0-std::exp(-b_*t))/b_;
292
0
        return 0.5*temp1*temp1 + 0.5*temp2*temp2 + rho_*temp1*temp2 + forward;
293
0
    }
294
295
0
    Rate G2ForwardProcess::shortRate(Time, Real z1, Real z2) const {
296
0
        return z1 + z2;
297
0
    }
298
299
}
300